Compare commits

...

621 Commits

Author SHA1 Message Date
1eed0e9b21 introduce separate database-schemas base+rbac (#103)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Co-authored-by: Michael Hönnig <michael@hoennig.de>
Reviewed-on: #103
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-16 15:36:37 +02:00
80d79de5f4 import-legacy-ids-of-booking-items-and-hosting-assets (#101)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #101
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-16 10:00:41 +02:00
860df4c69f user-definable verificationCode and more business-level-validation-tests (#100)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #100
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-12 10:52:44 +02:00
b1ab1afbb6 test deep patch into properties and fix typing while patching array properties (#99)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #99
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-11 18:16:50 +02:00
13f258fb90 fix import with domain setup dns verification (#98)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #98
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-11 13:32:49 +02:00
a7d586f0f7 check-domain-setup-permission (#97)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #97
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-10 13:15:03 +02:00
8e02610679 fix salt problem for yescrypt hashes in HashGenerator (#96)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #96
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-10 10:31:49 +02:00
fbd17a21e2 ceate bookingitems for domain-setup hostingassets (#95)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #95
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-04 11:15:37 +02:00
e57f4bf0c8 add-webspace-gid-and-create-webspace-main-user (#94)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #94
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-09-03 10:28:57 +02:00
8b5cf8adc1 document-potential-rbac-optimizations (#91)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #91
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-09-03 09:37:49 +02:00
0c9931d73a fix running tests from command-line via gw-test (#93)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #93
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-30 10:06:39 +02:00
2bacea7ad9 historic-view (#92)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #92
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-29 17:00:19 +02:00
a1163bfc8d fix-and-improve-test-execution (#90)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #90
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-08-22 11:56:21 +02:00
1eaeade155 real rbac-entities in booking+hosting (#89)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #89
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-21 06:18:36 +02:00
2138b3eed0 fix-domain-setup-rbac-grant-problems (#88)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #88
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-15 10:38:43 +02:00
0763511edd import-email-addresses (#86)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #86
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-12 12:06:12 +02:00
99a26aed8b report multiple zonefile errors, don't stop after first violation (#85)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #85
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-08 15:25:11 +02:00
5046e9a296 import-hosting-domain-assets (#84)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #84
2024-08-08 10:40:34 +02:00
085876c772 improve-performance-of-office-data-import (#83)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #83
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-05 11:48:33 +02:00
e4e1216a85 import-database-users-and-databases (#82)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #82
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-02 10:40:15 +02:00
d6a0511d98 import-unix-user-and-email-aliases (#81)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #81
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-08-01 13:12:58 +02:00
e1fda412ae rbac-optimization (#80)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #80
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-27 10:18:07 +02:00
4d27a98c9a hosting-asset-data-migration (#79)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #79
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-22 11:30:33 +02:00
c191af2ea1 add-ipnumber-validatation (#77)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #77
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-16 10:32:41 +02:00
05e97f4844 TP-202405-filtered_import (#78)
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@solidcharity.com>
Co-authored-by: Dev und Test fuer hsadminng <hsh03-hsngdev@h50.hostsharing.net>
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #78
2024-07-16 10:23:16 +02:00
c32361a83a add-postgresql-instance-user-and-database-validation (#76)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #76
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-15 12:00:34 +02:00
46fce275ae add-mariadb-instance-database-and-user-validations (#75)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #75
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-07-12 10:54:47 +02:00
9d2692add3 add-domain-email-setup-validation (#74)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #74
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-07-11 10:43:47 +02:00
0af389d7c6 add-domain-http-setup-validation (#73)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #73
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-07-10 15:54:02 +02:00
afb6771ed7 HostingAsset-Hierarchie spec in enum HsHostingAssetType and generates PlantUML (#72)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #72
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-07-09 14:32:14 +02:00
f6d66d5712 add-domain-setup-validation (#71)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #71
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-05 11:56:32 +02:00
a77eaefb94 add-email-alias-hosting-asset (#70)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #70
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-03 11:43:08 +02:00
c5722e494f fix HsHostingAssetRepository.findAllByCriteriaImpl query (#69)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #69
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-03 10:36:29 +02:00
409f5e97c7 integrate-sha512-password-hashing (#68)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #68
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-07-01 15:53:50 +02:00
3391ec6cc9 implement password-hashing (not fully integrated yet) (#67)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #67
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-06-28 11:00:15 +02:00
6167ef2221 add-unix-user-hosting-asset-validation (#66)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #66
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-27 12:39:44 +02:00
de88f1d842 hosting-asset-validation-beyond-property-validators (#65)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #65
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-06-24 12:33:14 +02:00
9418303b7c add optional alarm-contact to hosting-asset (#64)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #64
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-21 12:02:07 +02:00
d157730de7 finalize PrivateCloud, Cloud- and ManagedServer and ManagedWebspace Billingtems and HostingAssets (#63)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #63
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-20 11:03:59 +02:00
04d9b43301 BookingItem validity start date today (#62)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #62
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-20 10:44:28 +02:00
62867a4cac booking-item-to-related-hosting-asset-just-1-to-1 (#61)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #61
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-18 13:53:11 +02:00
cbadc6e2c7 mitigate-hosting-asset-fetching-performance-problems (#60)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #60
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-17 16:46:26 +02:00
46dc653174 hierarchical-validation-baseline (#59)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #59
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-14 16:48:00 +02:00
fc2b437a55 add assigned-asset, add more hosting-asset test-data and introduce HsBookingDebitor+hs_booking_debitor_rv (#58)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #58
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-06 13:46:14 +02:00
c23baca47a introduce-booking-project-and-nested-booking-items (#57)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #57
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-06-03 14:45:28 +02:00
23a6f89943 hosting-asset-validation-baseline (#56)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #56
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-05-30 10:45:12 +02:00
2e9e5d6ef0 hosting-asset-validation-for-cloud-server-to-webspace (#54)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #54
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-05-06 10:50:59 +02:00
6c25dddcda Merge pull request 'rename contact.label to contact.caption' (#55) from rename-contact-label-to-caption into master
Reviewed-on: #55
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-05-06 10:09:06 +02:00
Michael Hoennig
85376d51af rename contact.label to contact.caption 2024-05-06 09:26:54 +02:00
a93c097f64 list hosting-assets with debitor, parent and type query-parameters (#52)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #52
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-05-03 10:28:03 +02:00
1201c16094 new subscriber role: generalversammlung (#53)
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@solidcharity.com>
Reviewed-on: #53
Reviewed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Co-committed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-05-03 10:17:47 +02:00
c953b815d5 introduce booking-item-type and check (#51)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #51
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-05-02 13:53:53 +02:00
e09a09cf92 office-related spec-clarifications and -amendmends (contact.emailaddresses+.phonenumbers JSON) (#50)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #50
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-30 12:27:20 +02:00
dbe695c214 allow-multiple-insert-permission-grants (#49)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #49
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-29 11:43:49 +02:00
66332b6de2 introduce-hosting-module (#46)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #46
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-23 11:14:48 +02:00
9806bcd78f conditional insert permission grant (so far just exactly 1 unique for each table) (#48)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #48
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-23 10:42:24 +02:00
4eda99b95a Merge pull request 'fix booking item identity view and some other minor issues' (#47) from fix-booking-item-identity-view into master
Reviewed-on: #47
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-19 10:09:54 +02:00
Michael Hoennig
d8b1d18952 fix booking item identity view and some other minor issues 2024-04-19 10:06:57 +02:00
5b18681e96 revert-upgrade-openapiprocessor-spring-back-to-2022-5 and fix bookingItem.validFrom assertion (#45)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #45
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-17 08:27:08 +02:00
65a4647af9 fix contractual missing check in import-office-data (#43)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #43
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-16 14:39:18 +02:00
661b06859f introduce-booking-module (#41)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #41
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-16 11:21:34 +02:00
f0eb76ee61 upgrade io.openapiprocessor:openapi-processor-spring to 2024.2 (#42)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #42
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-16 10:08:00 +02:00
8d8c5df0b6 test-data-cleanup-via-raw-tables-and-fix-arc-tests (#39)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #39
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-13 13:55:27 +02:00
b0a28200f9 coop-shares-transaction-reverse-entry (#40)
Co-authored-by: Marc O. Sandlus <marc.o.sandlus@hostsharing.net>
Reviewed-on: #40
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-12 11:29:26 +02:00
216886e5f4 add-reverse-mapping-for-assetsharestx-adjustment (#38)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #38
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-11 10:08:45 +02:00
f5de2a8850 coop-assets-transaction-reverse-entry (#37)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #37
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-10 12:44:56 +02:00
48f4cf8ed6 import-cancelled-memberships-if-booking-exist (#36)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #36
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-09 10:22:57 +02:00
4314b647f6 automatically-create-ex-partner-relation (#35)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #35
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-09 10:08:48 +02:00
ec1deb8903 add @Version property to all RBAC-Entities (#34)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #34
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-08 11:21:22 +02:00
44ff30c54a RBAC generator with conditional grants used for REPRESENTATIVE-Relation (#33)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #33
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
2024-04-08 11:16:06 +02:00
896968e110 Merge pull request 'add-salut-and-title-to-person' (#25) from add-salut-and-title-to-person into master
Reviewed-on: #25
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-08 10:28:09 +02:00
Michael Hoennig
552146a98c add title+salutation to EntityPatcherUnitTest 2024-04-08 10:22:10 +02:00
Michael Hoennig
898aa858b1 toShortString without title+salutation 2024-04-08 10:17:02 +02:00
Michael Hoennig
ca952ce748 merging master + rbac-generation 2024-04-08 10:14:22 +02:00
Michael Hoennig
4438e7abd5 Merge remote-tracking branch 'origin/master' into add-salut-and-title-to-person 2024-04-08 10:08:30 +02:00
73c378b456 spring-boot-3-2-upgrade (#32)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #32
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 13:24:25 +02:00
ad04faa21d cleanup-todos (#31)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #31
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 13:14:46 +02:00
277369a960 debitornumbersuffix-as-string (#30)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #30
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 13:09:12 +02:00
87af20a3a1 structured-liquibase-files (#29)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #29
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 12:29:31 +02:00
7f418c12a1 uniform idnames (#28)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #28
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 12:01:37 +02:00
f8fb273918 generated RBAC for coopshares and -assets (#27)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #27
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-04-02 11:04:56 +02:00
d3ca2b7e23 move Parter+Debitor person+contact to related Relationsship (#20)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #20
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-28 12:15:13 +01:00
4572c6bda0 improved RBAC generators (#26)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #26
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-26 11:25:18 +01:00
Marc O. Sandlus
cc7b8fcf9b some tests 2024-03-18 17:41:58 +01:00
Marc O. Sandlus
1a3fad80ee wip initial commit 2024-03-18 15:40:48 +01:00
67c1b50239 fix setting relation mark via API (#24)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #24
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-14 12:52:24 +01:00
3faf2ea99e rename partnerRole -> partnerRel, relationship -> relation and remove rel-Prefix (relAnchor etc.) (#23)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #23
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-13 15:01:24 +01:00
907e27ec19 import-debitor-relationship (into intermediate data structure) (#22)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #22
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-12 10:13:36 +01:00
187c0db8e2 RBAC Diagram+PostgreSQL Generator and view->SELECT etc. refactoring (#21)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #21
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-03-11 12:30:43 +01:00
d9558f2cfe add-trigger-object-to-rbacgrant (#18)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #18
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-02-24 09:04:07 +01:00
c7003148ae Merge pull request 'fix misleading findPermissionId naming' (#19) from fix-findPermissionId into master
Reviewed-on: #19
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-02-22 09:27:07 +01:00
Michael Hoennig
36654a69d8 fix misleading findPermissionId naming 2024-02-16 16:48:37 +01:00
496cdf295b fix import error for missing contractual contact and legacy-ids (#17)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #17
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-02-05 14:37:50 +01:00
2c0101b46d introduce-partner-business-role (#16)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #16
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-02-01 14:48:15 +01:00
188cb9733e add-subscriber-role (#14)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #14
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-26 09:30:31 +01:00
fb974a3b43 memberNumber as partnerNumber+memberNumberSuffix (#13)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #13
Reviewed-by: Michael Hierweck <michael.hierweck@hostsharing.net>
2024-01-24 15:57:16 +01:00
f150ea2091 more detailed person type (#12)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #12
Reviewed-by: Michael Hierweck <michael.hierweck@hostsharing.net>
2024-01-24 15:18:44 +01:00
fd1bd897b1 initial data import for hs-office tables (db-migration #10)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Reviewed-on: #10
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-23 15:11:23 +01:00
e427bb1784 Merge pull request 'fix+improve-project-setup' (#8) from fix+improve-project-setup into master
Reviewed-on: #8
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-09 09:01:12 +01:00
Timotheus Pokorra
238e413aa7 Merge branch 'master' into fix+improve-project-setup 2024-01-09 09:00:26 +01:00
5f9a6d53d8 Merge pull request 'Add information related to Podman storage to README.' (#3) from readme-podman-storage into master
Reviewed-on: #3
Reviewed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
2024-01-09 08:58:57 +01:00
Timotheus Pokorra
fef9fba073 Merge branch 'master' into readme-podman-storage 2024-01-09 08:58:00 +01:00
3d77dcc2ce Merge pull request 'Requirements aktualisiert' (#4) from TP-20240104-readme_requirements into master
Reviewed-on: #4
Reviewed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
2024-01-09 08:56:18 +01:00
Timotheus Pokorra
38e4b00107 Merge branch 'master' into TP-20240104-readme_requirements 2024-01-09 08:55:29 +01:00
Michael Hoennig
96ef490207 code cleanup, removing commented code 2024-01-06 18:23:13 +01:00
Michael Hoennig
0a996a9a8f remove SDKMAN from README, JDK is now downloaded by Gradle Toolchain 2024-01-06 18:12:25 +01:00
Michael Hoennig
242b6f88c9 make OWASP_API_KEY optionally 2024-01-06 18:11:24 +01:00
0f71c6a88d re-activate auto-download of JDK with Gradle Toolchain support (#7)
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #7
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-05 15:16:12 +01:00
5c651891c3 Merge pull request 'Version Upgrade to Spring Boot 3.1.7, JDK 21, Gradle 8.5, Postgres 15 etc.' (#5) from version-upgrade-spring-boot-3-2-1-etc into master
Reviewed-on: #5
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-05 13:40:49 +01:00
Michael Hoennig
727644736d Merge remote-tracking branch 'origin/master' into version-upgrade-spring-boot-3-2-1-etc 2024-01-05 13:25:50 +01:00
Michael Hoennig
1f49970e66 amendmends according to code review 2024-01-05 11:15:51 +01:00
e5e9f26856 Liquibase-Changesets für Legacy-ID Mapping of Office-Admininistration-Tables
Reviewed-on: #6
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-05 10:58:47 +01:00
Michael Hoennig
85abe5c3cb amendmends according to code review 2024-01-05 10:52:15 +01:00
Michael Hoennig
47338cead8 Liquibase-Changesets für Legacy-ID Mapping 2024-01-05 08:19:49 +01:00
63b02ff9cb Small fix in README 2024-01-04 22:46:40 +01:00
Michael Hoennig
07dbc45c80 Revert "upgrade to SpringBoot 3.2.1" because HibernateTypes are incompatible to inclided Hibernate version
This reverts commit b15464bed5.
2024-01-04 15:51:59 +01:00
Michael Hoennig
b15464bed5 upgrade to SpringBoot 3.2.1 2024-01-04 14:45:40 +01:00
Michael Hoennig
650ae365e5 Merge remote-tracking branch 'origin/master' into version-upgrade-spring-boot-3-2-1-etc
# Conflicts:
#	build.gradle
2024-01-04 14:07:19 +01:00
Michael Hoennig
d97827c01e add IDEA Plugin Suggestions and re-generated index 2024-01-04 14:06:11 +01:00
Michael Hoennig
e9dde69c40 improve Markdown/PlantUML/Mermaid documentation 2024-01-04 13:24:04 +01:00
Michael Hoennig
7f98bbde64 fix pitest, spotless and cleanup 2024-01-04 12:39:39 +01:00
Michael Hoennig
ea130581a3 unit test and improve PostgresArray 2024-01-04 12:38:40 +01:00
Michael Hoennig
f8de575b77 cleanup 2024-01-04 10:28:53 +01:00
Michael Hoennig
d2f9f0ae8f fix invalid assertion 2024-01-04 10:28:53 +01:00
Michael Hoennig
4c44f42b79 fix vulnerability CVE-2022-1471 by forcing snakeyaml 2.2 2024-01-04 10:28:53 +01:00
Michael Hoennig
73f147c557 fix most unit tests and improve usage of assumeThat 2024-01-04 10:28:53 +01:00
Michael Hoennig
378e1ec584 fix Postgres typing error by casting 2nd operator of like operator to text 2024-01-04 10:28:53 +01:00
Michael Hoennig
ec53934f30 fix problem with Postgres function array return value in Hibernate 6 2024-01-04 10:28:52 +01:00
Michael Hoennig
063fcf90a3 SpringBoot, Gradle, Java etc. version upgrades and add OWASP API key via gradle.properties 2024-01-04 09:58:29 +01:00
845857c14d Requirements aktualisiert 2024-01-04 09:13:27 +01:00
994a0e13c0 improve setup instructions and fix Swagger UI (#2)
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@solidcharity.com>
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #2
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-02 15:27:15 +01:00
Michael Hoennig
51aebc65b2 version upgrade to PostgreSQL Server 15.5-bookworm 2024-01-02 13:28:56 +01:00
Michael Hoennig
287c1ad9dc minor improvement in setup instructions 2024-01-02 13:08:55 +01:00
Michael Hoennig
53ffe9e738 fix SwaggerUI issue with duplicate method name 2024-01-02 13:03:15 +01:00
Michael Hierweck
cec31055a5 Add information related to Podman storage to README. 2024-01-02 12:20:19 +01:00
Michael Hoennig
f1e977c905 add documentation for re-running tests from command line 2024-01-02 11:21:22 +01:00
Timotheus Pokorra
95457980d8 use alias gw 2024-01-02 10:35:47 +01:00
Timotheus Pokorra
70d73d8caa added gradlew again 2024-01-02 10:30:52 +01:00
Timotheus Pokorra
bdac905958 adjust README 2024-01-02 09:51:08 +01:00
Timotheus Pokorra
1c19afefa6 avoid error in openapi for ambigous mapping for findRelationshipRelatedToPersonUuid 2023-09-04 20:37:40 +02:00
Timotheus Pokorra
0edc2cca91 add dependancy for swagger-ui / springdoc-openapi
according to
https://www.baeldung.com/spring-rest-openapi-documentation and
https://central.sonatype.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.2.0
2023-09-04 20:23:57 +02:00
Timotheus Pokorra
51c658cdc7 no need to install gradle anymore 2023-07-19 14:19:54 +02:00
Timotheus Pokorra
796d1a0991 README: use gradlew instead of gradle wrapper
add instructions for starting the postgresql docker container if the
container has been built already
2023-07-19 09:43:06 +02:00
Timotheus Pokorra
5986ca26fe README: add instructions to install jdk 17 and gradle 7.6.2 on Ubuntu 22.02 2023-07-18 10:58:57 +02:00
Michael Hoennig
db17a2e990 update to SpringBoot 3.0.0 final 2022-11-25 06:51:53 +01:00
Michael Hoennig
ddfabef9b7 hs_office_sepamandate_rv: fix missing update column reference 2022-10-29 11:42:03 +02:00
Michael Hoennig
6df93bfbb6 leftover from SpringBoot 3 upgrade: cleanup in gradle.properties 2022-10-28 17:12:07 +02:00
Michael Hoennig
b69af8fa57 remove spurious grant from debitor RBAC diagram 2022-10-28 17:11:12 +02:00
Michael Hoennig
cdc21f05ba SpringBoot 3 upgrade 2022-10-28 15:57:06 +02:00
Michael Hoennig
64461fc4da SEPA-Mandate signed date and patcher 2022-10-28 13:44:48 +02:00
Michael Hoennig
3b340a92ed final ContextUnitTest.defineContextQueryString 2022-10-27 18:24:28 +02:00
Michael Hoennig
909312a8a1 test class naming convention 2022-10-27 11:45:53 +02:00
Michael Hoennig
dc1a17e915 use @PersistenceContext for EntityManager, not @Autowired 2022-10-27 11:00:12 +02:00
Michael Hoennig
1d0d2372ea use simple @GeneratedValue and some code cleanup 2022-10-27 09:42:18 +02:00
Michael Hoennig
09c19649e4 avoid select before insert and map sub-entities in mapper 2022-10-26 16:44:51 +02:00
Michael Hoennig
d7eed08420 always run tests with locale en_US, no matter of system settings 2022-10-25 13:08:17 +02:00
Michael Hoennig
e5ec867819 get endpoints for coopassets+coopshares 2022-10-25 10:32:57 +02:00
Michael Hoennig
6f3c03e6b6 ModelMapper as Spring Bean 2022-10-23 13:00:00 +02:00
Michael Hoennig
0b0f57c176 map HsOfficeMembershipInsertResource via ModelMapper and Entity.setValidTo/From 2022-10-22 12:38:36 +02:00
Michael Hoennig
b3b70aaaf4 add HsOfficeMembershipEntityPatcher 2022-10-22 10:40:59 +02:00
Michael Hoennig
8eab3b7cd5 add HsOfficeMembershipControllerRestTest and make mainDebitorUuid mandatory 2022-10-21 18:43:15 +02:00
Michael Hoennig
7d200efbb5 TODO cleanup in HsOfficeContactRepository 2022-10-21 17:31:49 +02:00
Michael Hoennig
97fa23165c simplified deletion of temp data in HsOfficeBankAccountControllerAcceptanceTest 2022-10-21 17:30:28 +02:00
Michael Hoennig
a39cf73cf0 hs-office-coopassets, no get API endpoints yet 2022-10-21 17:12:55 +02:00
Michael Hoennig
5ada0dae35 fixes instable debitor temp test data deletion and code coverage exclusions 2022-10-21 17:05:28 +02:00
Michael Hoennig
8864a17b2b hs-office-debitor: test update of partner and bankaccount and fix null cases 2022-10-21 11:38:31 +02:00
Michael Hoennig
5a296e0a9f improved ArchitectureTest rules and package refactoring 2022-10-21 08:01:17 +02:00
Michael Hoennig
810c3923d2 version upgrades, but skipping -RC versions 2022-10-21 07:00:57 +02:00
Michael Hoennig
26efbd5652 remove the outdated TODO list 2022-10-21 06:33:51 +02:00
Michael Hoennig
e1895e3735 hs-office-partner-details 2022-10-20 20:11:31 +02:00
Michael Hoennig
dc0835fa25 hs-office-coopshares: add non-negative validation 2022-10-20 14:08:19 +02:00
Michael Hoennig
22bca20c49 code cleanup: remove unnecessary "raise notice" 2022-10-20 10:53:47 +02:00
Michael Hoennig
e2b6986d2b hs-office-coopshares: refactored RestTest 2022-10-20 08:15:47 +02:00
Michael Hoennig
5f4f50a325 hs-office-coopshares: add validation + RestTest for Controller + improve test data 2022-10-20 07:41:48 +02:00
Michael Hoennig
ca0589d084 hs-office-coopshares: use only membership uuid in filter 2022-10-19 18:15:21 +02:00
Michael Hoennig
5764accbc5 add hs-office-coopshares API+controller 2022-10-19 17:14:27 +02:00
Michael Hoennig
c2dd3d8de9 add hs-office-coopshares entity+repository 2022-10-19 07:55:51 +02:00
Michael Hoennig
7376301ed4 use lower case in audit-log and rbac tables 2022-10-19 07:39:10 +02:00
Michael Hoennig
ea2d2c9100 add TestHsMembership instance 2022-10-18 18:53:18 +02:00
Michael Hoennig
cb47d526ac add hs-office-coopshares rbac + test-data 2022-10-18 17:29:36 +02:00
Michael Hoennig
bec559c9c3 generateRbacRestrictedView for non-updateable tables 2022-10-18 17:29:10 +02:00
Michael Hoennig
61473abf68 add hs_office_coopsharestransaction table SQL 2022-10-18 14:44:10 +02:00
Michael Hoennig
c862df7846 add hs-office-membership API and controller 2022-10-18 13:57:35 +02:00
Michael Hoennig
27f29ef665 hs-office-sepamandate simplyfied controller code 2022-10-18 12:30:26 +02:00
Michael Hoennig
0232ff078d just a single find method in hs-office-membership repo 2022-10-18 09:14:35 +02:00
Michael Hoennig
e6f9484f99 add hs-office-membership entity+repo + fix rbac 2022-10-17 19:42:14 +02:00
Michael Hoennig
28bdd9220d add hs-office-membership test-data 2022-10-17 13:23:10 +02:00
Michael Hoennig
63db939583 hoist select queryAccessibleObjectUuidsOfSubjectIds(...) into WITH CTE for better performance 2022-10-17 12:18:12 +02:00
Michael Hoennig
368da1cc95 add hs-office-membership RBAC rules (yet without mainDebitor update) 2022-10-17 08:14:09 +02:00
Michael Hoennig
080f9279b3 code cleanup 2022-10-17 08:11:51 +02:00
Michael Hoennig
221889e6ca add hs-office-membership table 2022-10-17 07:53:25 +02:00
Michael Hoennig
7663825a30 add postgresql_autodoc 2022-10-16 18:50:32 +02:00
Michael Hoennig
77ef126a7e improved REST-API error handling for broken request body JSON 2022-10-16 10:08:42 +02:00
Michael Hoennig
e305c3c935 add SEPA-mandate API+Controller 2022-10-15 15:32:32 +02:00
Michael Hoennig
67e850f9b2 add API validation 2022-10-15 11:29:56 +02:00
Michael Hoennig
4f22dffe5d use "YYYY-MM-DD" format when writing LocalDate to JSON 2022-10-14 12:37:59 +02:00
Michael Hoennig
c6d92e271f improved SQL query logging via net.ttddyy.dsproxy.listener for test 2022-10-14 08:33:59 +02:00
Michael Hoennig
d666e22297 add SEPA-Mandate entity+repository 2022-10-13 18:54:56 +02:00
Michael Hoennig
bece972a4e add SEPA-Mandate entity+repository 2022-10-13 18:53:38 +02:00
Michael Hoennig
7f5b2358d3 add SEPA-Mandate SQL changesets 2022-10-13 13:35:50 +02:00
Michael Hoennig
560cd9cf9f audit log with context per task instead of just transaction 2022-10-13 10:36:20 +02:00
Michael Hoennig
2506acc531 add audit journal log for hs_office_debitor 2022-10-13 09:02:09 +02:00
Michael Hoennig
64fbabf606 add audit journal log for hs_office_relationship 2022-10-13 08:47:17 +02:00
Michael Hoennig
d77d853c80 add audit journal log for hs_office_partner 2022-10-13 08:34:04 +02:00
Michael Hoennig
472734a516 add audit journal log for hs_office_person 2022-10-13 08:23:08 +02:00
Michael Hoennig
df493e40d7 add audit journal log for hs_office_bankaccount 2022-10-13 08:03:48 +02:00
Michael Hoennig
a56af5af25 add audit journal log for hs_office_contact 2022-10-13 07:57:19 +02:00
Michael Hoennig
0b60b9f0ff introduces agent+guest role for role-system around debitor+partner 2022-10-12 15:48:56 +02:00
Michael Hoennig
de0c8dcfbc Array.fromFormatted 2022-10-10 09:33:17 +02:00
Michael Hoennig
639925e834 fix debitor update trigger 2022-10-06 11:12:18 +02:00
Michael Hoennig
6b6f8127bb add refundBankAccount to hs_office_debitor 2022-10-05 17:22:33 +02:00
Michael Hoennig
d98b8feaad add IBAN+BIC validation 2022-10-05 08:04:44 +02:00
Michael Hoennig
398f15d5de dependency upgrades and suppress irrelevant security vulnerability in jackson-databind 2022-10-05 06:31:53 +02:00
Michael Hoennig
a93143ff00 add HsOfficeBankAccount* 2022-10-04 19:09:37 +02:00
Michael Hoennig
c3195662dd adds HsOfficePartner 2022-10-03 11:09:36 +02:00
Michael Hoennig
d3312c4444 improved Stringify 2022-09-30 16:27:18 +02:00
Michael Hoennig
31854bb838 ArchTest->Architecture test for shorter @ArchTest annotations and disallow JUnit 4 classes 2022-09-30 14:06:06 +02:00
Michael Hoennig
16d1372d41 shorter person names to make tests easier to read 2022-09-30 13:11:34 +02:00
Michael Hoennig
956ee581c6 implements table hs_office_relationship to HsOfficeRelationshipController 2022-09-30 09:44:24 +02:00
Michael Hoennig
49ba141ca5 Merge branch 'patch-with-getReference' 2022-09-26 13:32:54 +02:00
Marc O. Sandlus
89415067ef patch-with-getReference, no need for fetch anymore 2022-09-26 13:32:30 +02:00
Michael Hoennig
21a4519743 code cleanup in partner sql files 2022-09-26 09:26:22 +02:00
Michael Hoennig
d13f0cbcdf refactor HsOfficeContactEntityPatcherUnitTest to PatchUnitTestBase 2022-09-25 11:37:46 +02:00
Michael Hoennig
b431a6653d refactor HsOfficePersonEntityPatchUnitTest to PatchUnitTestBase 2022-09-25 11:24:29 +02:00
Michael Hoennig
3e6da45302 refactor HsOfficePartnerEntityPatchUnitTest to PatchUnitTestBase 2022-09-24 12:43:42 +02:00
Michael Hoennig
d08e60f8dc re-generated gradle wrapper 2022-09-22 12:55:41 +02:00
Michael Hoennig
09be849b09 remove .gradle from git 2022-09-22 12:42:14 +02:00
Michael Hoennig
082bdd5527 How to Configure .pgpass for the Default PostgreSQL Database? 2022-09-22 12:33:54 +02:00
Michael Hoennig
08f51a196e proper .gitignore for gradlew in git 2022-09-21 14:31:59 +02:00
Michael Hoennig
0feea6b2d6 add gradlew to git 2022-09-21 10:37:39 +02:00
Michael Hoennig
6ec4c86ad6 increase test code coverage thresholds 2022-09-21 09:44:25 +02:00
Michael Hoennig
00174e4c4a implements HsOfficePersonController 2022-09-21 09:44:09 +02:00
Michael Hoennig
0bab27d723 add missing HsOfficeContactEntityPatchUnitTest 2022-09-20 16:32:56 +02:00
Michael Hoennig
863f0e2811 implements HsOfficeContactController 2022-09-20 14:17:12 +02:00
Michael Hoennig
44eb59c918 introduces generateRbacRestrictedView to generate restricted view + triggers 2022-09-19 20:43:14 +02:00
Michael Hoennig
2cae17a045 introduces generateRbacIdentityView to generate identity views 2022-09-16 16:14:39 +02:00
Michael Hoennig
d63e3f31e9 introduce 058-rbac-generators.sql with generateRelatedRbacObject+generateRbacRoleDescriptors 2022-09-16 15:25:58 +02:00
Michael Hoennig
1dd63161ab properly implement update for hs_office_partner_rv 2022-09-15 13:32:01 +02:00
Michael Hoennig
430f75ea15 add aliases podman-start und podman-use 2022-09-15 11:19:04 +02:00
Michael Hoennig
01d9cbd711 upgrade snakeyaml BOM override - but it still has a vulnerability, not relevant for us, though 2022-09-14 17:52:56 +02:00
Michael Hoennig
bad7d146fa implements HsOfficePartnerController.patch 2022-09-14 17:51:04 +02:00
Michael Hoennig
68c3375a08 implements HsOfficePartnerController.deletePartnerByUuid 2022-09-14 13:47:25 +02:00
Michael Hoennig
7d8d6bb495 improved documentation for using Podman with IntelliJ IDEA 2022-09-14 13:02:57 +02:00
Michael Hoennig
e51485eb63 improved documentation for using Podman 2022-09-14 12:28:18 +02:00
Michael Hoennig
9cd4525e2b create partner now taking existing contact+person uuids instead of complete (new) objects 2022-09-14 12:16:44 +02:00
Michael Hoennig
3fa02d4a10 prefix alex+fran with superuser- to make tests easier to understand 2022-09-14 09:56:22 +02:00
Michael Hoennig
a3d2dd3db1 HsOfficePartnerControllerAcceptanceTest against real repo+db 2022-09-14 09:24:19 +02:00
Michael Hoennig
bc27e6dc89 refactor name extractors into entity classes 2022-09-13 15:00:10 +02:00
Michael Hoennig
8041553734 always use role+grant names in create+delete role+grants tests 2022-09-13 14:17:30 +02:00
Michael Hoennig
4e90f53bf3 rename hs-admin to hs-office regarding the module name 2022-09-13 13:27:52 +02:00
Michael Hoennig
7465b9df63 cleanup old domain emailaddess and test sql scripts 2022-09-13 12:37:10 +02:00
Michael Hoennig
0b48e8d1b7 consolidating role deletion from business objects to rbac system 2022-09-13 12:35:46 +02:00
Michael Hoennig
ac5f19e399 improve Contact/Partner/Contact Repository integration tests with Raw...Repos 2022-09-12 16:27:17 +02:00
Michael Hoennig
3eec8a4138 improved rbacroles_ev view and raw access for testing purposes 2022-09-10 14:49:01 +02:00
Michael Hoennig
2c5ad094f1 add partner business object at repo level (WIP) 2022-09-09 17:43:43 +02:00
Michael Hoennig
2f0f18182c add partner business object at db level 2022-09-09 10:40:05 +02:00
Michael Hoennig
35efa40ebb fix role generation for contact+person 2022-09-09 09:58:36 +02:00
Michael Hoennig
0fe1f85549 version upgrade com.vladmihalcea:hibernate-types-55:2.19.0 2022-09-09 09:57:03 +02:00
Michael Hoennig
cacfbabcfb documentation to run tests against local or remote Podman daemon 2022-09-08 16:15:59 +02:00
Michael Hoennig
765d3c689e test role-creation, grant+revoke for contact+person 2022-09-08 11:21:54 +02:00
Michael Hoennig
ec00e445a0 improve code + mutation coverage in HsAdminPersonEntity 2022-09-08 09:47:19 +02:00
Michael Hoennig
37e7b5179d add person business object at db level 2022-09-07 20:24:35 +02:00
Michael Hoennig
9720b37d85 prepare hs-admin-customer files to be used as template + generate script v1 2022-09-07 14:09:33 +02:00
Michael Hoennig
23796c56f9 cleanup+categorization of TODOs 2022-09-07 12:25:12 +02:00
Michael Hoennig
756d5e1ae6 implements delete for hs_admin_contact 2022-09-07 10:32:49 +02:00
Michael Hoennig
18a3718c75 better error message in assumeSuccessful+assertSuccessful 2022-09-07 10:24:51 +02:00
Michael Hoennig
2afdb3c3d7 add hs_admin_contact table and repository with findContactByOptionalLabelLike+save 2022-09-06 19:43:15 +02:00
Michael Hoennig
da793ee546 refactor to only a single global admin object global#global.admin 2022-09-06 13:48:30 +02:00
Michael Hoennig
87e2b05926 fix gw alias to preserve colors and fix test strength threshold 2022-09-06 13:45:33 +02:00
Michael Hoennig
4ec26108fa makes HTML reports from failed gradle tasks directly accessible via gw alias + grep 2022-09-06 11:07:49 +02:00
Michael Hoennig
af90fefd49 hs.admin.partner from API via Controller to Entity 2022-09-06 11:07:08 +02:00
Michael Hoennig
2abe88eb15 experimental access code for RbacUserEntity 2022-09-03 12:31:56 +02:00
Michael Hoennig
1451e7b661 add missing linked yaml files to test api-definition 2022-09-02 13:56:21 +02:00
Michael Hoennig
cd9be1db75 api-definition subdirectories rbac+test 2022-09-02 13:11:15 +02:00
Michael Hoennig
3541b0c48c use /api/rbac/... instead of /api/rbac-... same for /api/test/... 2022-09-02 11:18:09 +02:00
Michael Hoennig
fd96bfffb2 fix security vulnerability in snakeyaml 2022-09-02 08:58:15 +02:00
Michael Hoennig
df48bfc0da implements delete rbacuser 2022-09-01 16:57:16 +02:00
Michael Hoennig
a06feff42e defineContext now requires an existing user name or null to create a new user 2022-08-31 16:26:31 +02:00
Michael Hoennig
8731f4a7b2 migrate hs-api to test-api 2022-08-31 15:44:06 +02:00
Michael Hoennig
a117258085 replace unixuser test entities with domain 2022-08-31 15:07:25 +02:00
Michael Hoennig
84ce90b28f use customer/package/unixuser only as test data structure (Java part) 2022-08-31 09:47:35 +02:00
Michael Hoennig
a33cb4ec29 use customer/package/unixuser only as test data structure (DB part) 2022-08-31 09:42:40 +02:00
Michael Hoennig
817c1a9e58 split off rbac-global from hs-base and allow multiple global objects 2022-08-30 14:21:24 +02:00
Michael Hoennig
7f6e363c8f store http request in audit log 2022-08-30 13:38:12 +02:00
Michael Hoennig
8af93603d5 improve test code coverage and generic array return from native queries 2022-08-30 13:08:29 +02:00
Michael Hoennig
a1c3e95032 introduce defineContext replacing explicit "set local current..." 2022-08-30 09:35:59 +02:00
Michael Hoennig
8045b66324 rename currentUserId->currentUserUuid + currentSubjectIds->currentSubjectsUuids 2022-08-30 09:18:52 +02:00
Michael Hoennig
1c45443da6 add enhanced views for users, roles and grants 2022-08-30 08:58:33 +02:00
Michael Hoennig
4aa8b85bb6 use context.define(...) instead of setCurrent... 2022-08-28 20:39:44 +02:00
Michael Hoennig
7d4815e2cf documented pitest mutation testing 2022-08-26 16:54:46 +02:00
Michael Hoennig
9fb6610ec8 configured pitest mutation testing 2022-08-26 16:06:27 +02:00
Michael Hoennig
2124d448bf improve test code coverage and introduce context.register 2022-08-26 14:13:02 +02:00
Michael Hoennig
1a18ba4a3d improve test code coverage 2022-08-25 20:35:31 +02:00
Michael Hoennig
2531b9071f align create methods and improve and align acceptance tests 2022-08-25 14:46:05 +02:00
Michael Hoennig
9a7b683e7d also use new xxx/yyy/zzz example customers in README 2022-08-25 11:06:24 +02:00
Michael Hoennig
68dfadf0e2 set currentTask in addCustomer 2022-08-24 18:48:37 +02:00
Michael Hoennig
595c11c7ac org.gradle.jvmargs not necessary anymore after version upgrades 2022-08-24 18:47:53 +02:00
Michael Hoennig
8b4e7869ad add Dependency Version Upgrade and upgrade versions 2022-08-24 18:27:40 +02:00
Michael Hoennig
6b4c9f6c51 use xxx, yyy and zzz for test customers, makes tests easier to read 2022-08-24 17:56:13 +02:00
Michael Hoennig
258f8b1f66 split tx_audit_log into tx_context and tx_journal 2022-08-24 12:52:28 +02:00
Michael Hoennig
e880a1c2c8 initial audit-log implementation 2022-08-24 12:01:54 +02:00
Michael Hoennig
cb641eb8c6 introduce currentTask and ContextBasedTest 2022-08-24 11:48:31 +02:00
Michael Hoennig
81cfbc62e4 re-numbering rbac db-migration scripts while extracting context (currentUser etc.) 2022-08-24 11:10:34 +02:00
Michael Hoennig
661f23e1e2 archrule to disallow JUnit 4, dependency exclude not possible as it's still needed for testcontainers 2022-08-23 14:31:54 +02:00
Michael Hoennig
3de8b00672 exclude indirect JUnit 4 compile-time dependency 2022-08-23 14:10:50 +02:00
Michael Hoennig
a7f669a1f9 removes logging from jsonb-changes-delta 2022-08-23 10:18:14 +02:00
Michael Hoennig
d0b201aff9 implements jsonb-changes-delta 2022-08-23 10:17:32 +02:00
Michael Hoennig
2022492f21 implements jsonb-changes-delta 2022-08-23 09:36:39 +02:00
Michael Hoennig
dd4f39902f cleaning up database migration files, removing date prefix, renumbering etc. 2022-08-23 09:25:10 +02:00
Michael Hoennig
a04929453c implements optimistic locking for PackageEntity 2022-08-20 12:29:14 +02:00
Michael Hoennig
5ea8069608 implements rbac-grants get-by-id 2022-08-19 17:39:41 +02:00
Michael Hoennig
a66ed8e59f add jacoco test code coverage 2022-08-19 12:00:13 +02:00
Michael Hoennig
86802a2aab use Spring Transactional annotation instead of JavaX 2022-08-19 08:26:30 +02:00
Michael Hoennig
eebe2d40a6 refactor RbacGrantControllerAcceptanceTest introducing fixture classes for better readability 2022-08-18 13:47:22 +02:00
Michael Hoennig
8a62d9802e implements REST API DELETE to /api/rbac-grants/{grantedRoleUuid}/{granteeUserUuid}: 2022-08-17 17:34:10 +02:00
Michael Hoennig
787400c089 only use @Transactional from Spring (not JavaX) and use readOnly=true where possible 2022-08-17 16:38:03 +02:00
Michael Hoennig
6be5cfd923 arch unit test for @Accepts only in AcceptanceTests 2022-08-17 13:21:05 +02:00
Michael Hoennig
2cb9375d03 implements revoking role from user at repository level 2022-08-16 17:51:51 +02:00
Michael Hoennig
c8e835f880 implements user granting roles to other users 2022-08-16 10:46:41 +02:00
Michael Hoennig
7869d07d30 add unixuser prototype as preparation for testability of grants 2022-08-14 16:44:26 +02:00
Michael Hoennig
bc05fb1eeb improve + cleanup RbacRoleControllerAcceptanceTests 2022-08-13 17:24:56 +02:00
Michael Hoennig
322736cd01 creating and viewing grants 2022-08-13 16:47:36 +02:00
Michael Hoennig
c03697ccd9 POST to /api/customers response 200 -> 201 2022-08-13 14:58:19 +02:00
Michael Hoennig
8529ec9949 findByRoleName 2022-08-12 18:00:15 +02:00
Michael Hoennig
41d3b678c4 implements create new rbac-user and transacted JpaAttemp 2022-08-12 17:56:39 +02:00
Michael Hoennig
dfc7162675 fix references in api-definition 2022-08-11 12:43:29 +02:00
Michael Hoennig
1dd3b3e8e3 directly use repository in test fixture of PackageControllerAcceptanceTest 2022-08-11 12:42:41 +02:00
Michael Hoennig
4616bb133d avoid string concatenation in RestAssured API usage 2022-08-11 12:36:30 +02:00
Michael Hoennig
9aa86a1652 fix vulnerability in postgresql jdbc driver 41.3.6 and JDK 16 incompatibility 2022-08-11 12:35:19 +02:00
Michael Hoennig
5d63544a15 cleanup 2022-08-11 09:31:24 +02:00
Michael Hoennig
cb84efe63d split api-definition.yaml 2022-08-11 09:29:15 +02:00
Michael Hoennig
5ebed0f75d extract OptionalFromJson for JSON mapper bug 2022-08-09 18:30:06 +02:00
Michael Hoennig
0e4602aac6 add updatePackage (description) using JsonNullableModule and HTTP-to-DB test with RestAssured 2022-08-09 17:51:50 +02:00
Michael Hoennig
0486dc3fae documentation of directory structure 2022-08-08 17:57:02 +02:00
Michael Hoennig
c4cf6c4312 progress status as of 2022-08-07 2022-08-08 17:34:12 +02:00
Michael Hoennig
eeab68d63a API-first with openapiprocessor and modelmapper 2022-08-08 17:12:51 +02:00
Michael Hoennig
80f342eeae add more allowed licenses 2022-08-08 12:12:02 +02:00
Michael Hoennig
c4531cb217 add dependency license compatibility check 2022-08-08 10:03:26 +02:00
Michael Hoennig
86128f5994 test alignment and code cleanup 2022-08-05 15:39:23 +02:00
Michael Hoennig
f16953877f add RbacUser* tests and improved http status codes 2022-08-05 15:00:29 +02:00
Michael Hoennig
bef358eda6 add RbacUser* tests and improved http status codes 2022-08-05 14:31:54 +02:00
Michael Hoennig
f2bc42bd85 replace GRP with PRM in requirements 2022-08-05 13:11:01 +02:00
Michael Hoennig
433d0e19f5 add Gradle Java toolchain and improve documentation 2022-08-05 13:10:11 +02:00
Michael Hoennig
06996e4dc4 add RbacUserController/-Entity/-Repository 2022-08-04 17:23:10 +02:00
Michael Hoennig
18f3234272 clickable link to markdown plugin settings for IntelliJ IDEA 2022-08-04 14:56:19 +02:00
Michael Hoennig
e2bdb96e83 README: How to Run the Appplication on a Different Port 2022-08-04 13:07:16 +02:00
Michael Hoennig
58200eee1f cleanup IntelliJ DB run scripts, not needed anymore due to Liquibase 2022-08-04 12:34:51 +02:00
Michael Hoennig
507bee0953 fix gradle .gitignore 2022-08-04 12:30:14 +02:00
Michael Hoennig
80b1e0ff25 add OWASP dependencyCheck 2022-08-04 12:26:41 +02:00
Michael Hoennig
d9ee0017dd set gradle wrapper version+distribution 2022-08-04 12:25:42 +02:00
Michael Hoennig
60f2af6404 remove gradle wrapper from JHipster + configure version 7.5 for "gradle wrapper" 2022-08-04 11:15:08 +02:00
Michael Hoennig
9945eddf7a Merge remote-tracking branch 'origin/spike/ATN+HST'
# Conflicts:
#	.gitignore
#	TODO.md
#	tools/todo-progress
#	tools/todo-progress.gnuplot
2022-08-04 11:08:26 +02:00
Michael Hoennig
45c1bed43b add TODO document + progress calculation 2022-08-04 11:06:07 +02:00
Michael Hoennig
bf3da34d32 add adr for row-level-security 2022-08-04 10:51:21 +02:00
Michael Hoennig
2eb8b70517 add glossary 2022-08-04 10:49:31 +02:00
Michael Hoennig
a2f2fdb6a4 add spotless 2022-08-04 10:11:35 +02:00
Michael Hoennig
8605d4c4f1 PackageController+Repository with name search option 2022-08-04 09:26:01 +02:00
Michael Hoennig
212b1077c8 adds RbacRoleControllerRestTest + RbacRoleRepositoryIntegrationTest + fix duplicate key 2022-08-04 09:11:11 +02:00
Michael Hoennig
57cf316c00 for customer findAll()->findCustomerByOptionalPrefix(...) 2022-08-04 09:09:06 +02:00
Michael Hoennig
8fb92f9978 remove hateoas and hal explorer for now 2022-08-03 09:07:02 +02:00
Michael Hoennig
5f08b41724 add SwaggerUI 2022-08-03 09:06:21 +02:00
Michael Hoennig
dc6445544a add RbacRoleEntity.roleName as computed column 2022-08-03 08:52:45 +02:00
Michael Hoennig
ece36209a5 add RbacRole... 2022-08-03 06:12:16 +02:00
Michael Hoennig
bb05eb4ac4 refactoring packages, moving Hostsharing related entities etc. to hs package 2022-08-03 06:11:39 +02:00
Michael Hoennig
142aac2bce add ArchUnitTest 2022-08-02 15:51:06 +02:00
Michael Hoennig
2ac476d99b add-customer and introducing JpaAttempt test helper 2022-08-02 14:31:48 +02:00
Michael Hoennig
03ee2cfd62 add-customer and introducing JpaAttempt test helper 2022-08-02 11:51:36 +02:00
Michael Hoennig
f58a68d1cc experimental CustomerRepositoryIntegrationTest with Given/When/Then classes 2022-08-01 12:53:09 +02:00
Michael Hoennig
27c5699c36 add CustomerRepositoryIntegrationTest, fix testcontainers liquibase context and improve error messages 2022-07-31 18:56:03 +02:00
Michael Hoennig
5d4fb85383 TODO tracking 2022-07-31 15:16:49 +02:00
Michael Hoennig
53d3d68021 introduce TestPackage.java 2022-07-30 18:37:32 +02:00
Michael Hoennig
1463807b89 initial test-concept documentation 2022-07-30 18:23:18 +02:00
Michael Hoennig
08804bd45c use plural for /api/packages too 2022-07-30 18:23:02 +02:00
Michael Hoennig
fe4fef2752 add CustomerControllerRestTest and -UnitTest 2022-07-30 18:17:45 +02:00
Michael Hoennig
2610ecd311 add PackageControllerTest as WebMvcTest through the REST API 2022-07-30 16:35:26 +02:00
hsh-michaelhoennig
5b8688cd63 amended README.md for MacOS 2022-07-30 14:41:11 +02:00
Michael Hoennig
b20920d646 make package owner/admin/tenant roles assumable 2022-07-29 16:25:46 +02:00
Michael Hoennig
4721d1be23 PackagerEntity, -Repository and -Controller 2022-07-29 16:13:38 +02:00
Michael Hoennig
46957dc590 bugfix in pureIdentifier + findUuidByIdName and CustomerEntity+Repository 2022-07-29 15:53:20 +02:00
Michael Hoennig
5126514877 improved README.md with TOC 2022-07-29 15:39:35 +02:00
Michael Hoennig
414149f0a1 introduce Context service 2022-07-29 14:24:50 +02:00
Michael Hoennig
2b630aadbc hs-package SQL-Scripts in Liquibase and some bugfixes 2022-07-29 12:37:40 +02:00
Michael Hoennig
61c50c46ed hs-customer SQL-Scripts in Liquibase 2022-07-29 12:14:22 +02:00
Michael Hoennig
8ba952a41d hs-customer SQL-Scripts in Liquibase 2022-07-29 11:53:58 +02:00
Michael Hoennig
a478fe4cf1 cleanup in Liquibase files, header, formatting etc. 2022-07-29 11:38:51 +02:00
Michael Hoennig
4edff1c2f0 move rbac.md to doc folder 2022-07-29 09:20:18 +02:00
Michael Hoennig
9beaf5e684 improved README.md for first steps 2022-07-29 09:12:52 +02:00
Michael Hoennig
4c403b0436 formatted SQL code 2022-07-29 08:46:04 +02:00
Michael Hoennig
fb8862c37e convert rbac-statistics and rbac-role-builder*.sql files to Liquibase changesets 2022-07-28 17:17:22 +02:00
Michael Hoennig
583c45c85d add first integration test based on Testcontainers 2022-07-28 16:56:34 +02:00
Michael Hoennig
d234ac3227 convert rbac*.sql files, except test-file, to Liquibase changesets 2022-07-28 16:55:21 +02:00
Michael Hoennig
6c33bbe780 add SpringBoot 2.7.x application 2022-07-28 16:51:36 +02:00
Michael Hoennig
feff1b5794 RBAC documentation improved, but still WIP 2022-07-28 12:15:32 +02:00
Michael Hoennig
306f8d1fa8 improved README.md with aliases for pg-sql-... 2022-07-28 10:43:49 +02:00
Michael Hoennig
457641a2dd introduce referential integrity for role identification - part 2 assume 2022-07-28 10:43:23 +02:00
Michael Hoennig
1dde6b2609 introduce referential integrity for role identification - part 1 2022-07-27 19:54:05 +02:00
Michael Hoennig
6f6320565c the _rv query with WHERE IN was faster after all, removing the JOIN variant 2022-07-27 13:05:19 +02:00
Michael Hoennig
bafae52ce5 _rv query performance experiments 2022-07-27 12:32:54 +02:00
Michael Hoennig
e97022fb02 add documentation to backup+restore the database from PostgreSQL docker container 2022-07-26 13:19:17 +02:00
Michael Hoennig
190d39400a add users to customer+package role diagrams 2022-07-26 13:18:36 +02:00
Michael Hoennig
46c5f5e53e final performance tests as do block to measure total time 2022-07-26 13:18:03 +02:00
Michael Hoennig
04a3438182 improve documentation 2022-07-25 20:11:39 +02:00
Michael Hoennig
4814e7899c better layout for RBAC entity diagram 2022-07-25 17:15:01 +02:00
Michael Hoennig
16513f0786 remove generated pdf 2022-07-25 17:06:42 +02:00
Michael Hoennig
d4eeb35e91 improved role structure including comprised tenant sub roles 2022-07-25 16:38:21 +02:00
Michael Hoennig
377b63ca3d RbacGrants with follow=false for customer.owner to customer.admin 2022-07-22 16:52:49 +02:00
Michael Hoennig
f2d0fbe67a removing JHipster 2022-07-22 13:31:37 +02:00
Michael Hoennig
31cd92f3be #145 [Rights-Module] preparation for module specific roles / fix UserRoleAssignmentCriteria 2019-07-03 22:23:51 +02:00
Michael Hoennig
f79c4bd7a1 #145 [Rights-Module] preparation for module specific roles / static init 2019-07-03 21:54:03 +02:00
Michael Hoennig
7db2c23de1 #145 [Rights-Module] preparation for module specific roles 2019-06-27 23:48:16 +02:00
Michael Hoennig
7983aa7e52 #145 [Rights-Module] documented the role system and some renaming 2019-05-25 19:43:29 +02:00
Michael Hoennig
57b6399950 historicization of JHI_USER_AUTHORITY table 2019-05-25 16:40:41 +02:00
Michael Hoennig
354cfbbebc historicization of JHI_USER table 2019-05-25 16:40:16 +02:00
Michael Hoennig
cbb5532394 Deserializer: improved test code coverage for entity associations 2019-05-22 21:53:45 +02:00
Michael Hoennig
e7cb3622f3 Deserializer: improved test code coverage for IGNORE field access 2019-05-16 20:25:02 +02:00
Michael Hoennig
f9b68df901 Deserializer: BadRequestAlertException("Unknown property") with test code coverage 2019-05-15 07:41:21 +02:00
Michael Hoennig
60612f6c41 #146 [AccessRights] simple AccessFilter for UserRoleAssignments
allows r/w for ADMINs and r/o for SUPPORTERs
no entity dependent access rights implemented yet
2019-05-14 07:36:14 +02:00
Michael Hoennig
3143f27b6c refactored ugly and risky code in role filter / removed Role.isIndependent() 2019-05-10 17:37:36 +02:00
Michael Hoennig
a2b90b0a36 separated MockSecurityContext into SecurityContextMock+SecurityContextMock 2019-05-10 17:21:50 +02:00
Michael Hoennig
72e79e2134 #112 [RightsModule] use UserRoleAssignments from database 2019-05-10 11:18:15 +02:00
Michael Hoennig
01d28a85d4 #137 [Rechte-Modul] sample-data für User+UserRoleAssignment 2019-05-08 20:17:28 +02:00
Michael Hoennig
6eaab336c0 minor corrections in README.md 2019-05-08 13:22:57 +02:00
Michael Hoennig
671d80cc71 adding @EntityTypeId to *DTO as preparation for UserRoleAssignments 2019-05-08 13:17:26 +02:00
Michael Hoennig
735f672ea1 UserRoleAssignment entityTypeId+entityObjectId now required
this table only stores entity-dependent roles,
independent roles are stored in the JHipster table JHI_USER_AUTHORITY
2019-05-08 10:56:48 +02:00
Michael Hoennig
8ca206e1c0 accessrights.jdl: UserRoleAssignments entity*Id required
Merge branch 'jhipster-generated'
2019-05-07 13:08:26 +02:00
Michael Hoennig
bac012f9b4 accessrights.jdl: UserRoleAssignments entity*Id required 2019-05-07 13:01:04 +02:00
Michael Hoennig
167bac8a1d minor improvements to jhipster workflow 2019-05-07 12:57:14 +02:00
Michael Hoennig
c756a251c2 .gitignore /worktrees/ 2019-05-07 12:45:15 +02:00
Michael Hoennig
3228b0d0da make Hibernate sequence start above 1,000,000 to spare ids for sample data 2019-05-07 12:11:17 +02:00
Michael Hoennig
a45e809228 updated moving HOWTO-tag documentation in README.md 2019-05-07 11:04:41 +02:00
Michael Hoennig
807ed74091 historicization for UserRoleAssinment 2019-05-07 11:01:01 +02:00
Michael Hoennig
6d85a59744 temporarily removing historicization for UserRoleAssinment
just to prepare a complete HOWTO-commit
2019-05-07 10:57:17 +02:00
Michael Hoennig
5337e7a7e8 historicization trigger+view for Customer, Membership, SepaMandate, Share and Asset 2019-05-07 10:54:27 +02:00
Michael Hoennig
e49323e99f UserRoleAssignment historicization 2019-05-06 19:12:12 +02:00
Michael Hoennig
f3c207528e general historicization table+function for Postgres 2019-05-06 19:11:33 +02:00
Michael Hoennig
295e45e8b8 ReplaceCustomChange: assume auto commit off during Liquibase-Changeset 2019-05-06 18:20:10 +02:00
Michael Hoennig
1284c2acaa add debug hint to README.md 2019-05-06 18:19:37 +02:00
Michael Hoennig
ecd5573654 ReplaceCustomChangeUnitTest 2019-05-06 11:47:08 +02:00
Michael Hoennig
f8ed5069fb sample-data: replacing pipes ('|') with newlines now via customChange
this avoids database-specific code spread over all sample-data/*.xml files
2019-05-05 14:15:07 +02:00
Michael Hoennig
4c42d15c12 fixing sample-data flag via gw -Psample-data in code+documentation 2019-05-05 14:13:17 +02:00
Michael Hoennig
6efa167427 adding Postgres configurations for dev-environment with sample-data 2019-05-04 13:59:36 +02:00
Michael Hierweck
24e76e03d1 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-05-04 10:24:45 +02:00
Michael Hierweck
1e0d34306c Transaction as foreign key. Example usage. 2019-05-04 10:24:29 +02:00
Michael Hoennig
22e7511952 #124 [intUI/Filter] for UserRoleAssignment 2019-05-03 23:22:17 +02:00
Michael Hoennig
5e001c59f9 Revert "#124 [intUI/Filter] for UserRoleAssignment"
I am reverting because I need to fix a bug in this commit and it's a tagged HOWTO commit.
2019-05-03 23:19:12 +02:00
Michael Hoennig
fbb356c8e9 fixing problems with infinite scroll due to hidden scrollbar and filtering 2019-05-03 23:17:52 +02:00
Michael Hoennig
d672d5b0cd more sample-data for assets 2019-05-03 22:36:57 +02:00
Michael Hoennig
967b06cfa7 #133 [intUI] for now, no sort functionality for *DisplayLabel columns 2019-05-03 21:50:08 +02:00
Michael Hoennig
cd94cb118d add placeholder="YYYY|--|++" to date filter inputs 2019-05-03 21:02:17 +02:00
Michael Hoennig
b7aac92807 match membership sample-date with sepa-mandate sample-data 2019-05-03 20:22:17 +02:00
Michael Hoennig
20b9d2f105 #131 sample-data for sepamandate 2019-05-03 20:21:07 +02:00
Michael Hoennig
0a9f2584f1 #124 [intUI/Filter] for UserRoleAssignment 2019-05-03 14:41:41 +02:00
Michael Hoennig
3c6618e3cb uniform formatting of TableFilter constructor call in share.component 2019-05-03 14:02:19 +02:00
Michael Hoennig
ab88505144 #135 [intUI/Filter] filtering non-/empty values possible with '--'/'++' 2019-05-03 12:22:10 +02:00
Michael Hoennig
1a8fb9276e #120 [intUI/Filter] for Membership 2019-05-03 12:20:54 +02:00
Michael Hoennig
447fbb773d #122 [intUI/Filter] for Asset and alignment with implementation in Share 2019-05-03 09:28:24 +02:00
Michael Hoennig
5f536ad043 #115/#116/#117 [intUI/textareas] Remark in Membership/Asset/Share 2019-05-03 00:32:58 +02:00
Michael Hoennig
da60a3020f remove Remark from table view of SepaMandate 2019-05-03 00:22:00 +02:00
Michael Hoennig
6816930793 #118 [intUI/textareas] multiline Remark in SepaMandate 2019-05-03 00:17:43 +02:00
Michael Hoennig
0b3f4d4a03 textarea input and multiline output for Customer.remark (was missing) 2019-05-03 00:12:41 +02:00
Michael Hoennig
8c367ab566 #129 [intUI/Date-Format] SepaMandate 2019-05-03 00:03:51 +02:00
Michael Hoennig
64a91c82ae makes the actual data easier to read in details views 2019-05-03 00:01:40 +02:00
Michael Hoennig
d758e3a979 #127/#128 [intUI/Date-Format] Asset+Share 2019-05-02 23:45:07 +02:00
Michael Hoennig
30b4d1f95c #126 [intUI/Date-Format] Membership 2019-05-02 23:39:50 +02:00
Michael Hoennig
5415806621 use STRINGDECODE \n in UPDATE-Statements for linebreaks in sample-data 2019-05-02 23:36:08 +02:00
Michael Hoennig
1bb712276c properly display line-breaks in customer address fields + STRINGDECODE \n 2019-05-02 23:25:35 +02:00
Michael Hoennig
dccafcc900 added TableFilter to share.component, TableFilter now with date range 2019-05-02 22:51:17 +02:00
Michael Hoennig
8ae7547775 customer table with translated customerKind 2019-05-02 11:52:41 +02:00
Michael Hoennig
2dd3deb17b fixing tslint issues 2019-05-02 11:28:40 +02:00
Michael Hoennig
13100a9010 assets sample data 2019-05-02 11:25:13 +02:00
Michael Hoennig
84f1abf0b0 shares sample data and some cleanup 2019-05-02 11:17:22 +02:00
Michael Hoennig
7f358cc7fe membership sample data and some minor fixes 2019-05-02 11:06:09 +02:00
Michael Hoennig
89437ca067 filter Customer table + class TableFilter 2019-05-02 09:54:37 +02:00
Michael Hoennig
d182637f41 improved the README.md, especially regarding the JHipster workflow 2019-05-01 14:37:22 +02:00
Michael Hoennig
f40a574ec3 improved the documentation for JHipster with spotless in README.md 2019-04-30 18:35:24 +02:00
Michael Hoennig
be52181b7e Merge branch 'jhipster-generated'
# Conflicts:
#	src/test/java/org/hostsharing/hsadminng/web/rest/CustomerResourceIntTest.java
2019-04-30 18:30:48 +02:00
Michael Hoennig
6df2cbce0b remove duplicate userId from entity UserRoleAssignment
(it already comes implicitly through the relationship)
2019-04-30 18:29:32 +02:00
Michael Hoennig
4529527e3b removing duplicate userId from entity UserRoleAssignment
(it comes implicitly through the relationship)
2019-04-30 18:04:25 +02:00
Michael Hoennig
3c89878c21 cate userId from entity UserRoleAssignment
(it comes implicitly through the relationship)
2019-04-30 17:58:56 +02:00
Michael Hoennig
3908ff3d74 Merge branch 'jhipster-generated' with initially imported accessrighs.jdl 2019-04-30 17:29:18 +02:00
Michael Hoennig
084c12d2c4 adding and importing accessrights.jdl with spotless 2019-04-30 17:24:24 +02:00
Michael Hoennig
1aed3236a5 Merge branch 'jhipster-generated'
# Conflicts:
#	build.gradle
#	cfg/spotless/eclipse_formatter.xml
#	cfg/spotless/hsadminng.importorder
#	src/main/java/org/hostsharing/hsadminng/HsadminNgApp.java
#	src/main/java/org/hostsharing/hsadminng/domain/Asset.java
#	src/main/java/org/hostsharing/hsadminng/domain/Membership.java
#	src/main/java/org/hostsharing/hsadminng/domain/SepaMandate.java
#	src/main/java/org/hostsharing/hsadminng/domain/Share.java
#	src/main/java/org/hostsharing/hsadminng/repository/MembershipRepository.java
#	src/main/java/org/hostsharing/hsadminng/security/SecurityUtils.java
#	src/main/java/org/hostsharing/hsadminng/service/AssetService.java
#	src/main/java/org/hostsharing/hsadminng/service/CustomerQueryService.java
#	src/main/java/org/hostsharing/hsadminng/service/MembershipQueryService.java
#	src/main/java/org/hostsharing/hsadminng/service/MembershipService.java
#	src/main/java/org/hostsharing/hsadminng/service/SepaMandateQueryService.java
#	src/main/java/org/hostsharing/hsadminng/service/ShareService.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/CustomerCriteria.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/SepaMandateDTO.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java
#	src/main/java/org/hostsharing/hsadminng/service/mapper/CustomerMapper.java
#	src/main/java/org/hostsharing/hsadminng/web/rest/AssetResource.java
#	src/main/java/org/hostsharing/hsadminng/web/rest/ShareResource.java
#	src/main/java/org/hostsharing/hsadminng/web/rest/errors/ExceptionTranslator.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/AssetResourceIntTest.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/CustomerResourceIntTest.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/MembershipResourceIntTest.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/SepaMandateResourceIntTest.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/ShareResourceIntTest.java
2019-04-30 17:13:06 +02:00
Michael Hoennig
2c94d6a985 adding spotless, re-importing customer.jdl and spotlessApply 2019-04-30 17:08:45 +02:00
Michael Hoennig
1373756877 Revert "makes 'gw spotlessCheck' deliberately fail to test Jenkins pipeline"
This reverts commit dcf7bdc9
2019-04-30 13:13:57 +02:00
Michael Hoennig
dcf7bdc9b1 makes 'gw spotlessCheck' deliberately fail to test Jenkins pipeline 2019-04-30 13:07:27 +02:00
Michael Hoennig
13fb78a622 added gw spotlessCheck to Jenkins pipeline 2019-04-30 12:53:52 +02:00
Michael Hoennig
b5d50acfbd gw spotlessApply to latest revision, preparation for re-merging generated 2019-04-30 12:21:33 +02:00
Michael Hoennig
a5f69a8cad adding spotless to gradle config 2019-04-30 12:18:31 +02:00
Michael Hoennig
29a2178a48 splitting up build.gradle - step 1 2019-04-30 07:52:37 +02:00
Michael Hoennig
de6c7c5d4a added links to PiTest reports in Jenkins 2019-04-30 06:56:33 +02:00
Michael Hoennig
087e6617d3 using a proper displayLabel for the parent SepaMandate->Customer (HOWTO)
Unfortunately without a test for the HTML template changes of the Angular components
because these seem to come only with Protractor, which we had not configured in JHipster.
2019-04-29 18:43:23 +02:00
Michael Hoennig
2980103764 run createSepaMandate test now as FINANCIAL_CONTACT of related customer 2019-04-29 15:18:45 +02:00
Michael Hoennig
d3db9d1392 added a PiTest section to HOWTO.md 2019-04-29 15:15:59 +02:00
Michael Hoennig
2fa728cc45 restructuring the HOWTO.md headline levels 2019-04-29 14:46:03 +02:00
Michael Hoennig
b433f91bea section about HOWTO- tags 2019-04-29 14:44:02 +02:00
Michael Hoennig
0fd30a5700 adding AccessMappings SepaMandateDTO with tests + consequences (HOWTO) 2019-04-29 12:39:31 +02:00
Michael Hoennig
cc0857e33b FluentBuilder<D> as interface with default method 2019-04-29 12:19:34 +02:00
Michael Hoennig
1916347490 Revert "adding AccessMappings SepaMandateDTO with tests + consequences (HOWTO)"
This reverts commit 328e809a because I want all changes of this HOWTO in a single commit.

Unfortunately due to the access filter, the SepaMandateIntTest failed with branch coverage, as some branches were not reachable anymore.
2019-04-29 11:23:05 +02:00
Michael Hoennig
328e809a2b adding AccessMappings SepaMandateDTO with tests + consequences (HOWTO) 2019-04-29 09:39:08 +02:00
Michael Hoennig
474d51fbae refactoring of AccessMappingsUnitTestBase overloading -> method refs in ctor 2019-04-29 09:29:04 +02:00
Michael Hoennig
0de11a2548 cleanup unused constants in SepaMandateResourceIntTest 2019-04-29 09:12:30 +02:00
Michael Hoennig
8267cef973 improved code coverage for ShareResource 2019-04-28 14:38:03 +02:00
Michael Hoennig
5835f15cd9 implemented more AssetDTO+ShareDTO tests and fixed but in ShareDTO 2019-04-28 14:15:51 +02:00
Michael Hoennig
2cd92d4e2c generalized AccessMappingsUnitTestBase 2019-04-28 12:05:02 +02:00
Michael Hoennig
0257c83fa5 fine-tuning pitest config and increasing threshold 2019-04-28 11:14:44 +02:00
Michael Hoennig
405568fee4 fine-tuning pitest config and increasing threshold 2019-04-28 08:11:41 +02:00
Michael Hoennig
187ccfdcc7 writtenFields->updatingFields and remove fields which are not actually updating (effectively same value) 2019-04-27 21:01:29 +02:00
Michael Hoennig
c2880a3914 exclude generated *MapperImpl from pitest 2019-04-27 20:51:03 +02:00
Michael Hoennig
acfef3dfbe test for equality via compareTo() to consider BigDecimal/DateTime etc. => effectively unchanged in update 2019-04-27 20:36:25 +02:00
Michael Hoennig
3abc201a8d split up AssetDTOUnitTest and AssetDTOIntTest, AccessMappingsUnitTestBase 2019-04-27 17:56:50 +02:00
Michael Hierweck
2fdb914f6d Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-27 15:46:55 +02:00
Michael Hierweck
d4adbb972d Sample data for customers. 2019-04-27 15:46:43 +02:00
Michael Hoennig
5edf97b230 AssetDTOUnitTest and Membership.displayLabel now with memberFrom/Until 2019-04-27 15:03:03 +02:00
Michael Hoennig
456a2f4811 reduced logging for determined user role to debug level 2019-04-27 13:48:27 +02:00
Michael Hoennig
956cadeb09 membership.model.ts: membershipDisplayLabel -> customerDisplayLabel 2019-04-27 13:23:32 +02:00
Michael Hoennig
efa2972375 code coverage improved for MembershipResource and CustomerResource
and Customer not explicitly deletable anymore
2019-04-27 12:05:30 +02:00
Michael Hoennig
3b0d46a4c8 better display labels and less columns in tables (deliberately not yet in SepaMandate*) 2019-04-27 11:30:15 +02:00
Michael Hoennig
b9a7c33c6a explicitly mock user+role in *IntTest, set global role HOSTMASTER only in dev profile 2019-04-27 11:10:36 +02:00
Michael Hoennig
5d843f4725 org.hostsharing.hsadminng logging on INFO level and logging in SecurityUtils 2019-04-27 10:28:16 +02:00
Michael Hoennig
53382beb88 MembershipDTO JSonSerializerWithAccessFilter proper membershipDisplayReference 2019-04-27 09:52:54 +02:00
Michael Hoennig
bed81ee7f8 Fix AssetResourceIntTest BigDecimal scaling problem 2019-04-27 07:16:26 +02:00
Michael Hoennig
c8bb8cb2ca @AccessFrom in AssetDTO and AssetMapper with improved membershipDisplayReference 2019-04-27 07:13:52 +02:00
Michael Hoennig
6570981dc2 Revert "@AccessFrom in AssetDTO and AssetMapper with improved membershipDisplayReference"
This reverts commit 4ad0d5d954.
2019-04-27 06:37:56 +02:00
Michael Hoennig
51ff0c4990 Revert "MembershipDTO JSonSerializerWithAccessFilter proper membershipDisplayReference"
This reverts commit da5b667607.
2019-04-27 06:37:30 +02:00
Michael Hoennig
638a35ea5a Revert "Fix AssetResourceIntTest BigDecimal scaling problem"
This reverts commit a86e4d7afb.
2019-04-27 06:36:47 +02:00
Michael Hoennig
7e3d3ec5de Revert "better display labels and less columns in tables (deliberately not yet in SepaMandate*)"
This reverts commit d81b71cd3a.
2019-04-27 06:36:11 +02:00
Michael Hoennig
f301364d84 Revert "trying to find reason for failing tests in Jenkins, locally all tests are green"
This reverts commit 318ecd0be4.
2019-04-27 06:36:01 +02:00
Michael Hoennig
f08a1d89b3 Revert "trying to find reason for failing tests in Jenkins, locally all tests are green"
This reverts commit 411342eb0b.
2019-04-27 06:35:51 +02:00
Michael Hoennig
411342eb0b trying to find reason for failing tests in Jenkins, locally all tests are green 2019-04-26 23:00:21 +02:00
Michael Hoennig
318ecd0be4 trying to find reason for failing tests in Jenkins, locally all tests are green 2019-04-26 22:53:32 +02:00
Michael Hoennig
d81b71cd3a better display labels and less columns in tables (deliberately not yet in SepaMandate*) 2019-04-26 22:11:49 +02:00
Michael Hoennig
a86e4d7afb Fix AssetResourceIntTest BigDecimal scaling problem 2019-04-26 19:35:07 +02:00
Michael Hoennig
590a3031c7 Merge remote-tracking branch 'origin/master' 2019-04-26 19:11:25 +02:00
Michael Hoennig
da5b667607 MembershipDTO JSonSerializerWithAccessFilter proper membershipDisplayReference 2019-04-26 17:43:22 +02:00
Michael Hoennig
4ad0d5d954 @AccessFrom in AssetDTO and AssetMapper with improved membershipDisplayReference 2019-04-26 17:15:39 +02:00
Michael Hierweck
7b039fcedb Remove outdate liquibase changesets 2019-04-26 12:54:10 +02:00
Michael Hierweck
40ce312880 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-26 11:55:52 +02:00
Michael Hierweck
9a19a65edc Documentation of Git workflow 2019-04-26 11:55:42 +02:00
Michael Hoennig
6092afe32b better coverage 2019-04-26 11:41:48 +02:00
Michael Hoennig
a10a3a62e5 fix failing unit test and better coverage 2019-04-26 11:27:29 +02:00
Michael Hoennig
ef3e8c968b Merge remote-tracking branch 'origin/master' 2019-04-26 10:26:34 +02:00
Michael Hoennig
ea08f4a6c6 removed bloat-code from JsonComponents in DTOs 2019-04-26 10:26:25 +02:00
Michael Hierweck
7592a1d459 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-26 10:04:17 +02:00
Michael Hierweck
9c1bed2189 Historicization with PostgreSQL improved. 2019-04-26 10:04:06 +02:00
Michael Hoennig
a6fd210de5 test code coverage improved and more JSon/field types 2019-04-25 18:53:32 +02:00
Michael Hierweck
070f321a06 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-25 17:17:18 +02:00
Michael Hierweck
da00c80b34 Historicization with PostgreSQL. 2019-04-25 17:17:03 +02:00
Michael Hoennig
fb3b79cfc4 for now, make give all users the role of a HOSTMASTER 2019-04-25 12:56:51 +02:00
Michael Hoennig
3e30cf2d17 merging current DTO during deserialization 2019-04-25 12:22:45 +02:00
Michael Hoennig
fb961ca4a1 merging from master 2019-04-24 17:41:28 +02:00
Michael Hierweck
7c7d79c870 Fixed tests after merge. 2019-04-24 17:22:56 +02:00
Michael Hierweck
5ac1277e7e Merge branch 'jhipster-generated' 2019-04-24 16:14:14 +02:00
Michael Hierweck
2a4ee0507c Preliminary completed customer model. 2019-04-24 14:36:11 +02:00
Michael Hoennig
ad1517a16e JSonAccessFilter with generic access to grand parent role 2019-04-24 13:23:08 +02:00
Michael Hoennig
639ea06243 JSonAccessFilter with initially working (hardcoded) grand parent role 2019-04-24 12:30:26 +02:00
Michael Hoennig
a94516b3ce JSonSerializer/DeserializerWithAccessFilter: also use role in parent 2019-04-23 14:37:49 +02:00
Michael Hoennig
1505e7bd66 JSonDeserializerWithAccessFilter: shouldDetectMultipleSelfIdFields 2019-04-23 09:22:23 +02:00
Michael Hoennig
d2b0f477f2 CustomerDTO: AccessFor/Rights-Configuration 2019-04-23 08:42:47 +02:00
Michael Hoennig
63bd602397 JSonDeserializerWithAccessFilter with working access rights validation 2019-04-23 08:31:26 +02:00
Michael Hoennig
bb0fb4aa78 parallel structure for JSonSerializer/DeserializerWithAccessFilter 2019-04-23 06:57:06 +02:00
Michael Hoennig
90316a262b JSonSerializerWithAccessFilter - idea for parallel structure with deserializer 2019-04-23 06:28:36 +02:00
Michael Hoennig
741e91bb78 JSonSerializerWithAccessFilter - idea for generalization of field types 2019-04-23 06:27:41 +02:00
Michael Hoennig
1dae396d99 JSonDeserializerWithAccessFilter 2019-04-23 06:17:55 +02:00
Michael Hoennig
1bc7044527 fixing pitest failure by excluding purely generated code etc.
also removed unused methods and added a unit test to improve coverage
and setting thresholds now to 90% each
2019-04-21 11:53:30 +02:00
Michael Hoennig
1ac8df5ed9 improving branch coverage for AssetValidator+ShareValidator 2019-04-20 14:47:45 +02:00
Michael Hoennig
7ba20b3687 improving branch coverage for AssetResourceIntTest 2019-04-20 10:47:01 +02:00
Michael Hoennig
9f95af7547 fixing the ShareServiceUnitTest as validation was moved to ShareValidator 2019-04-20 09:43:11 +02:00
Michael Hoennig
98f96a72b1 fixing the AssetResourceIntTest BigDecimal equals 2019-04-19 14:25:25 +02:00
Michael Hoennig
de439a1658 fixing the *IntTest regarding required related entities and unique constraints 2019-04-19 14:13:16 +02:00
Michael Hoennig
70db76b439 comment access rights on CustomerDTO 2019-04-19 14:13:15 +02:00
Michael Hierweck
a89967e91b Translations. 2019-04-19 14:11:54 +02:00
Michael Hierweck
bc1c94a364 Shares and Assets. 2019-04-19 13:44:52 +02:00
Michael Hoennig
998a5a8aa1 WIP JSonDeserializerWithAccessFilter 2019-04-19 12:08:30 +02:00
Michael Hoennig
0b7ebac472 first shot @AccessFor on MembershipDTO 2019-04-19 12:08:04 +02:00
Michael Hoennig
6e017aba49 removing orphaned specialized serializer from CustomerDTO 2019-04-19 11:01:52 +02:00
Michael Hoennig
a24ca35bd7 JSonSerializerWithAccessFilterUnitTest 2019-04-19 10:46:40 +02:00
Michael Hoennig
1ad74907bd RoleUnitTest + special case FINANCIAL_CUSTOMER_CONTACT 2019-04-19 09:13:19 +02:00
Michael Hoennig
d5a37ddfae JavaDoc for Role 2019-04-19 07:46:11 +02:00
Michael Hoennig
f4960f260e moving the JSonSerializer to a separate package 2019-04-19 07:18:20 +02:00
Michael Hoennig
6ab67995ff fixing tests after merging master 2019-04-18 18:11:56 +02:00
Michael Hoennig
16cf845015 Merge branch 'master' into spike/JSonSerializerDeserializerForDTOs
# Conflicts:
#	package-lock.json
#	src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java
2019-04-18 17:36:35 +02:00
Michael Hoennig
ce083e928a comment access rights on CustomerDTO 2019-04-18 17:30:28 +02:00
Michael Hierweck
e3792d9570 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-18 17:18:13 +02:00
Michael Hierweck
633ae1d7d1 Merge branch 'jhipster-generated' 2019-04-18 17:18:03 +02:00
Michael Hoennig
5287d78fcb JSON serializer generalized 2019-04-18 17:16:18 +02:00
Michael Hierweck
0e417fa772 Merge branch 'jhipster-generated' 2019-04-18 16:33:28 +02:00
Michael Hierweck
fa0ba61de3 Generated 2019-04-18 16:31:39 +02:00
Michael Hierweck
c6be30895e Customer JDL 2019-04-18 16:20:25 +02:00
Michael Hierweck
1dc03456b2 Merge branch 'jhipster-generated' 2019-04-18 15:25:44 +02:00
Michael Hoennig
bc87739d6f JSON serialiezr/deserializer for CustomerDTO - manually 2019-04-18 14:48:58 +02:00
Michael Hierweck
2459dd3b64 Model customers. 2019-04-18 12:12:25 +02:00
Michael Hierweck
bff4547d40 Merge branch 'jhipster-generated' 2019-04-18 10:51:02 +02:00
Michael Hierweck
7f0dfebaa4 Model customers. 2019-04-18 09:33:55 +02:00
Michael Hoennig
35565e1b43 comment access rights on CustomerDTO 2019-04-17 16:45:16 +02:00
Michael Hoennig
7b7598871b added sample data for memberships 2019-04-12 18:50:38 +02:00
Michael Hoennig
3e66a21805 added comment regarding potential race condition 2019-04-12 17:34:28 +02:00
Michael Hoennig
73969f5a40 using IntTests.createEntity() as generated by JHipster with required fields 2019-04-12 17:20:48 +02:00
Michael Hoennig
3fa514c982 Merge branch 'jhipster-generated'
# Conflicts:
#	src/main/java/org/hostsharing/hsadminng/domain/Asset.java
#	src/main/java/org/hostsharing/hsadminng/domain/Customer.java
#	src/main/java/org/hostsharing/hsadminng/domain/Membership.java
#	src/main/java/org/hostsharing/hsadminng/domain/Share.java
#	src/main/java/org/hostsharing/hsadminng/service/dto/CustomerCriteria.java
#	src/main/resources/config/liquibase/changelog/20190403083735_added_entity_Customer.xml
#	src/main/webapp/app/entities/customer/customer-detail.component.html
#	src/main/webapp/app/entities/customer/customer-update.component.html
#	src/main/webapp/app/entities/customer/customer.component.html
#	src/main/webapp/app/entities/entity.module.ts
#	src/test/java/org/hostsharing/hsadminng/web/rest/CustomerResourceIntTest.java
#	src/test/java/org/hostsharing/hsadminng/web/rest/ShareResourceIntTest.java
2019-04-12 16:47:18 +02:00
Michael Hoennig
bd5bb859d9 re-importing latest customer.jdl from master branch 2019-04-12 16:46:43 +02:00
Michael Hoennig
39950f0d69 several 'required' flags added to jdl and re-import 2019-04-12 16:43:16 +02:00
Michael Hoennig
4dbbd5ecde MembershipValidator using membershipRepository.hasUncancelledMembershipForCustomer 2019-04-12 14:43:23 +02:00
Michael Hoennig
ca9879db92 improved shouldValidateThatUntilDateIsAfterSinceDate test 2019-04-11 18:16:08 +02:00
Michael Hoennig
d1d6001842 introduced MembershipValidator 2019-04-11 18:06:12 +02:00
Michael Hoennig
c6bf856b34 prevent deleting memberhsip via service call 2019-04-11 16:52:50 +02:00
Michael Hoennig
4fdcf77e8e added mutation testing with pitest as a separate task (too slow because there are almost only IntTests) 2019-04-11 14:34:19 +02:00
Michael Hoennig
19c0049b0e added mutation testing with pitest as a separate task (too slow because there are almost only IntTests) 2019-04-11 12:51:07 +02:00
Michael Hoennig
e7e38f68b5 lowering jacoco branch cover ratio, so it should pass 2019-04-09 15:25:58 +02:00
Michael Hoennig
19a02460e8 adds jacoco check to Jenkinsfile 2019-04-09 15:18:11 +02:00
Michael Hoennig
ec70357ea0 jacoco code coverage (with deliberately too low limit for testing) 2019-04-09 15:00:01 +02:00
Michael Hoennig
933e4f3d3d ts-lint: do not warn if properties or parameters define inferrable type 2019-04-09 13:19:57 +02:00
Michael Hoennig
c6d40c8219 dont close H2 on exit in test runs 2019-04-09 12:00:47 +02:00
Michael Hoennig
9039fa5109 properly display line-breaks in customer address fields 2019-04-09 11:33:34 +02:00
Michael Hoennig
fe60e789d1 line-breaks in customer address sanmple data + sample data separated 2019-04-09 10:11:10 +02:00
Michael Hoennig
0419e7cd21 ShareServiceUnitTest and fixes in other related tests 2019-04-08 16:16:06 +02:00
Michael Hoennig
09da027bed Merge branch 'jhipster-generated' 2019-04-04 09:21:25 +02:00
Michael Hoennig
d787544ebf added name+address+salutation to customer 2019-04-04 09:19:50 +02:00
Tim Weber
bb4bebaee6 Make API port configurable and add documentation 2019-04-03 18:28:50 +02:00
Tim Weber
cdde4db66c Add missing files for Vue frontend
Don't add files to the staging area while IntelliJ is indexing, I guess.
2019-04-03 17:58:44 +02:00
Tim Weber
8799cc86e9 Add current state of the Vue frontend 2019-04-03 17:54:05 +02:00
Michael Hierweck
31aafd3b86 Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng 2019-04-03 14:29:48 +02:00
Michael Hierweck
603bd4ad67 PoC: data set based input validation (backend) 2019-04-03 14:29:12 +02:00
1187 changed files with 65476 additions and 65024 deletions

97
.aliases Normal file
View File

@ -0,0 +1,97 @@
# For using the alias gw-importOfficeData or gw-importHostingAssets,
# copy the file .tc-environment to .environment (ignored by git)
# and amend them according to your external DB.
gradleWrapper () {
if [ ! -f gradlew ]; then
echo "No 'gradlew' found. Maybe you are not in the root dir of a gradle project?"
return 1
fi
TEMPFILE=$(mktemp /tmp/gw.XXXXXX)
unbuffer ./gradlew "$@" | tee $TEMPFILE
echo
grep --color=never "Report:" $TEMPFILE
rm $TEMPFILE
}
postgresAutodoc () {
if ! [ -x "$(command -v postgresql_autodoc)" ]; then
echo "Program 'postgresql_autodoc' not found. Please install, e.g. via: sudo apt install postgresql-autodoc" >&2
echo "See also https://github.com/cbbrowne/autodoc" >&2
return 1
fi
if ! [ -x "$(command -v dot)" ]; then
echo "Program 'graphviz dot' not found. Please install, e.g. via: sudo apt install graphviz" >&2
echo "See also https://graphviz.org" >&2
return 1
fi
postgresql_autodoc -d postgres -f build/postgres-autodoc -h localhost -u postgres --password=password \
-m '(rbacobject|hs).*' \
-l /usr/share/postgresql-autodoc -t neato &&
dot -Tsvg build/postgres-autodoc.neato >build/postgres-autodoc-hs.svg && \
echo "generated: $PWD/build/postgres-autodoc-hs.svg"
postgresql_autodoc -d postgres -f build/postgres-autodoc -h localhost -u postgres --password=password \
-m '(global|rbac).*' \
-l /usr/share/postgresql-autodoc -t neato &&
dot -Tsvg build/postgres-autodoc.neato >build/postgres-autodoc-rbac.svg && \
echo "generated $PWD/build/postgres-autodoc-rbac.svg"
}
alias postgres-autodoc=postgresAutodoc
function importLegacyData() {
export target=$1
if [ -z "$target" ]; then
echo "importLegacyData needs target argument, but none was given" >&2
else
source .tc-environment
if [ -f .environment ]; then
source .environment
fi
echo "using environment (with ending ';' for use in IntelliJ IDEA):"
echo "--- BEGIN: ---"
set | grep ^HSADMINNG_ | sed 's/$/;/'
echo "---- END. ----"
echo
echo ./gradlew $target --rerun
./gradlew $target --rerun
fi
}
alias gw-importOfficeData='importLegacyData importOfficeData'
alias gw-importHostingAssets='importLegacyData importHostingAssets'
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-use='export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"; export TESTCONTAINERS_RYUK_DISABLED=true'
alias gw=gradleWrapper
alias pg-sql-run='docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:15.5-bookworm'
alias pg-sql-stop='docker stop hsadmin-ng-postgres'
alias pg-sql-start='docker container start hsadmin-ng-postgres'
alias pg-sql-remove='docker rm hsadmin-ng-postgres'
alias pg-sql-reset='pg-sql-stop; pg-sql-remove; pg-sql-run'
alias pg-sql-backup='docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean --create -U postgres postgres | gzip -9'
alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres'
alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l'
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
alias gw-test='. .aliases; ./gradlew test'
alias gw-check='. .aliases; gw test importOfficeData check -x pitest -x :dependencyCheckAnalyze'
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
alias gw-importOfficeData-in-docker-compose='
docker-compose -f etc/docker-compose.yml down &&
docker-compose -f etc/docker-compose.yml up -d && sleep 10 &&
time gw-importHostingAssets'
if [ ! -f .environment ]; then
cp .tc-environment .environment
fi
source .environment

View File

@ -18,7 +18,3 @@ insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[package.json]
indent_style = space
indent_size = 2

20
.gitignore vendored
View File

@ -3,6 +3,7 @@
######################
/build/www/**
/src/test/javascript/coverage/
/worktrees/
######################
# Node
@ -77,7 +78,10 @@ out/
# Gradle
######################
.gradle/
!gradle/wrapper/gradle-wrapper.jar
/build/
.gradletasknamecache
gradle-app.setting
######################
# Package Files
@ -128,17 +132,13 @@ Desktop.ini
*~
.merge_file*
######################
# Gradle Wrapper
######################
!gradle/wrapper/gradle-wrapper.jar
######################
# Maven Wrapper
######################
!.mvn/wrapper/maven-wrapper.jar
######################
# ESLint
######################
.eslintcache
######################
# Project Related
######################
/.environment*
/src/test/resources/migration-prod/*

View File

@ -1,5 +0,0 @@
{
"hooks": {
"pre-commit": "lint-staged"
}
}

View File

@ -1,53 +0,0 @@
{
"name": "Asset",
"fields": [
{
"fieldName": "date",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "action",
"fieldType": "AssetAction",
"fieldValues": "PAYMENT,HANDOVER,ADOPTION,LOSS,CLEARING,PAYBACK",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "amount",
"fieldType": "BigDecimal",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "comment",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "membership",
"otherEntityRelationshipName": "asset",
"relationshipName": "member",
"otherEntityField": "id"
}
],
"changelogDate": "20190403083740",
"entityTableName": "asset",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,49 +0,0 @@
{
"name": "Contact",
"fields": [
{
"fieldName": "firstName",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "lastName",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 80
},
{
"fieldName": "email",
"fieldType": "String",
"fieldValidateRules": [
"required",
"maxlength"
],
"fieldValidateRulesMaxlength": 80
}
],
"relationships": [
{
"relationshipType": "one-to-many",
"otherEntityName": "customerContact",
"otherEntityRelationshipName": "contact",
"relationshipName": "role"
}
],
"changelogDate": "20190403083736",
"entityTableName": "contact",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,50 +0,0 @@
{
"name": "Customer",
"fields": [
{
"fieldName": "number",
"fieldType": "Integer",
"fieldValidateRules": [
"required",
"unique",
"min",
"max"
],
"fieldValidateRulesMin": 10000,
"fieldValidateRulesMax": 99999
},
{
"fieldName": "prefix",
"fieldType": "String",
"fieldValidateRules": [
"required",
"unique",
"pattern"
],
"fieldValidateRulesPattern": "[a-z][a-z0-9]+"
}
],
"relationships": [
{
"relationshipType": "one-to-many",
"otherEntityName": "membership",
"otherEntityRelationshipName": "customer",
"relationshipName": "membership"
},
{
"relationshipType": "one-to-many",
"otherEntityName": "customerContact",
"otherEntityRelationshipName": "customer",
"relationshipName": "role"
}
],
"changelogDate": "20190403083735",
"entityTableName": "customer",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,40 +0,0 @@
{
"name": "CustomerContact",
"fields": [
{
"fieldName": "role",
"fieldType": "CustomerContactRole",
"fieldValues": "CONTRACTUAL,TECHNICAL,FINANCIAL",
"fieldValidateRules": [
"required"
]
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "contact",
"otherEntityRelationshipName": "role",
"relationshipValidateRules": "required",
"relationshipName": "contact",
"otherEntityField": "email"
},
{
"relationshipType": "many-to-one",
"otherEntityName": "customer",
"otherEntityRelationshipName": "role",
"relationshipValidateRules": "required",
"relationshipName": "customer",
"otherEntityField": "prefix"
}
],
"changelogDate": "20190403083737",
"entityTableName": "customer_contact",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,46 +0,0 @@
{
"name": "Membership",
"fields": [
{
"fieldName": "sinceDate",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "untilDate",
"fieldType": "LocalDate"
}
],
"relationships": [
{
"relationshipType": "one-to-many",
"otherEntityName": "share",
"otherEntityRelationshipName": "member",
"relationshipName": "share"
},
{
"relationshipType": "one-to-many",
"otherEntityName": "asset",
"otherEntityRelationshipName": "member",
"relationshipName": "asset"
},
{
"relationshipType": "many-to-one",
"otherEntityName": "customer",
"otherEntityRelationshipName": "membership",
"relationshipName": "customer",
"otherEntityField": "prefix"
}
],
"changelogDate": "20190403083738",
"entityTableName": "membership",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,53 +0,0 @@
{
"name": "Share",
"fields": [
{
"fieldName": "date",
"fieldType": "LocalDate",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "action",
"fieldType": "ShareAction",
"fieldValues": "SUBSCRIPTION,CANCELLATION",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "quantity",
"fieldType": "Integer",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "comment",
"fieldType": "String",
"fieldValidateRules": [
"maxlength"
],
"fieldValidateRulesMaxlength": 160
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "membership",
"otherEntityRelationshipName": "share",
"relationshipName": "member",
"otherEntityField": "id"
}
],
"changelogDate": "20190403083739",
"entityTableName": "share",
"dto": "mapstruct",
"pagination": "infinite-scroll",
"service": "serviceClass",
"jpaMetamodelFiltering": true,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}

View File

@ -1,3 +0,0 @@
node_modules
target
package-lock.json

View File

@ -1,12 +0,0 @@
# Prettier configuration
printWidth: 140
singleQuote: true
tabWidth: 4
useTabs: false
# js and ts rules:
arrowParens: avoid
# jsx and tsx rules:
jsxBracketSameLine: false

View File

@ -0,0 +1,37 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImportHostingAssets into local" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_POSTGRES_ADMIN_PASSWORD" value="password" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="postgres" />
<entry key="HSADMINNG_POSTGRES_JDBC_URL" value="jdbc:postgresql://localhost:5432/postgres" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importHostingAssets" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportHostingAssets&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,36 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImportHostingAssets" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importHostingAssets" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportHostingAssets&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,103 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importOfficeData" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportOfficeData&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importOfficeData" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.office.migration.ImportOfficeData&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":importOfficeData" />
<option value="--tests" />
<option value="&quot;net.hostsharing.hsadminng.hs.migration.ImportOfficeData&quot;" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="coverage" sample_coverage="false" />
</EXTENSION>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>true</RunAsTest>
<method v="2" />
</configuration>
</component>

1
.run/README.txt Normal file
View File

@ -0,0 +1 @@
Stored run-Configurations for IntelliJ IDEA.

8
.tc-environment Normal file
View File

@ -0,0 +1,8 @@
unset HSADMINNG_POSTGRES_JDBC_URL # dynamically set, different for normal tests and imports
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
export HSADMINNG_POSTGRES_ADMIN_PASSWORD=
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
export HSADMINNG_MIGRATION_DATA_PATH=migration
export LIQUIBASE_CONTEXT=
export LANG=en_US.UTF-8

8
.unset-environment Normal file
View File

@ -0,0 +1,8 @@
unset HSADMINNG_POSTGRES_JDBC_URL
unset HSADMINNG_POSTGRES_ADMIN_USERNAME
unset HSADMINNG_POSTGRES_ADMIN_PASSWORD
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
unset HSADMINNG_SUPERUSER
unset HSADMINNG_MIGRATION_DATA_PATH
unset LIQUIBASE_CONTEXT

View File

@ -1,38 +0,0 @@
{
"generator-jhipster": {
"promptValues": {
"packageName": "org.hostsharing.hsadminng",
"nativeLanguage": "de"
},
"jhipsterVersion": "5.8.2",
"applicationType": "monolith",
"baseName": "hsadminNg",
"packageName": "org.hostsharing.hsadminng",
"packageFolder": "org/hostsharing/hsadminng",
"serverPort": "8080",
"authenticationType": "jwt",
"cacheProvider": "ehcache",
"enableHibernateCache": false,
"websocket": false,
"databaseType": "sql",
"devDatabaseType": "h2Memory",
"prodDatabaseType": "postgresql",
"searchEngine": false,
"messageBroker": false,
"serviceDiscoveryType": false,
"buildTool": "gradle",
"enableSwaggerCodegen": true,
"jwtSecretKey": "ZDFlMDUzODIzMTUzZDEwZjExN2E5ZjAzY2VhZmYzNDE1YjhlYWUxZGRhMGU3ODZiNjRkNjVlNzEwZjExYWY4YzczM2NlYzI5YWE1OTRkNWM0YThlYjZjZjA5Zjc5YWJkOTgzYjdhZjQxZWQyZGUyYjFlYjI5ZDE3NmE4M2UzYjQ=",
"clientFramework": "angularX",
"useSass": false,
"clientPackageManager": "npm",
"testFrameworks": ["cucumber"],
"jhiPrefix": "jhi",
"entitySuffix": "",
"dtoSuffix": "DTO",
"otherModules": [],
"enableTranslation": true,
"nativeLanguage": "de",
"languages": ["de", "en"]
}
}

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
# build using:
# docker build -t postgres-with-contrib:15.5-bookworm .
FROM postgres:15.5-bookworm
RUN apt-get update && \
apt-get install -y postgresql-contrib && \
apt-get clean
COPY etc/postgresql-log-slow-queries.conf /etc/postgresql/postgresql.conf

View File

@ -1,196 +0,0 @@
# hsadminNg
This application was generated using JHipster 5.8.2, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v5.8.2](https://www.jhipster.tech/documentation-archive/v5.8.2).
## Development
Before you can build this project, you must install and configure the following dependencies on your machine:
1. [Node.js][]: We use Node to run a development web server and build the project.
Depending on your system, you can install Node either from source or as a pre-packaged bundle.
After installing Node, you should be able to run the following command to install development tools.
You will only need to run this command when dependencies change in [package.json](package.json).
npm install
We use npm scripts and [Webpack][] as our build system.
Run the following commands in two separate terminals to create a blissful development experience where your browser
auto-refreshes when files change on your hard drive.
./gradlew
npm start
Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by
specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies.
Add the `help` flag on any command to see how you can use it. For example, `npm help update`.
The `npm run` command will list all of the scripts available to run for this project.
### Service workers
Service workers are commented by default, to enable them please uncomment the following code.
- The service worker registering script in index.html
```html
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./service-worker.js').then(function() {
console.log('Service Worker Registered');
});
}
</script>
```
Note: workbox creates the respective service worker and dynamically generate the `service-worker.js`
### Managing dependencies
For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command:
npm install --save --save-exact leaflet
To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command:
npm install --save-dev --save-exact @types/leaflet
Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them:
Edit [src/main/webapp/app/vendor.ts](src/main/webapp/app/vendor.ts) file:
```
import 'leaflet/dist/leaflet.js';
```
Edit [src/main/webapp/content/css/vendor.css](src/main/webapp/content/css/vendor.css) file:
```
@import '~leaflet/dist/leaflet.css';
```
Note: there are still few other things remaining to do for Leaflet that we won't detail here.
For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][].
### Using angular-cli
You can also use [Angular CLI][] to generate some custom client code.
For example, the following command:
ng generate component my-component
will generate few files:
create src/main/webapp/app/my-component/my-component.component.html
create src/main/webapp/app/my-component/my-component.component.ts
update src/main/webapp/app/app.module.ts
### Doing API-First development using openapi-generator
[OpenAPI-Generator]() is configured for this application. You can generate API code from the `src/main/resources/swagger/api.yml` definition file by running:
```bash
./gradlew openApiGenerate
```
Then implements the generated delegate classes with `@Service` classes.
To edit the `api.yml` definition file, you can use a tool such as [Swagger-Editor](). Start a local instance of the swagger-editor using docker by running: `docker-compose -f src/main/docker/swagger-editor.yml up -d`. The editor will then be reachable at [http://localhost:7742](http://localhost:7742).
Refer to [Doing API-First development][] for more details.
## Building for production
To optimize the hsadminNg application for production, run:
./gradlew -Pprod clean bootWar
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
To ensure everything worked, run:
java -jar build/libs/*.war
Then navigate to [http://localhost:8080](http://localhost:8080) in your browser.
Refer to [Using JHipster in production][] for more details.
## Testing
To launch your application's tests, run:
./gradlew test
### Client tests
Unit tests are run by [Jest][] and written with [Jasmine][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with:
npm test
For more information, refer to the [Running tests page][].
### Code quality
Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with:
```
docker-compose -f src/main/docker/sonar.yml up -d
```
Then, run a Sonar analysis:
```
./gradlew -Pprod clean test sonarqube
```
For more information, refer to the [Code quality page][].
## Using Docker to simplify development (optional)
You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services.
For example, to start a postgresql database in a docker container, run:
docker-compose -f src/main/docker/postgresql.yml up -d
To stop it and remove the container, run:
docker-compose -f src/main/docker/postgresql.yml down
You can also fully dockerize your application and all the services that it depends on.
To achieve this, first build a docker image of your app by running:
./gradlew bootWar -Pprod jibDockerBuild
Then run:
docker-compose -f src/main/docker/app.yml up -d
For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications.
## Continuous Integration (optional)
To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information.
[jhipster homepage and latest documentation]: https://www.jhipster.tech
[jhipster 5.8.2 archive]: https://www.jhipster.tech/documentation-archive/v5.8.2
[using jhipster in development]: https://www.jhipster.tech/documentation-archive/v5.8.2/development/
[using docker and docker-compose]: https://www.jhipster.tech/documentation-archive/v5.8.2/docker-compose
[using jhipster in production]: https://www.jhipster.tech/documentation-archive/v5.8.2/production/
[running tests page]: https://www.jhipster.tech/documentation-archive/v5.8.2/running-tests/
[code quality page]: https://www.jhipster.tech/documentation-archive/v5.8.2/code-quality/
[setting up continuous integration]: https://www.jhipster.tech/documentation-archive/v5.8.2/setting-up-ci/
[node.js]: https://nodejs.org/
[yarn]: https://yarnpkg.org/
[webpack]: https://webpack.github.io/
[angular cli]: https://cli.angular.io/
[browsersync]: http://www.browsersync.io/
[jest]: https://facebook.github.io/jest/
[jasmine]: http://jasmine.github.io/2.0/introduction.html
[protractor]: https://angular.github.io/protractor/
[leaflet]: http://leafletjs.com/
[definitelytyped]: http://definitelytyped.org/
[openapi-generator]: https://openapi-generator.tech
[swagger-editor]: http://editor.swagger.io
[doing api-first development]: https://www.jhipster.tech/documentation-archive/v5.8.2/doing-api-first-development/

47
Jenkinsfile vendored
View File

@ -1,47 +0,0 @@
#!/usr/bin/env groovy
node {
withEnv(["PATH=$HOME/bin:$PATH"]) {
stage('checkout') {
checkout scm
}
stage('check java') {
sh "java -version"
}
stage('clean') {
sh "chmod +x gradlew"
sh "./gradlew clean --no-daemon"
}
stage('npm install') {
sh "./gradlew npm_install -PnodeInstall --no-daemon"
}
stage('backend tests') {
try {
sh "./gradlew test -PnodeInstall --no-daemon"
} catch (err) {
throw err
} finally {
junit '**/build/**/TEST-*.xml'
}
}
stage('frontend tests') {
try {
sh "./gradlew npm_run_test -PnodeInstall --no-daemon"
} catch (err) {
throw err
} finally {
junit '**/build/test-results/TESTS-*.xml'
}
}
stage('packaging') {
sh "./gradlew bootWar -x test -Pprod -PnodeInstall --no-daemon"
archiveArtifacts artifacts: '**/build/libs/*.war', fingerprint: true
}
}
}

26
LICENSE.md Normal file
View File

@ -0,0 +1,26 @@
The MIT License (MIT)
=====================
Copyright ©2022 Michael Hönnig
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

763
README.md
View File

@ -1,21 +1,762 @@
= hsadminNg Development
# hsadminNg Development
== Setting up the Development Environment
This documents gives an overview of the development environment and tools.
For architecture consider the files in the `doc` and `adr` folder.
You'll often need to execute `./gradlew`, therefore we suggest to define this alias:
<!-- generated TOC begin: -->
- [Setting up the Development Environment](#setting-up-the-development-environment)
- [PostgreSQL Server](#postgresql-server)
- [Markdown](#markdown)
- [Render Markdown embedded PlantUML](#render-markdown-embedded-plantuml)
- [Render Markdown Embedded Mermaid Diagrams](#render-markdown-embedded-mermaid-diagrams)
- [IDE Specific Settings](#ide-specific-settings)
- [IntelliJ IDEA](#intellij-idea)
- [Other Tools](#other-tools)
- [Running the SQL files](#running-the-sql-files)
- [For RBAC](#for-rbac)
- [For Historization](#for-historization)
- [Coding Guidelines](#coding-guidelines)
- [Directory and Package Structure](#directory-and-package-structure)
- [General Directory Structure](#general-directory-structure)
- [Source Code Package Structure](#source-code-package-structure)
- [Run Tests from Command Line](#run-tests-from-command-line)
- [Spotless Code Formatting](#spotless-code-formatting)
- [JaCoCo Test Code Coverage Check](#jacoco-test-code-coverage-check)
- [PiTest Mutation Testing](#pitest-mutation-testing)
- [Remark](#remark)
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
- [Dependency-License-Compatibility](#dependency-license-compatibility)
- [Dependency Version Upgrade](#dependency-version-upgrade)
- [How To ...](#how-to-...)
- [How to Configure .pgpass for the Default PostgreSQL Database?](#how-to-configure-.pgpass-for-the-default-postgresql-database?)
- [How to Run the Tests Against a Local User-Space Podman Daemon?](#how-to-run-the-tests-against-a-local-user-space-podman-daemon?)
- [Install and Run Podman](#install-and-run-podman)
- [Use the Command Line to Run the Tests Against the Podman Daemon ](#use-the-command-line-to-run-the-tests-against-the-podman-daemon-)
- [Use IntelliJ IDEA Run the Tests Against the Podman Daemon](#use-intellij-idea-run-the-tests-against-the-podman-daemon)
- [~/.testcontainers.properties](#~/.testcontainers.properties)
- [How to Run the Tests Against a Remote Podman or Docker Daemon?](#how-to-run-the-tests-against-a-remote-podman-or-docker-daemon?)
- [How to Run the Application on a Different Port?](#how-to-run-the-application-on-a-different-port?)
- [How to Use a Persistent Database for Integration Tests?](#how-to-use-a-persistent-database-for-integration-tests?)
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
- [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?)
- [How to Generate Database Table Diagrams?](#how-to-generate-database-table-diagrams?)
- [Further Documentation](#further-documentation)
<!-- generated TOC end. -->
alias gw='./gradlew'
## Setting up the Development Environment
== Building the Application with Test Execution
All instructions assume that you're using a current _Linux_ or _MacOS_ operating system.
Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
gw build
To be able to build and run the Java Spring Boot application, you need the following tools:
== Starting the Application
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar) or Podman
- optionally: PostgreSQL Server 15.5-bookworm
(see instructions below to install and run in Docker)
- The matching Java JDK at will be automatically installed by Gradle toolchain support to `~/.gradle/jdks/`.
- You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
Either simply:
If you have at least Docker and the Java JDK installed in appropriate versions and in your `PATH`, then you can start like this:
gw bootRun
cd your-hsadmin-ng-directory
source .aliases # creates some comfortable bash aliases, e.g. 'gw'='./gradlew'
gw # initially downloads the configured Gradle version into the project
or with a specific port:
gw test # compiles and runs unit- and integration-tests
# if the container has not been built yet, run this:
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
# if the container has been built already, run this:
pg-sql-start
SERVER_PORT=8081 ./gradlew bootRun
gw bootRun # compiles and runs the application on localhost:8080
# the following command should reply with "pong":
curl http://localhost:8080/api/ping
# the following command should return a JSON array with just all customers:
curl \
-H 'current-subject: superuser-alex@hostsharing.net' \
http://localhost:8080/api/test/customers
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
curl \
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: test_customer#yyy:ADMIN' \
http://localhost:8080/api/test/packages
# add a new customer
curl \
-H 'current-subject: superuser-alex@hostsharing.net' -H "Content-Type: application/json" \
-d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
-X POST http://localhost:8080/api/test/customers
If you wonder who 'superuser-alex@hostsharing.net' and 'superuser-fran@hostsharing.net' are and where the data comes from:
Mike and Sven are just example global admin accounts as part of the example data which is automatically inserted in Testcontainers and Development environments.
Also try for example 'admin@xxx.example.com' or 'unknown@example.org'.
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html.
If you still need to install some of these tools, find some hints in the next chapters.
### PostgreSQL Server
You could use any PostgreSQL Server (version 15) installed on your machine.
You might amend the port and user settings in `src/main/resources/application.yml`, though.
But the easiest way to run PostgreSQL is via Docker.
Initially, pull an image compatible to current PostgreSQL version of Hostsharing:
docker pull postgres:15.5-bookworm
<big>**&#9888;**</big>
If we switch the version, please also amend the documentation as well as the aliases file. Thanks!
Create and run a container with the given PostgreSQL version:
docker run --name hsadmin-ng-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres:15.5-bookworm
# or via alias:
pg-sql-run
To check if the PostgreSQL container is running, the following command should list a container with the name "hsadmin-ng-postgres":
docker container ls
Stop the PostgreSQL container:
docker stop hsadmin-ng-postgres
# or via alias: pg-sql-stop
Start the PostgreSQL container again:
docker container start hsadmin-ng-postgres
# or via alias: pg-sql-start
Remove the PostgreSQL container:
docker rm hsadmin-ng-postgres
# or via alias:
pg-sql-remove
To reset to a clean database, use:
pg-sql-stop; pg-sql-remove; pg-sql-run
# or via alias:
pg-sql-reset
After the PostgreSQL container is removed, you need to create it again as shown in "Create and run ..." above.
Given the container is running, to create a backup in ~/backup, run:
docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean --create -U postgres postgres | gzip -9 > ~/backup/hsadmin-ng-postgres.sql.gz
# or via alias:
pg-sql-backup >~/backup/hsadmin-ng-postgres.sql.gz
Again, given the container is running, to restore the backup from ~/backup, run:
gunzip --stdout --keep ~/backup/hsadmin-ng-postgres.sql.gz | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres
# or via alias:
pg-sql-restore <~/backup/hsadmin-ng-postgres.sql.gz
### Markdown
To generate the TOC (Table of Contents), a little bash script from a
[Blog Article](https://medium.com/@acrodriguez/one-liner-to-generate-a-markdown-toc-f5292112fd14) was used.
Given this is in PATH as `md-toc`, use:
```shell
md-toc <README.md 2 4 | cut -c5-'
```
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
#### Render Markdown embedded PlantUML
Can you see the following diagram right in your IDE?
I mean a real graphic diagram, not just some markup code.
```plantuml
@startuml
me -> you: Can you see this diagram?
you -> me: Sorry, I don't :-(
me -> you: Install some tooling!
@enduml
```
If not, you need to install some tooling.
##### for IntelliJ IDEA (or derived products)
You just need the bundled Markdown plugin enabled and install and activate the PlantUML plugin in its [settings](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown).
You might also need to install Graphviz on your operating system.
For Debian-based Linux systems this might work:
```sh
sudo apt install graphviz
```
##### Ubuntu Linux command line
1. Install Pandoc with some extra libraries:
```shell
sudo apt-get install pandoc texlive-latex-base texlive-fonts-recommended texlive-extra-utils texlive-latex-extra pandoc-plantuml-filter
```
2. Install mermaid-filter, e.g. this way:
```shell
npm install -g mermaid-filter
```
3. Run Pandoc to generate a PDF from a Markdown file with PlantUML and Mermaid diagrams:
```shell
pandoc --filter mermaid-filter --filter pandoc-plantuml rbac.md -o rbac.pdf
```
##### for other IDEs / operating systems
If you have figured out how it works, please add instructions above this section.
#### Render Markdown Embedded Mermaid Diagrams
The source of RBAC role diagrams are much easier to read with Mermaid than with PlantUML or GraphViz, that's also the main reason Mermaid is used.
Can you see the following diagram right in your IDE?
I mean a real graphic diagram, not just some markup code.
@startuml
me -> you: Can you see this diagram?
you -> me: Sorry, I don't :-(
me -> you: Install some tooling!
@enduml
```mermaid
graph TD;
A[Can you see this diagram?];
A --> yes;
A --> no;
no --> F[Follow the instructions below!]
F --> yes
yes --> E[Then everything is fine.]
```
If not, you need to install some tooling.
##### for IntelliJ IDEA (or derived products)
1. Activate the bundled Jebrains Markdown PlantUML Extension via
[File | Settings | Languages & Frameworks | Markdown](jetbrains://idea/settings?name=Languages+%26+Frameworks--Markdown)
2. Install the Jetbrains Mermaid plugin: https://plugins.jetbrains.com/plugin/20146-mermaid, it also works embedded in Markdown files.
Now the above diagram should be rendered.
##### for other IDEs / command-line / operating systems
If you have figured out how it works, please add instructions above this section.
### IDE Specific Settings
#### IntelliJ IDEA
##### Build Settings
Go to [Gradle Settings}(jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Build+Tools--Gradle) and select "Build and run using" and "Run tests using" both to "gradle".
Otherwise, settings from `build.gradle`, like compiler arguments, are not applied when compiling through *IntelliJ IDEA*.
##### Annotation Processor
Go to [Annotations Processors](jetbrains://idea/settings?name=Build%2C+Execution%2C+Deployment--Compiler--Annotation+Processors) and activate annotation processing.
Otherwise, *IntelliJ IDEA* can't see *Lombok* generated classes
and will show false errors (missing identifiers).
##### Suggested Plugins
- [Jetbrains Mermaid Integration](https://plugins.jetbrains.com/plugin/20146-mermaid)
- [Vojtěch Krása PlantUML Integration](https://plugins.jetbrains.com/plugin/7017-plantuml-integration)
### Other Tools
**jq**: a JSON formatter.
On _Debian_'oid systems you can install it with `sudo apt-get install jq`.
On _MacOS_ you can install it with `brew install jq`, given you have _brew_ installed.
## Running the SQL files
### For RBAC
The Schema is automatically created via *Liquibase*, a database migration library.
Currently, also some test data is automatically created.
To increase the amount of test data, increase the number of generated customers in `2022-07-28-051-hs-customer.sql` and run that
If you already have data, e.g. for customers 0..999 (thus with reference numbers 10000..10999) and want to add another 1000 customers, amend the for loop to 1000...1999 and also uncomment and amend the `CONTINUE WHEN` or `WHERE` conditions in the other test data generators, using the first new customer reference number (in the example that's 11000).
### For Historization
The historization is not yet integrated into the *Liquibase*-scripts.
You can explore the prototype as follows:
- start with an empty database
(the example tables are currently not compatible with RBAC),
- then run `historization.sql` in the database,
- finally run `examples.sql` in the database.
## Coding Guidelines
### Directory and Package Structure
#### General Directory Structure
`.aliases`
Shell-aliases for common tasks.
`build/`
Output directory for gradle build results. Ignored by git.
`build.gradle`
Gradle build-file. Contains dependencies and build configurations.
`doc/`
Contains project documentation.
`.editorconfig`
Rules for indentation etc. considered by many code editors.
`etc/`
Miscellaneous configurations, as long as these don't need to be in the rood directory.
`.git/`
Git repository. Do not temper with this!
`.gitattributes`
Git configurations regarding text file format conversion between operating systems.
`.gitignore`
Git configuration regarding which files and directories should be ignored (not checked in).
`.gradle/`
Config files created by `gradle wrapper`. Ignored by git.
`gradle/`
The gradle distribution downloaded by `gradle wrapper`. Ignored by git.
`gradlew` and `gradlew.bat` use these batches to run gradle for builds etc.
`.idea/` (optional)
Config and cache files created by *IntelliJ IDEA*. Ignore by git.
`LICENSE.md`
Contains the license used for this software.
`out/` (optional)
Build output created by *IntelliJ IDEA". Ignored by git.
`README.md`
Contains an overview about how to build the project and the used tools.
`.run/` (optional)
Created by *IntelliJ IDEA* to contain run and debug configurations.
`settings.gradle`
Configuration file for gradle.
`sql/`
Contains SQL scripts for experiments and useful tasks.
Most of this will sooner or later be moved to Liquibase-scripts.
`src/`
The actual source-code, see [Source Code Package Structure](#source-code-package-structure) for details.
`tools/`
Some shell-scripts to useful tasks.
#### Source Code Package Structure
For the source code itself, the general standard Java directory structure is used, where productive and test code are separated like this:
```
src
main/
java/
net.hostsharing.hasadminng/
resources/
test/
java/
net.hostsharing.hasadminng/
resources/
```
The Java package structure below contains:
- config and global (utility) packages,
these should not access any other packages within the project
- rbac, containing all packages related to the RBAC subsystem
- hs, containing Hostsharing business object related packages
Underneath of rbac and hs, the structure is business oriented, NOT technical / layer -oriented.
Some of these rules are checked with *ArchUnit* unit tests.
### Run Tests from Command Line
Run all tests which have not yet been passed with the current source code:
```shell
gw test
```
Force running all tests:
```shell
gw cleanTest test
```
### Spotless Code Formatting
Code formatting for Java is checked via *spotless*.
The formatting style can be checked with this command:
```shell
gw spotlessCheck
```
This task is also included in `gw build` and `gw check`.
To apply formatting rules, use:
```shell
gw spotlessApply
```
### JaCoCo Test Code Coverage Check
This project uses the JaCoCo test code coverage report with limit checks.
It can be executed with:
```shell
gw jacocoTestReport
```
This task is also automatically run after `gw test`.
It is configured in [build.gradle](build.gradle).
A report is generated under [build/reports/jacoco/tests/test/index.html](./build/reports/jacoco/test/html/index.html).
Additionally, quality limits are checked via:
```shell
gw jacocoTestCoverageVerification
```
This task is also executed as part of `gw check`.
### PiTest Mutation Testing
PiTest mutation testing is configured for unit tests.
It can be executed with:
```shell
gw pitest
```
Classes to be scanned, tests to be executed and thresholds are configured in [build.gradle](build.gradle).
A report is generated under [build/reports/pitest/index.html](./build/reports/pitest/index.html).
A link to the report is also printed after the `pitest` run.
This task is also executed as part of `gw check`.
#### Remark
In this project, there is little business logic in *Java* code;
most business code is in *plsql*
and *Java* ist mostly used for mapping REST calls to database queries.
This mapping ist mostly done through *Spring* annotations and other implicit code.
Therefore, there are only few unit tests and thus mutation testing has limited value.
We'll see if this changes when the project progresses and more validations are added.
### OWASP Security Vulnerability Check
An OWASP security vulnerability is configured and can be utilized by running:
```shell
gw dependencyCheckAnalyze
```
This task is also included in `gw build` and `gw check`.
It is configured in [build.gradle](build.gradle).
Often vulnerability reports don't apply to our use cases.
Therefore, reports can be [suppressed](./etc/owasp-dependency-check-suppression.xml).
In case of suppression, a note must be added to explain why it does not apply to us.
See also: https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/index.html.
### Dependency-License-Compatibility
The `gw check` phase depends on a dependency-license-compatibility check.
If any dependency violates the configured [list of allowed licenses](etc/allowed-licenses.json), the build will fail.
New licenses can be added to that list after a legal investigation.
<big>**&#9888;**</big>
*GPL* (*GNU General Public License*) is only allowed with classpath exception.
Do <u>not</u> use any dependencies under *GPL* without this exception,
except if these offer an alternative license which is allowed.
*LGPL* (*GNU <u>Library</u> General Public License*) is also allowed.
To run just the dependency-license-compatibility check, use:
```shell
gw checkLicense
```
If the check fails, a report can be found here: The generated license can be found under [dependencies-without-allowed-license.json](/build/reports/dependency-license/dependencies-without-allowed-license.json).
And to generate a report, use:
```shell
gw generateLicenseReport
```
The generated license can be found here: [index.html](build/reports/dependency-license/index.html).
More information can be found on the [project's website](https://github.com/jk1/Gradle-License-Report).
### Dependency Version Upgrade
Dependency versions can be automatically upgraded to the latest available version:
```shell
gw useLatestVersions
```
Afterwards, `gw check` is automatically started.
Please only commit+push to master if the check run shows no errors.
More infos, e.g. on blacklists see on the [project's website](https://github.com/patrikerdes/gradle-use-latest-versions-plugin).
## How To ...
### How to Configure .pgpass for the Default PostgreSQL Database?
To access the default database schema as used during development, add this line to your `.pgpass` file in your users home directory:
```
localhost:5432:postgres:postgres:password
```
Amend host and port if necessary.
### How to Run the Tests Against a Local User-Space Podman Daemon?
Using a normal Docker daemon running as root has some security issues.
As an alternative, this chapter shows how you can run a Podman daemon in user-space.
#### Install and Run Podman
You can find directions in [this project on Github](https://stackoverflow.com/questions/71549856/testcontainers-with-podman-in-java-tests)
Summary for Debian-based Linux systems:
1. Install Podman, e.g. like this:
```shell
sudo apt-get -y install podman
```
It is possible to move the storage directory to /tmp, e.g. to increase performance or to avoid issues with NFS mounted home directories:
```shell
cat .config/containers/storage.conf
[storage]
driver = "vfs"
graphRoot = "/tmp/containers/storage"
```
2. Then start it like this:
```shell
systemctl --user enable --now podman.socket
systemctl --user status podman.socket
ls -la /run/user/$UID/podman/podman.sock
```
These commands are also available in `.aliases` as `podman-start`.
#### Use the Command Line to Run the Tests Against the Podman Daemon
1. In a local shell. in which you want to run the tests, set some environment variables:
```shell
export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"
export TESTCONTAINERS_RYUK_DISABLED=true
```
These commands are also available in `.aliases` as `podman-use`.
Disabling RYUK is necessary, because it's not supported by Podman.
Supposedly this means that containers are not properly cleaned up after test runs,
but I could not see any remaining containers after test runs.
If we are running into problems with stale containers,
we need to register a shutdown-hook in the test source code.
2. Now You Can Run the Tests
```shell
gw test # gw is from the .aliases file
```
#### Use IntelliJ IDEA Run the Tests Against the Podman Daemon
To run the tests against a Podman Daemon in IntelliJ IDEA too, you also need to set the environment variables `DOCKER_HOST` and `TESTCONTAINERS_RYUK_DISABLED` as show above.
This can either be done in the environment from which IDEA is started.
Or you can use the run config template for gradle to set these variables:
![IntelliJ IDEA Gradle Run Template](./doc/.images/intellij-idea-gradle-run-template.png)
If you already have Gradle run configs, you need to delete them, so they get re-created from the template.
Alternatively you need to add the environment varibles here too:
![IntelliJ IDEA Gradle Run Config Example](./doc/.images/intellij-idea-gradle-run-config.png)
Find more information [here](https://www.jetbrains.com/help/idea/run-debug-configuration.html).
#### ~/.testcontainers.properties
It should be possible to set these environment variables in `~/.testcontainers.properties`,
but it did not work so far.
Maybe a problem with quoting.
If you manage to make it work, please amend this documentation, thanks.
### How to Run the Tests Against a Remote Podman or Docker Daemon?
1. On the remote host, you need to have a Podman or Docker daemon running on a port accessible from the Internet.
Probably, you want to protect it with a VPN, but that's not part of this documentation.
e.g. to make Podman listen to a port, run this:
```shell
podman system service -t 0 tcp:HOST:PORT # please replace HOST+PORT
```
2. In a local shell. in which you want to run the tests, set some environment variables:
```shell
export DOCKER_HOST=tcp://HOST:PORT # please replace HOST+PORT again
export TESTCONTAINERS_RYUK_DISABLED=true # only for Podman
```
Regarding RYUK, see also in the directions for a locally running Podman, above.
3. Now you can run the tests:
```shell
gw clean test # gw is from the .aliases file
```
For information about how to run the tests in IntelliJ IDEA against a remote Podman daemon, see also in the chapter above just with the HOST:PORT-based DOCKER_HOST.
### How to Run the Application on a Different Port?
By default, `gw bootRun` starts the application on port 8080.
This port can be changed in
[src/main/resources/application.yml](src/main/resources/application.yml) through the property `server.port`.
### How to Use a Persistent Database for Integration Tests?
Usually, the `DataJpaTest` integration tests run against a database in a temporary docker container.
As soon as the test ends, the database is gone; this might make debugging difficult.
Alternatively, a persistent database could be used by amending the
[resources/application.yml](src/main/resources/application.yml) through the property `spring.datasource.url` in [src/test/resources/application.yml](src/test/resources/application.yml) , e.g. to the JDBC-URL from [src/main/resources/application.yml](src/main/resources/application.yml).
If the persistent database and the temporary database show different results, one of these reasons could be the cause:
1. You might have some changesets only running in either context,
check the `context: ...` in the changeset control lines.
2. You might have changes in the database which interfere with the tests,
e.g. from a previous run of tests or manually applied.
It's best to run `pg-sql-reset && gw bootRun` before each test run, to have a clean database.
### How to Amend Liquibase SQL Changesets?
Liquibase changesets are meant to be immutable and based on each other.
That means, once a changeset is written, it never changes, not even a whitespace or comment.
Liquibase is a *database migration tool*, not a *database initialization tool*.
This, if you need to add change a table, stored procedure or whatever,
create a new changeset and apply `ALTER`, `DROP`, `CREATE OR REPLACE` or whatever SQL commands to perform your changes.
These changes will be automatically applied once the application starts up again.
This way, any staging or production database will always match the application code.
But, during initial development that can be a big hassle because the database structure changes a lot in that stage.
Also, the actual structure of the database won't be easily recognized anymore through lots of migration changesets.
Therefore, during initial development, it's good approach just to amend the existing changesets and delete the database:
```shell
pg-sql-reset
gw bootRun
```
<big>**&#9888;**</big>
Just don't forget switching to the migration mode, once there is a production database!
### How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?
The API is described as OpenAPI specifications in `src/main/resources/api-definition/`.
Once generated, the interfaces for the Spring-Controllers can be found in `build/generated/sources/openapi`.
These interfaces have to be implemented by subclasses named `*Controller`.
All gradle tasks which need the generated interfaces depend on the Gradle task `openApiGenerate` which controls the code generation.
It can also be executed directly:
```shell
gw openApiGenerate
```
### How to Generate Database Table Diagrams?
Some overview documentation about the database can be generated via [postgresql_autodoc](https://github.com/cbbrowne/autodoc").
To make it easier, the command line is included in the `.aliases`, just call:
```shell
postgres-autodoc
```
The output will list the generated files.
## Further Documentation
- the `doc` directory contains architecture concepts and a glossary
- the `ideas` directory contains unstructured ideas for future development or documentation

View File

@ -1,39 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"hsadmin-ng": {
"root": "",
"sourceRoot": "src/main/webapp",
"projectType": "application",
"architect": {}
}
},
"defaultProject": "hsadmin-ng",
"cli": {
"packageManager": "npm"
},
"schematics": {
"@schematics/angular:component": {
"inlineStyle": true,
"inlineTemplate": false,
"spec": false,
"prefix": "jhi",
"styleExt": "css"
},
"@schematics/angular:directive": {
"spec": false,
"prefix": "jhi"
},
"@schematics/angular:guard": {
"spec": false
},
"@schematics/angular:pipe": {
"spec": false
},
"@schematics/angular:service": {
"spec": false
}
}
}

View File

@ -0,0 +1,36 @@
#!/bin/bash
# get the current branch name
BRANCH=$(git rev-parse --abbrev-ref HEAD)
while true; do
# get the latest commit hashes from origin and local
git fetch origin
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse origin/$BRANCH)
# check if the local branch differs from the remote branch
if [ "$LOCAL" != "$REMOTE" ]; then
echo "local $LOCAL differs from remote $REMOTE => pulling changes from origin"
git pull origin $BRANCH
# run the command
echo "Running ./gradlew test"
source .aliases # only variables, aliases are not expanded in scripts
./gradlew test
fi
# wait 10s with a little animation
echo -e -n " waiting for changes (/) ..."
sleep 2
echo -e -n "\r\033[K waiting for changes (-) ..."
sleep 2
echo -e -n "\r\033[K waiting for changes (\) ..."
sleep 2
echo -e -n "\r\033[K waiting for changes (|) ..."
sleep 2
echo -e -n "\r\033[K waiting for changes ( ) ... "
sleep 2
echo -e -n "\r\033[K"
done

View File

@ -1,291 +1,387 @@
import org.gradle.internal.os.OperatingSystem
buildscript {
repositories {
mavenLocal()
mavenCentral()
maven { url "http://repo.spring.io/plugins-release" }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${spring_boot_version}"
classpath "io.spring.gradle:propdeps-plugin:0.0.10.RELEASE"
classpath "org.openapitools:openapi-generator-gradle-plugin:3.3.0"
classpath "gradle.plugin.com.gorylenko.gradle-git-properties:gradle-git-properties:1.5.2"
//jhipster-needle-gradle-buildscript-dependency - JHipster will add additional gradle build script plugins here
}
dependencies {
classpath 'org.owasp:dependency-check-gradle:4.0.2'
}
}
plugins {
id "org.sonarqube" version "2.6.2"
id "net.ltgt.apt-eclipse" version "0.19"
id "net.ltgt.apt-idea" version "0.19"
id "net.ltgt.apt" version "0.19"
id "io.spring.dependency-management" version "1.0.6.RELEASE"
id "com.moowork.node" version "1.2.0"
id 'org.liquibase.gradle' version '2.0.1'
//jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
id 'java'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
id 'io.openapiprocessor.openapi-processor' version '2023.2'
id 'com.github.jk1.dependency-license-report' version '2.6'
id "org.owasp.dependencycheck" version "9.0.10"
id "com.diffplug.spotless" version "6.25.0"
id 'jacoco'
id 'info.solidsoft.pitest' version '1.15.0'
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
id 'com.github.ben-manes.versions' version '0.51.0'
}
apply plugin: 'java'
apply plugin: 'org.owasp.dependencycheck'
sourceCompatibility=1.8
targetCompatibility=1.8
// Until JHipster supports JDK 9
assert System.properties['java.specification.version'] == '1.8'
apply plugin: 'maven'
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
apply plugin: 'propdeps'
apply plugin: 'com.moowork.node'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'idea'
idea {
module {
excludeDirs += files('node_modules')
}
}
dependencyManagement {
imports {
mavenBom 'io.github.jhipster:jhipster-dependencies:' + jhipster_dependencies_version
//jhipster-needle-gradle-dependency-management - JHipster will add additional dependencies management here
}
}
defaultTasks 'bootRun'
group = 'org.hostsharing.hsadminng'
group = 'net.hostsharing'
version = '0.0.1-SNAPSHOT'
description = ''
bootWar {
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
}
war {
webAppDirName = 'build/www/'
enabled = true
extension = 'war.original'
}
springBoot {
mainClassName = 'org.hostsharing.hsadminng.HsadminNgApp'
}
if (OperatingSystem.current().isWindows()) {
// https://stackoverflow.com/questions/40037487/the-filename-or-extension-is-too-long-error-using-gradle
task classpathJar(type: Jar) {
dependsOn configurations.runtime
appendix = 'classpath'
doFirst {
manifest {
attributes 'Class-Path': configurations.runtime.files.collect {
it.toURI().toURL().toString().replaceFirst(/file:\/+/, '/').replaceAll(' ', '%20')
}.join(' ')
}
}
}
bootRun {
dependsOn classpathJar
doFirst {
classpath = files("$buildDir/classes/java/main", "$buildDir/resources/main", classpathJar.archivePath)
}
}
}
test {
exclude '**/CucumberTest*'
// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = false
}
task cucumberTest(type: Test) {
description = "Execute cucumber BDD tests."
group = "verification"
include '**/CucumberTest*'
// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = false
}
check.dependsOn cucumberTest
task testReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn test
}
task cucumberTestReport(type: TestReport) {
destinationDir = file("$buildDir/reports/tests")
reportOn cucumberTest
}
apply from: 'gradle/docker.gradle'
apply from: 'gradle/sonar.gradle'
apply from: 'gradle/swagger.gradle'
//jhipster-needle-gradle-apply-from - JHipster will add additional gradle scripts to be applied here
if (project.hasProperty('prod')) {
apply from: 'gradle/profile_prod.gradle'
} else {
apply from: 'gradle/profile_dev.gradle'
}
if (!project.hasProperty('runList')) {
project.ext.runList = 'main'
}
project.ext.diffChangelogFile = 'src/main/resources/config/liquibase/changelog/' + new Date().format('yyyyMMddHHmmss') + '_changelog.xml'
liquibase {
activities {
main {
driver ''
url ''
username 'hsadminNg'
password ''
changeLogFile 'src/main/resources/config/liquibase/master.xml'
defaultSchemaName ''
logLevel 'debug'
classpath 'src/main/resources/'
}
diffLog {
driver ''
url ''
username 'hsadminNg'
password ''
changeLogFile project.ext.diffChangelogFile
referenceUrl 'hibernate:spring:org.hostsharing.hsadminng.domain?dialect=&amp;hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&amp;hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy'
defaultSchemaName ''
logLevel 'debug'
classpath "$buildDir/classes/java/main"
}
}
runList = project.ext.runList
wrapper {
distributionType = Wrapper.DistributionType.BIN
gradleVersion = '8.5'
}
configurations {
providedRuntime
compile.exclude module: "spring-boot-starter-tomcat"
compileOnly {
extendsFrom annotationProcessor
}
testCompile {
extendsFrom testAnnotationProcessor
// Only JUNit 5 (Jupiter) should be used at compile time.
// For runtime it's still needed by testcontainers, though.
exclude group: 'junit', module: 'junit'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
//jhipster-needle-gradle-repositories - JHipster will add additional repositories
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
vendor = JvmVendorSpec.ADOPTIUM
implementation = JvmImplementation.VENDOR_SPECIFIC
}
}
ext {
set('testcontainersVersion', "1.17.3")
}
dependencies {
// Use ", version: jhipster_dependencies_version, changing: true" if you want
// to use a SNAPSHOT release instead of a stable release
compile group: "io.github.jhipster", name: "jhipster-framework"
compile "org.springframework.boot:spring-boot-starter-cache"
compile "io.dropwizard.metrics:metrics-core"
compile 'io.micrometer:micrometer-registry-prometheus'
compile "net.logstash.logback:logstash-logback-encoder"
compile "com.fasterxml.jackson.datatype:jackson-datatype-hppc"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
compile "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
compile "com.fasterxml.jackson.core:jackson-annotations"
compile "com.fasterxml.jackson.core:jackson-databind"
compile "com.fasterxml.jackson.module:jackson-module-afterburner"
compile "javax.cache:cache-api"
compile "org.hibernate:hibernate-core"
compile "com.zaxxer:HikariCP"
compile "org.apache.commons:commons-lang3"
compile "commons-io:commons-io"
compile "javax.transaction:javax.transaction-api"
compile "org.ehcache:ehcache"
compile "org.hibernate:hibernate-entitymanager"
compile "org.hibernate:hibernate-envers"
compile "org.hibernate.validator:hibernate-validator"
compile "org.liquibase:liquibase-core"
compile "com.mattbertolini:liquibase-slf4j"
liquibaseRuntime "org.liquibase:liquibase-core"
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate5:${liquibase_hibernate5_version}"
liquibaseRuntime sourceSets.main.compileClasspath
compile "org.springframework.boot:spring-boot-loader-tools"
compile "org.springframework.boot:spring-boot-starter-mail"
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-aop"
compile "org.springframework.boot:spring-boot-starter-data-jpa"
compile "org.springframework.boot:spring-boot-starter-security"
compile ("org.springframework.boot:spring-boot-starter-web") {
exclude module: 'spring-boot-starter-tomcat'
}
compile "org.springframework.boot:spring-boot-starter-undertow"
compile "org.springframework.boot:spring-boot-starter-thymeleaf"
compile "org.zalando:problem-spring-web:0.24.0-RC.0"
compile "org.springframework.boot:spring-boot-starter-cloud-connectors"
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-data"
compile "org.springframework.security:spring-security-web"
compile "io.jsonwebtoken:jjwt-api"
runtime "io.jsonwebtoken:jjwt-impl"
runtime "io.jsonwebtoken:jjwt-jackson"
compile ("io.springfox:springfox-swagger2") {
exclude module: 'mapstruct'
}
compile "io.springfox:springfox-bean-validators"
compile "org.postgresql:postgresql"
liquibaseRuntime "org.postgresql:postgresql"
compile "org.mapstruct:mapstruct-jdk8:${mapstruct_version}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstruct_version}"
annotationProcessor "org.hibernate:hibernate-jpamodelgen"
annotationProcessor ("org.springframework.boot:spring-boot-configuration-processor") {
exclude group: 'com.vaadin.external.google', module: 'android-json'
}
testCompile "com.jayway.jsonpath:json-path"
testCompile "io.cucumber:cucumber-junit"
testCompile "io.cucumber:cucumber-spring"
testCompile ("org.springframework.boot:spring-boot-starter-test") {
exclude group: 'com.vaadin.external.google', module: 'android-json'
}
testCompile "org.springframework.security:spring-security-test"
testCompile "org.springframework.boot:spring-boot-test"
testCompile "org.assertj:assertj-core"
testCompile "junit:junit"
testCompile "org.mockito:mockito-core"
testCompile "com.mattbertolini:liquibase-slf4j"
testCompile "org.hamcrest:hamcrest-library"
testCompile "com.h2database:h2"
liquibaseRuntime "com.h2database:h2"
//jhipster-needle-gradle-dependency - JHipster will add additional dependencies here
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1'
implementation 'org.springdoc:springdoc-openapi:2.4.0'
implementation 'org.postgresql:postgresql:42.7.3'
implementation 'org.liquibase:liquibase-core:4.27.0'
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.commons:commons-text:1.11.0'
implementation 'net.java.dev.jna:jna:5.8.0'
implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
implementation 'org.reflections:reflections:0.9.12'
compileOnly 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.2.1'
testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.hamcrest:hamcrest-core:2.2'
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
}
task cleanResources(type: Delete) {
delete 'build/resources'
}
wrapper {
gradleVersion = '4.10.2'
}
task stage(dependsOn: 'bootWar') {
}
if (project.hasProperty('nodeInstall')) {
node {
version = "${node_version}"
npmVersion = "${npm_version}"
yarnVersion = "${yarn_version}"
download = false
dependencyManagement {
imports {
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
// Java Compiler Options
tasks.withType(JavaCompile) {
options.compilerArgs += [
"-parameters" // keep parameter names => no need for @Param for SpringData
]
}
// Configure tests
tasks.named('test') {
useJUnitPlatform()
jvmArgs '-Duser.language=en'
jvmArgs '-Duser.country=US'
}
// OpenAPI Source Code Generation
openapiProcessor {
springRoot {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition.yaml"
mapping "$projectDir/src/main/resources/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
springRbac {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml"
mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
springTest {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml"
mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
springHsOffice {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml"
mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
springHsBooking {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/hs-booking/hs-booking.yaml"
mapping "$projectDir/src/main/resources/api-definition/hs-booking/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
springHsHosting {
processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml"
mapping "$projectDir/src/main/resources/api-definition/hs-hosting/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
}
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
abstract class ProcessSpring extends DefaultTask {}
tasks.register('processSpring', ProcessSpring)
['processSpringRoot',
'processSpringRbac',
'processSpringTest',
'processSpringHsOffice',
'processSpringHsBooking',
'processSpringHsHosting'
].each {
project.tasks.processSpring.dependsOn it
}
project.tasks.processResources.dependsOn processSpring
project.tasks.compileJava.dependsOn processSpring
// Rename javax to jakarta in OpenApi generated java files because
// io.openapiprocessor.openapi-processor 2022.5 does not yet support the openapiprocessor useSpringBoot3 config option.
// TODO.impl: Upgrade to io.openapiprocessor.openapi-processor >= 2024.2
// and use either `bean-validation: true` in api-mapping.yaml or `useSpringBoot3 true` (not sure where exactly).
task openApiGenerate(type: Copy) {
from "$buildDir/generated/sources/openapi-javax"
into "$buildDir/generated/sources/openapi"
filter { line -> line.replaceAll('javax', 'jakarta') }
}
compileJava.source "$buildDir/generated/sources/openapi"
compileJava.dependsOn openApiGenerate
openApiGenerate.dependsOn processSpring
// Spotless Code Formatting
spotless {
java {
removeUnusedImports()
indentWithSpaces(4)
endWithNewline()
toggleOffOn()
target fileTree(rootDir) {
include '**/*.java'
exclude '**/generated/**/*.java'
}
}
}
project.tasks.check.dependsOn(spotlessCheck)
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
project.tasks.spotlessJava.dependsOn(
tasks.generateLicenseReport,
tasks.pitest,
tasks.jacocoTestReport,
tasks.processResources,
tasks.processTestResources)
// OWASP Dependency Security Test
dependencyCheck {
nvd {
apiKey = project.properties['OWASP_API_KEY'] // set it in ~/.gradle/gradle.properties
delay = 16000
}
format = 'ALL'
suppressionFile = 'etc/owasp-dependency-check-suppression.xml'
failOnError = true
failBuildOnCVSS = 5
}
project.tasks.check.dependsOn(dependencyCheckAnalyze)
project.tasks.dependencyCheckAnalyze.doFirst { // Why not doLast? See README.md!
println "OWASP Dependency Security Report: file:///${project.rootDir}/build/reports/dependency-check-report.html"
}
// License Check
licenseReport {
excludeBoms = true
allowedLicensesFile = new File("$projectDir/etc/allowed-licenses.json")
}
project.tasks.check.dependsOn(checkLicense)
// JaCoCo Test Code Coverage
jacoco {
toolVersion = "0.8.10"
}
test {
finalizedBy jacocoTestReport // generate report after tests
excludes = [
'net.hostsharing.hsadminng.**.generated.**',
]
useJUnitPlatform {
excludeTags 'import'
}
}
jacocoTestReport {
dependsOn test
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
"net/hostsharing/hsadminng/**/generated/**/*.class",
"net/hostsharing/hsadminng/hs/HsadminNgApplication.class"
])
}))
}
doFirst { // Why not doLast? See README.md!
println "HTML Jacoco Test Code Coverage Report: file://${reports.html.outputLocation.get()}/index.html"
}
}
project.tasks.check.dependsOn(jacocoTestCoverageVerification)
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.92
}
}
// element: PACKAGE, BUNDLE, CLASS, SOURCEFILE or METHOD
// counter: INSTRUCTION, BRANCH, LINE, COMPLEXITY, METHOD, or CLASS
// value: TOTALCOUNT, COVEREDCOUNT, MISSEDCOUNT, COVEREDRATIO or MISSEDRATIO
rule {
element = 'CLASS'
excludes = [
'net.hostsharing.hsadminng.**.generated.**',
'net.hostsharing.hsadminng.HsadminNgApplication',
'net.hostsharing.hsadminng.ping.PingController',
'net.hostsharing.hsadminng.mapper.Mapper'
]
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.98
}
}
rule {
element = 'METHOD'
excludes = [
'net.hostsharing.hsadminng.**.generated.**',
'net.hostsharing.hsadminng.HsadminNgApplication.main',
'net.hostsharing.hsadminng.ping.PingController.*'
]
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 1.00
}
}
}
}
tasks.register('importOfficeData', Test) {
useJUnitPlatform {
includeTags 'importOfficeData'
}
group 'verification'
description 'run the import jobs as tests'
mustRunAfter spotlessJava
}
tasks.register('importHostingAssets', Test) {
useJUnitPlatform {
includeTags 'importHostingAssets'
}
group 'verification'
description 'run the import jobs as tests'
mustRunAfter spotlessJava
}
// pitest mutation testing
pitest {
targetClasses = ['net.hostsharing.hsadminng.**']
excludedClasses = [
'net.hostsharing.hsadminng.config.**',
'net.hostsharing.hsadminng.**.*Controller',
'net.hostsharing.hsadminng.**.generated.**'
]
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
pitestVersion = '1.15.3'
junit5PluginVersion = '1.1.0'
threads = 4
// As Java unit tests are pretty pointless in our case, this maybe makes not much sense.
mutationThreshold = 71
coverageThreshold = 57
testStrengthThreshold = 87
outputFormats = ['XML', 'HTML']
timestampedReports = false
}
project.tasks.check.dependsOn(project.tasks.pitest)
project.tasks.pitest.doFirst { // Why not doLast? See README.md!
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
}
// Dependency Versions Upgrade
useLatestVersions {
finalizedBy check
}
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r)?$/
return !stableKeyword && !(version ==~ regex)
}
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(it.candidate.version)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

15
doc/adr-concept.md Normal file
View File

@ -0,0 +1,15 @@
## ADR-Concept
This project uses ADRs (Architecture Decision Records), see also https://adr.github.io/.
There is a template available under [0000-00-00.adr-tempate.md](./0000-00-00.adr-tempate.md).
It's suggested to write an ADR if any of these is true:
- an architectural decision is hard to change,
- there is a dispute about an architectural decision,
- some unusual architectural decision was made (e.g. unusual library),
- some deeper investigation was necessary before the decision.
ADRs should not be written for minor decisions with limited impact.

View File

@ -0,0 +1,39 @@
# TITLE
**Status:**
- [ ] proposed by (Proposer)
- [ ] accepted by (Participants)
- [ ] rejected by (Participants)
- [ ] superseded by (superseding ADR)
## Context and Problem Statement
A short description, why and under which circumstances this decision had to be made.
### Technical Background
Some details about the technical challenge.
## Considered Options
* OPTION-1
* OPTION-...
### OPTION-n
A short overview about the option.
#### Advantages
A list of advantages.
#### Disadvantages
A list of disadvantages.
## Decision Outcome
Which option was chose and why.

View File

@ -0,0 +1,160 @@
# Use VIEWs with JOIN into Permission-Assignments for Row-Level-Security
**Status:**
- [x] proposed by Michael Hönnig
- [ ] accepted by (Participants)
- [ ] rejected by (Participants)
- [ ] superseded by (superseding ADR)
## Context and Problem Statement
We need to decide how to apply the access rules defined in our RBAC system to the visibility of table rows for the accessing user.
The core problem here is, that in our RBAC system, determining the permissions of the accessing user has to consider a hierarchy of roles.
### Technical Background
The session variable `hsadminng.currentSubject` contains the accessing (domain-level) user, which is unrelated to the PostgreSQL user).
Given is a stored function `isPermissionGrantedToSubject` which detects if the accessing subject has a given permission (e.g. 'view').
Given is also a stored function `queryAllPermissionsOfSubjectId` which returns the flattened view to all permissions assigned to the given accessing user.
In the following code snippets `customer` is just an example domain table.
## Considered Options
* Perform Visibility-Checks programmatically in the Backend
* Add Visibility-Checks in the Backend
* POLICY with ENABLE ROW LEVEL SECURITY
* VIEW-RULE with ON SELECT DO INSTEAD
* VIEW with JOIN into Flattened Permissions
### Perform Visibility-Checks programmatically in the Backend
In this solution, the database ignores row level visibility and returns all rows which match a given query. Afterwards, the result is filtered programmatically with Java-code in the backend.
#### Advantages
Very flexible access, programmatic, rules could be implemented.
The role-hierarchy and permissions for current subjects (e.g. logged-in users) could be cached in the backend.
The access logic can be tested in pure Java unit tests.
At least regarding this aspect, an in-memory database could be used for integration testing; though the recursive Role-evaluation uses PostgreSQL features anyway.
#### Disadvantages
It's inefficient when initial query is not very restrictive, e.g. as on overview pages in a frontend, which often show all accessible objects, large parts or even whole database tables need to be transferred from the database to the backend.
It's error-prone and security leaks can happen too easily, because after every query the access rights for all participating joins have to be considered.
### Add Visibility-Checks in the Backend
In this solution again, the database ignores row level visibility and returns all rows which match a given query. And the backend adds filter conditions to each query sent to the database.
#### Advantages
At least regarding this aspect, an in-memory database could be used for integration testing.
#### Disadvantages
It's error-prone and security leaks can happen too easily, because for every query the access rights for all participating joins have to be considered.
### POLICY with ENABLE ROW LEVEL SECURITY
For restricted DB-users, which are used by the backend, access to rows is filtered using a policy:
SET SESSION AUTHORIZATION DEFAULT;
CREATE ROLE restricted;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
CREATE POLICY customer_policy ON customer
FOR SELECT
TO restricted
USING (
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('customer', id, 'view'), currentSubjectUuid())
);
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentSubject TO 'alex@example.com';
SELECT * from customer; -- will only return visible rows
#### Advantages
Using POLICY together with ENABLE ROW LEVEL SECURITY is the PostgreSQL native mechanism to control access to data on the role level. Therefore, it looked like an obvious and elegant solution.
Every access at from the backend is under access control at the database level.
### Disadvantages
Unfortunately security mechanisms in PostgreSQL prevent the query optimizer to work well beyond ownership barriers (session user vs. table owner) and a SELECT from a table with 1 million objects needed over 30 seconds with our hierarchical RBAC policy.
We are bound to PostgreSQL, including integration tests and testing the RBAC system itself.
### VIEW-RULE with ON SELECT DO INSTEAD
SET SESSION SESSION AUTHORIZATION DEFAULT;
CREATE VIEW cust_view AS
SELECT * FROM customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT * FROM customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('customer', id, 'view'), currentSubjectUuid());
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentSubject TO 'alex@example.com';
SELECT * from customer; -- will only return visible rows
#### Advantages
Every access at from the backend is under access control at the database level.
Also using ON UPDATE etc., original tables could be completely hidden from the backend, and thus improved security.
### Disadvantages
Unfortunately security mechanisms in PostgreSQL prevent the query optimizer to work well beyond ownership barriers (session user vs. table owner) and a SELECT from a table with 1 million objects needed over 30 seconds with our hierarchical RBAC policy.
We are bound to PostgreSQL, including integration tests and testing the RBAC system itself.
An extra view needed for every table.
### VIEW with JOIN into flattened permissions
We do not access the tables directly from the backend, but via views which join the flattened permissions
SET SESSION SESSION AUTHORIZATION DEFAULT;
CREATE OR REPLACE VIEW cust_view AS
SELECT c.id, c.reference, c.prefix
FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentSubjectUuid()) AS p
ON p.tableName='customer' AND p.rowId=c.id AND p.op='view';
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
SET hsadminng.currentSubject TO 'alex@example.com';
SELECT * from cust_view; -- will only return visible rows
Alternatively the JOIN could also be applied in a "ON SELECT DO INSTEAD"-RULE, if there is any advantage for later features.
#### Advantages
Every access at from the backend is under access control at the database level.
No special PostgreSQL features needed; though the recursive Role-evaluation uses PostgreSQL features anyway.
Very fast, on my laptop a SELECT * FROM a table with 1 million rows just took about 50ms.
Also using ON UPDATE etc., original tables could be completely hidden from the backend, and thus improved security.
### Disadvantages
An extra view needed for every table.
## Decision Outcome
We chose the option **"VIEW with JOIN into flattened permissions"** because it supports the best combination of performance and security with almost no disadvantge.

View File

@ -0,0 +1,108 @@
# Object Mapping
**Status:**
- [x] proposed by Michael Hönnig
- [ ] accepted by (Participants)
- [ ] rejected by (Participants)
- [ ] superseded by (superseding ADR)
## Context and Problem Statement
Since we are using the *API first*-approach,
thus generating Java interfaces and model classes from an OpenAPI specification,
we cannot use the JPA-entities anymore at the API level,
not even if the data fields are 100% identical.
Therefore, we need some kind of mapping strategy.
### Technical Background
Java does not support duck-typing and therefore, objects of different classes have to be converted to each other, even if all data fields are identical.
In our case, the database query is usually the slowest part of handling a request.
Therefore, for the mapper, ease of use is more important than performance,
at least as long as the mapping part does not take more than 10% of the total request.
## Considered Options
* specific programmatic conversion
* using the *MapStruct* library
* using the *ModelMapper* library
* Dozer, last update from 2014 + vulnerabilities => skipped
* Orika, last update from 2019 + vulnerabilities => skipped
* JMapper
### specific programmatic conversion
In this solution, we would write own code to convert the objects.
This usually means 3 converters for each entity/resource pair:
- entity -> resource
- resource -> entity
- list of entities -> list of resources
#### Advantages
Very flexible and fast.
#### Disadvantages
Huge amounts of bloat code.
### using the *MapStruct* library
See https://mapstruct.org/.
#### Advantages
- Most popular mapping library in the Java-world.
- Actively maintained, last release 1.5.2 from Jun 18, 2022.
- very fast (see [^1])
#### Disadvantages
- Needs interface declarations with annotations.
- Looks like it causes still too much bloat code for our purposes.
### using the *ModelMapper* library
See http://modelmapper.org/.
#### Advantages
- 1:1 mappings just need a simple method call without any bloat-code.
- Actively maintained, last release 3.1.0 from Mar 08, 2022.
#### Disadvantages
- could not find any, will give it a try
### using the *JMapper* library
See https://jmapper-framework.github.io/jmapper-core/.
#### Advantages
- Supports annotation-based and programmatic mapping exceptions.
- Actively maintained, last release 1.6.3 from May 27, 2022.
- very fast (see [^1])
#### Disadvantages
- needs a separate mapper instance for each mapping pair
- cannot map collections (needs `stream().map(...).collect(toList())` or similar)
## Decision Outcome
We chose the option **"using the *ModelMapper* library"** because it has an acceptable performance without any bloat code.
If it turns out to be too slow after all, "using the *JMapper* library" seems to be a good alternative.
[^1]: https://www.baeldung.com/java-performance-mapping-frameworks

74
doc/glossary.md Normal file
View File

@ -0,0 +1,74 @@
### hsadminNg Glossary
This is a collection of terms used in this project, which either might not be generally known or unclear in meaning.
If you miss something, please add it with a `TODO` marker.
#### Blackbox-Test
A blackbox-test does not know and not consider such internals of an implementation, it just tests externally observable behaviour.
#### Business Object
Used in the RBAC-system to refer to an object from the business realm.
The usual term is *domain object* but in our context, the term *domain* could be too easily confused with a DNS *Internet domain*.
#### Dummy
A *dummy* is a kind of *Test-Double* which replaces a real dependency which is not really needed in the test case.
#### Fake
A *fake* is a kind of *Test-Double* without using any library, but rather a manual fake implementation of a dependency.
#### Mock
A *mock* is a kind of *Test-Double* which can be configured to behaviours as needed by a test-case.
Often the term "mock" is used in a generic way, because typical mocking libraries like *Mockito* can also be used as dummies or spies and can replace fakes.
#### RBAC
Abbreviation for *Role Based Access Control*.
A system to control access to business objects by defining users, roles, and permissions.
See also [The INCITS 359-2012 Standard](https://www.techstreet.com/standards/incits-359-2012?product_id=1837530).
In our case we are implementing a hierarchical RBAC for a hierarchical and dynamic business object structure.
More information can be found in our [RBAC Architecture Document](rbac.md).
#### Tenant
*Tenant* is one of the standard roles of Hostsharing's RBAC system.
It is assigned as a sub-role to those who have rights on sub-objects of a business object.
Usually, tenants can only view the contents.
Generally, tenant roles only apply for the mere existence, id and name of a business object,
not for internal details.
E.g. a tenant of a customer could be the administrator of a hosting package of that customer.
They can view some identifying information of that customer, but not view their billing and banking information.
#### Whitebox-Test
A whitebox-test knows and considers the internals of an implementation, e.g. it knows which dependencies it needs and can test special, implementation-dependent cases.
#### Test-Double
A "double" is a general term for something which replaces a real implementation of a dependency of the unit under test.
This can be a "dummy", a "fake", a "mock", a "spy" or a "stub".
#### Test-Fixture
Generally a test-fixture refers to all code within a test
which is needed to setup the test environment and extract results,
but which is not part of the test-cases.
In other words: The code which is needed to bind test-cases to the actual unit under test,
is called test-fixture.

View File

@ -0,0 +1,218 @@
## HostingAsset Type Structure
### Server+Webspace
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_SETUP
}
package Hosting #feb28c{
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IPV4_NUMBER
entity HA_IPV6_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER *==> BI_CLOUD_SERVER
HA_MANAGED_SERVER *==> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_IPV4_NUMBER o..> HA_CLOUD_SERVER
HA_IPV4_NUMBER o..> HA_MANAGED_SERVER
HA_IPV4_NUMBER o..> HA_MANAGED_WEBSPACE
HA_IPV6_NUMBER o..> HA_CLOUD_SERVER
HA_IPV6_NUMBER o..> HA_MANAGED_SERVER
HA_IPV6_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### Domain
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_SETUP
}
package Hosting #feb28c{
package Domain #99bcdb {
entity HA_DOMAIN_SETUP
entity HA_DOMAIN_DNS_SETUP
entity HA_DOMAIN_HTTP_SETUP
entity HA_DOMAIN_SMTP_SETUP
entity HA_DOMAIN_MBOX_SETUP
entity HA_EMAIL_ADDRESS
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_DOMAIN_SETUP *==> BI_DOMAIN_SETUP
HA_DOMAIN_SETUP o..> HA_DOMAIN_SETUP
HA_DOMAIN_DNS_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_DNS_SETUP o--> HA_MANAGED_WEBSPACE
HA_DOMAIN_HTTP_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_HTTP_SETUP o--> HA_UNIX_USER
HA_DOMAIN_SMTP_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_SMTP_SETUP o--> HA_MANAGED_WEBSPACE
HA_DOMAIN_MBOX_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_MBOX_SETUP o--> HA_MANAGED_WEBSPACE
HA_EMAIL_ADDRESS *==> HA_DOMAIN_MBOX_SETUP
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### MariaDB
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_SETUP
}
package Hosting #feb28c{
package MariaDB #99bcdb {
entity HA_MARIADB_INSTANCE
entity HA_MARIADB_USER
entity HA_MARIADB_DATABASE
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_MARIADB_USER *==> HA_MANAGED_WEBSPACE
HA_MARIADB_USER o--> HA_MARIADB_INSTANCE
HA_MARIADB_DATABASE *==> HA_MARIADB_USER
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### PostgreSQL
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_SETUP
}
package Hosting #feb28c{
package PostgreSQL #99bcdb {
entity HA_PGSQL_INSTANCE
entity HA_PGSQL_USER
entity HA_PGSQL_DATABASE
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_PGSQL_USER *==> HA_MANAGED_WEBSPACE
HA_PGSQL_USER o--> HA_PGSQL_INSTANCE
HA_PGSQL_DATABASE *==> HA_PGSQL_USER
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
This code generated was by HsHostingAssetType.main, do not amend manually.

View File

@ -0,0 +1,187 @@
# Beispiel: juristische Person (GmbH)
```mermaid
classDiagram
direction TD
namespace Hostsharing {
class person-HostsharingEG
}
namespace Partner {
class partner-MeierGmbH
class rel-MeierGmbH
class personDetails-MeierGmbH
class contactData-MeierGmbH
class person-MeierGmbH
}
namespace Representatives {
class person-FrankMeier
class contactData-FrankMeier
class rel-MeierGmbH-FrankMeier
}
namespace Debitors {
class debitor-MeierGmbH
class contactData-MeierGmbH-Buha
class rel-MeierGmbH-Buha
}
namespace Operations {
class person-SabineMeier
class contactData-SabineMeier
class rel-MeierGmbH-SabineMeier
}
namespace Enums {
class RelationType {
<<enumeration>>
UNKNOWN
PARTNER
DEBITOR
REPRESENTATIVE
OPERATIONS
}
class PersonType {
<<enumeration>>
UNKNOWN: nur für Import
NATURAL_PERSON: natürliche Person
LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V.
INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft
UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft
PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer]
}
}
class person-HostsharingEG {
+personType: LEGAL
+tradeName: Hostsahring eG
+familyName
+givenName
}
class partner-MeierGmbH {
+Numeric partnerNumber: 12345
+Relation partnerRel
}
partner-MeierGmbH *-- rel-MeierGmbH
class person-MeierGmbH {
+personType: LEGAL
+tradeName: Meier GmbH
+familyName
+givenName
}
person-MeierGmbH *-- personDetails-MeierGmbH
class personDetails-MeierGmbH {
+registrationOffice: AG Hamburg
+registrationNumber: ABC123434
+birthName
+birthPlace
+dateOfDeath
}
class contactData-MeierGmbH {
+postalAddress: Hauptstraße 5, 22345 Hamburg
+phoneNumbers: +49 40 12345-00
+emailAddresses: office@meier-gmbh.de
}
class rel-MeierGmbH {
+RelationType type PARTNER
+Person anchor
+Person holder
+Contact contact
}
rel-MeierGmbH o-- person-HostsharingEG : anchor
rel-MeierGmbH o-- person-MeierGmbH : holder
rel-MeierGmbH o-- contactData-MeierGmbH
%% --- Debitors ---
class debitor-MeierGmbH {
+Partner partner
+Numeric[2] debitorNumberSuffix: 00
+Relation debitorRel
+boolean billable: true
+String vatId: ID123456789
+String vatCountryCode: DE
+boolean vatBusiness: true
+boolean vatReverseCharge: false
+BankAccount refundBankAccount
+String defaultPrefix: mei
}
debitor-MeierGmbH o-- partner-MeierGmbH
debitor-MeierGmbH *-- rel-MeierGmbH-Buha
class contactData-MeierGmbH-Buha {
+postalAddress: Hauptstraße 5, 22345 Hamburg
+phoneNumbers: +49 40 12345-05
+emailAddresses: buha@meier-gmbh.de
}
class rel-MeierGmbH-Buha {
+RelationType type DEBITOR
+Person anchor
+Person holder
+Contact contact
}
rel-MeierGmbH-Buha o-- person-MeierGmbH : anchor
rel-MeierGmbH-Buha o-- person-MeierGmbH : holder
rel-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
%% --- Representatives ---
class person-FrankMeier {
+ personType: NATURAL
+ tradeName
+ familyName: Meier
+ givenName: Frank
}
class contactData-FrankMeier {
+postalAddress
+phoneNumbers: +49 40 12345-22
+emailAddresses: frank.meier@meier-gmbh.de
}
class rel-MeierGmbH-FrankMeier {
+RelationType type REPRESENTATIVE
+Person anchor
+Person holder
+Contact contact
}
rel-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
rel-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
rel-MeierGmbH-FrankMeier o-- contactData-FrankMeier
%% --- Operations ---
class person-SabineMeier {
+personType: NATURAL
+tradeName
+familyName: Meier
+givenName: Sabine
}
class contactData-SabineMeier {
+postalAddress
+phoneNumbers: +49 40 12345-22
+emailAddresses: sabine.meier@meier-gmbh.de
}
class rel-MeierGmbH-SabineMeier {
+RelationType type OPERATIONAL
+Person anchor
+Person holder
+Contact contact
}
rel-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
rel-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
rel-MeierGmbH-SabineMeier o-- contactData-SabineMeier
```

View File

@ -0,0 +1,83 @@
*(this is just a scribbled draft, that's why it's still in German)*
### *Schema-F* für Permissions, Rollen und Grants
Permissions, Rollen und Grants werden in den INSERT/UPDATE/DELETE-Triggern von Geschäftsobjekten erzeugt und gelöscht. Das Löschen erfolgt meistens automatisch über das zugehörige RbacObject, die INSERT- und UPDATE-Trigger müssen jedoch in *pl/pgsql* ausprogrammiert werden.
Das folgende Schema soll dabei unterstützen, die richtigen Permissions, Rollen und Grants festzulegen.
An einigen Stellen ist vom *Initiator* die Rede. Als *Initiator* gilt derjenige User, der die Operation (INSERT oder UPDATE) durchführt bzw. eine explizit anzugebende Rolle des Users.
Wird keine solche explizite Rolle angegeben, gilt die granted Rolle als diejenige, als der das Grant erfolgt.
#### Typ Root: Objekte, welche nur eine Spezialisierung bzw. Zusatzdaten für andere Objekte bereitstellen (z.B. Partner für Relations vom Typ Partner oder Partner Details für Partner)
Objektorientiert gedacht, enthalten solche Objekte die Zusatzdaten einer Subklasse; die Daten im Partner erweitern also eine Relation vom Typ `partner`.
- Dann muss dieses Objekt zeitlich nach dem Objekt erzeugt werden, auf dass es sich bezieht, also z.B. zeitlich nach der Relation.
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Es werden **keine** Rollen für dieses Objekt erzeugt.
- Statt eigener Rollen werden die o.g. Permissions passenden Rollen des Hauptobjekts zugewiesen (granted) bzw. aus denen entfernt (revoked).
- Handelt es sich um Zusatzdaten zum Zwecke der Spezialisierung, dann z.B. so:
- Delete (\*) <-- Owner des Hauptobjektes
- Edit <-- **Admin** des Hauptobjektes
- View <-- Agent des Hauptobjektes
- Handelt es sich um Zusatzdaten, für die sich Edit-Rechte delegieren lassen sollen (wie im Falle der Partner-Details eines Partners), dann z.B. so:
- Delete (\*) <-- Owner des Hauptobjektes
- Edit <-- **Agent** des Hauptobjektes
- View <-- Agent des Hauptobjektes
- Für die Rollenzuordnung zwischen referenzierten Objekten gilt:
- Für Objekte vom Typ Root werden die Rollen des zugehörigen Aggregator-Objektes verwendet.
- Gibt es Referenzen auf hierarchisch verbundene Objekte (z.B. Debitor.refundBankAccount) gilt folgende Faustregel:
***Nach oben absteigen, nach unten halten oder aufsteigen.*** An einem fachlich übergeordneten Objekt wird also eine niedrigere Rolle (z.B. Debitor.ADMIN -> Partner.AGENT), einem fachlich untergeordneten Objekt eine gleichwertige Rolle (z.B. Partner.ADMIN -> Debitor.ADMIN) zugewiesen oder sogar aufgestiegen (Debitor.ADMIN -> Package.TENANT).
- Für Referenzen zwischen Objekten, die nicht hierarchisch zueinander stehen (z.B. Debitor und Bankverbindung), wird auf beiden seiten abgestiegen (also Debitor.ADMIN -> BankAccount.REFERRER und BankAccount.ADMIN -> Debitor.TENANT).
Anmerkung: Der Typ-Begriff *Root* bezieht sich auf die Rolle im fachlichen Datenmodell. Im Bezug auf den Teilgraphen eines fachlichen Kontexts ist dies auch eine Wurzel im Sinne der Graphentheorie. Aber in anderen fachlichen Kontexten können auch diese Objekte von anderen Teilgraphen referenziert werden und werden dann zum inneren Knoten.
#### Typ Aggregator: Objekte, welche weitere Objekte zusammenfassen (z.B. Relation fasst zwei Persons und einen Contact zusammen)
Solche Objekte verweisen üblicherweise auf Objekte vom Typ Leaf und werden oft von Objekten des Typs Root referenziert.
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
- Owner, Admin, Agent, Tenent(, Guest?)
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
- Owner -> Delete (\*)
- Admin --> Edit
- Tenant (oder ggf. Guest) --> View
- Außerdem werden folgende Grants erstellt bzw. entzogen:
- Initiator --> Owner
- Owner --> Admin
- Admin --> Referrer
- Admins der referenzierten Objekte werden Agent des Aggregators
- Tenants des Aggregators werden Referrer der referenzierten Objekte
### Typ Leaf: Handelt es sich um ein Objekt, welches (außer zur Modellierung separater Permissions) keine Unterobjekte enthält (z.B. Person, Customer)?
Solche Objekte werden üblicherweise von Objekten des Typs Aggregator, manchmal auch von Objekten des Typs Root, referenziert.
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
- Owner, Admin, Referrer
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
- Delete (\*) <-- Owner
- Edit <-- Admin
- View <-- Referrer
- Außerdem werden folgende Grants erstellt bzw. entzogen:
- Owner --> Admin
- Admin --> Referrer
```mermaid
flowchart LR
subgraph partnerDetails
direction TB
style partnerDetails fill:#eee
perm:partnerDetails.*{{partnerDetails.*}}
role:partnerDetails.edit{{partnerDetails.edit}}
role:partnerDetails.view{{partnerDetails.view}}
end
```

View File

@ -0,0 +1,29 @@
(this is just a scribbled idea, that's why it's still in German)
Ich habe mal wieder vom RBAC-System geträumt 🙈 Ok, im Halbschlaf darüber nachgedacht trifft es wohl besser. Und jetzt frage ich mich, ob wir viel zu kompliziert gedacht haben.
Bislang gingen wir ja davon aus, dass, wenn komplexe Entitäten (z.B. Partner) erzeugt werden, wir wir über den INSERT-Trigger den Rollen der verknüpften Entitäten (z.B. den Rollen der Personendaten des Partners) auch Rechte an den komplexeren Entitäten und umgekehrt geben müssen.
Da die komplexen Entitäten nur mit gewissen verbundenen Entitäten überhaupt sinnvoll nutzbar sind und diese daher über INNSER JOINs mitladen, könnte sonst auch nur jemand diese Entitäten, der auch die SELECT-Permission an den verküpften Entitäten hat.
Vor einigen Wochen hatten wir schon einmal darüber geredet, ob wir dieses Geflecht wirklich komplett durchplanen müssen, also über mehrere Stufen hinweg, oder ob sehr warscheinlich eh dieselben Leuten an den weiter entfernten Entitäten die nötien Rechte haben, weil dahinter dieselben User stehen. Also z.B. dass gewährleistet ist, dass jemand mit ADMIN-Recht an den Personendaten des Partners auch bis in die SEPA-Mandate eines Debitors hineinsehen kann.
Und nun gehe ich noch einen Schritt weiter: Könnte es nicht auch andersherum sein? Also wenn jemand z.B. SELECT-Recht am Partner hat, dass wir davon ausgehen können, dass derjenige auch die Partner-Personen- und Kontaktdaten sehen darf, und zwar implizit durch seine Partner-SELECT-Permission und ohne dass er explizit Rollen für diese Partner-Personen oder Kontaktdaten inne hat?
Im Halbschlaf kam mir nur die Idee, warum wir nicht einfach die komplexen JPA-Entitäten zwar auf die restricted View setzen, wie bisher, aber für die verknüpften Entitäten auf die direkten (bisher "Raw..." genannt) Entitäten gehen. Dann könnte jemand mit einer Rolle, welche die SELECT-Permission auf die komplexe JPA-Entität (z.B.) Partner inne hat, auch die dazugehörige Relation(ship) ["Relation" wurde vor kurzem auf kurz "Relation" umbenannt] und die wiederum dazu gehörigen Personen- und Kontaktdaten lesen, ohne dass in einem INSERT- und UPDATE-Trigger der Partner-Entität die ganzen Grants mit den verknüpften Entäten aufgebaut und aktualisiert werden müssen.
Beim Debitor ist das nämlich selbst mit Generator die Hölle, zumal eben auch Querverbindungen gegranted werden müssen, z.B. von der Debitor-Person zum Sema-Mandat - jedenfalls wenn man nicht Gefahr laufen wollte, dass jemand mit Admin-Rechten an der Partner-Person (also z.B. ein Repräsentant des Partners) die Sepa-Mandate der Debitoren gar nicht mehr sehen kann. Natürlich bräuchte man immer noch die Agent-Rolle am Partner und Debitor (evtl. repräsentiert durch die jeweils zugehörigen Relation - falls dieser Trick überhaupt noch nötig wäre), sowie ein Grant vom Partner-Agent auf den Debitor-Agent und vom Debitor-Agent auf die Sepa-Mandate-Admins, aber eben ohne filigran die ganzen Neben-Entäten (Personen- und Kontaktdaten von Partner und Debitor sowie Bank-Account) in jedem Trigger berücksichtigen zu müssen. Beim Refund-Bank-Account sogar besonders ätzend, weil der optional ist und dadurch zig "if ...refundBankAccountUuid is not null then ..." im Code enstehen (wenn der auch generiert ist).
Mit anderen Worten, um als Repräsentant eines Geschäftspartners auf den Bank-Account der Sepa-Mandate sehen zu dürfen, wird derzeut folgende Grant-Kette durchlaufen (bzw. eben noch nicht, weil es noch nicht funktioniert):
User -> Partner-Holder-Person:ADMIN -> Partner-Relation:AGENT -> Debitor-Relation:AGENT -> Sepa-Mandat:ADMIN -> BankAccount:ADMIN -> BankAccount:SELECT
Daraus würde:
User -> Partner-Relation:AGENT -> Debitor-Relation:AGENT -> Sepa-Mandat:ADMIN -> Sepa-Mandat:SELECT*
(*mit JOIN auf RawBankAccount, also implizitem Leserecht)
Das klingt zunächst nach nur einer marginalen Vereinfachung, die eigentlich Vereinfachung liegt aber im Erzeugen der Grants in den Triggern, denn da sind zudem noch Partner-Anchor-Person, Debitor-Holder- und Anchor-Person, Partner- und Debitor-Contact sowie der RefundBankAccount zu berücksichtigen. Und genau diese Grants würden großteils wegfallen, und durch implizite Persmissions über die JOINs auf die Raw-Tables ersetzt werden. Den refundBankAccound müssten wir dann, analog zu den Sepa-Mandataten, umgedreht modellieren, da den sonst
Man könnte das Ganze auch als "Entwicklung der Rechtestruktur für Hosting-Entitäten auf der obersten Ebene" (Manged Webspace, Managed Server, Cloud Server etc.) sehen, denn die hängen alle unter dem Mega-komplexen Debitor.

View File

@ -0,0 +1,288 @@
## HSAdmin-NG
### Project/BookingItems/HostingEntities
__ATTENTION__: The notation uses UML clas diagram elements, but partly with different meanings. See Agenda.
```mermaid
classDiagram
direction TD
Partner o-- "0..n" Membership
Partner *-- "1..n" Debitor
Debitor *-- "1..n" Project
Project o-- "0..n" PrivateCloudBI
Project o-- "0..n" CloudServerBI
Project o-- "0..n" ManagedServerBI
Project o-- "0..n" ManagedWebspaceBI
PrivateCloudBI o-- "0..n" ManagedServerBI
PrivateCloudBI o-- "0..n" CloudServerBI
CloudServerBI *-- CloudServerHE
ManagedServerBI *-- ManagedServerHE
ManagedServerBI o-- "0..n" ManagedWebspaceBI
ManagedWebspaceBI *-- ManagedWebspaceHE
ManagedWebspaceHE *-- "1..n" UnixUserHE
ManagedWebspaceHE o-- "0..n" DomainDNSSetupHE
ManagedWebspaceHE o-- "0..n" DomainHttpSetupHE
ManagedWebspaceHE o-- "0..n" DomainEMailSetupHE
ManagedWebspaceHE o-- "0..n" EMailAliasHE
DomainEMailSetupHE o-- "0..n" EMailAddressHE
ManagedWebspaceHE o-- "0..n" MariaDBUserHE
MariaDBUserHE o-- "0..n" MariaDBHE
ManagedWebspaceHE o-- "0..n" PostgresDBUserHE
PostgresDBUserHE o-- "0..n" PostgresDBHE
DomainHttpSetupHE --|> UnixUserHE : assignedToAsset
ManagedWebspaceHE --|> ManagedServerHE
namespace Office {
class Partner {
}
class Membership {
}
class Debitor {
}
}
namespace Booking {
class Project {
+caption
+create()
}
class PrivateCloudBI {
+caption
~resources = [
+CPUs
+RAM
+SSD
+HDD
+Traffic
]
+book()
}
class CloudServerBI {
+caption
~resources = [
+CPUs
+RAM
+SSD
+HDD
+Traffic
]
+book()
}
class ManagedServerBI {
+caption
~respources = [
+CPUs
+RAM
+SSD
+HDD
+Traffic
]
+book()
}
class ManagedWebspaceBI {
+caption
~resources = [
+SSD
+HDD
+Traffic
+MultiOptions
+Daemons
]
+book()
}
}
style Project stroke:blue,stroke-width:4px
style PrivateCloudBI stroke:blue,stroke-width:4px
style CloudServerBI stroke:blue,stroke-width:4px
style ManagedServerBI stroke:blue,stroke-width:4px
style ManagedWebspaceBI stroke:blue,stroke-width:4px
%% ---------------------------------------------------------
namespace HostingServers {
%% separate (pseudo-) namespace just for better rendering
class CloudServerHE {
-identifier, e.g. "vm1234"
-caption := bi.caption?
-parentAsset := parentHost
-identifier := serverName
-create()
}
class ManagedServerHE {
-identifier, e.g. "vm1234"
-caption := bi.caption?
-parentAsset := parentHost
-identifier := serverName
~config = [
+installed Software
]
-create()
}
}
namespace Hosting {
class ManagedWebspaceHE {
-parentAsset := parentManagedServer
-identifier : webspaceName
+caption
-create()
}
class UnixUserHE {
+identifier ["xyz00-..."]
+caption
~config = [
+SSD Soft Quota
+SSD Hard Quota
+HDD Soft Quota
+HDD Hard Quota
#shell
#password
]
+create()
}
class DomainDNSSetupHE {
+identifier, e.g. "example.com"
+caption
+create()
}
class DomainHttpSetupHE {
+identifier, e.g. "example.com"
+caption
+create()
}
class DomainEMailSetupHE {
+identifier, e.g. "example.com"
+caption
+create()
}
class EMailAliasHE {
+identifier, e.g "xyz00-..."
+caption
~config = [
+target[]
]
+create()
}
class EMailAddressHE {
+identifier, e.g. "test@example.org"
+caption
~config = [
+sub-domain
+local-part
+target
]
+create()
}
class MariaDBUserHE {
+identifier, e.g. "xyz00_mydb"
+caption
config = [
#password
]
+create()
}
class MariaDBHE {
+identifier, e.g. "xyz00_mydb"
+caption
~config = [
+encoding
]
+create()
}
class PostgresDBUserHE {
+identifier, e.g. "xyz00_mydb"
+caption
~config = [
#password
]
+create()
}
class PostgresDBHE {
+identifier, e.g. "xyz00_mydb"
+caption
~config = [
+encoding
+extensions
]
+create()
}
}
style CloudServerHE stroke:orange,stroke-width:4px
style ManagedServerHE stroke:orange,stroke-width:4px
style ManagedWebspaceHE stroke:orange,stroke-width:4px
style UnixUserHE stroke:blue,stroke-width:4px
style DomainDNSSetupHE stroke:blue,stroke-width:4px
style DomainHttpSetupHE stroke:blue,stroke-width:4px
style DomainEMailSetupHE stroke:blue,stroke-width:4px
style EMailAliasHE stroke:blue,stroke-width:4px
style EMailAddressHE stroke:blue,stroke-width:4px
style MariaDBUserHE stroke:blue,stroke-width:4px
style MariaDBHE stroke:blue,stroke-width:4px
style PostgresDBUserHE stroke:blue,stroke-width:4px
style PostgresDBHE stroke:blue,stroke-width:4px
%% --------------------------------------
ParentA o-- ChildA : can contain
ParentB *-- ChildB : contains
namespace Agenda {
class ParentA {
}
class ChildA {
}
class ParentB {
}
class ChildB {
}
class CreatedByClient {
}
class CreatedAutomatically {
}
class SomeEntity {
~patchable = [
%% the following indentations uses two U+2800 to have effect in the rendered diagram
+first
+second
]
-readOnly for client accounts
+readWrite for client accounts
#writeOnly
}
}
style CreatedByClient stroke:blue,stroke-width:4px
style CreatedAutomatically stroke:orange,stroke-width:4px
end
```

View File

@ -0,0 +1,468 @@
# RBAC Performance Analysis
This describes the analysis of the legacy-data-import which took way too long, which turned out to be a problem in the RBAC-access-rights-check as well as `EntityManager.persist` creating too many SQL queries.
## Our Performance-Problem
During the legacy data import for hosting assets we noticed massive performance problems. The import of about 2200 hosting-assets (IP-numbers, managed-webspaces, managed- and cloud-servers) as well as the creation of booking-items and booking-projects as well as necessary office-data entities (persons, contacts, partners, debitors, relations) **took 25 minutes**.
Importing hosting assets up to UnixUsers and EmailAddresses even **took about 100 minutes**.
(The office data import sometimes, but rarely, took only 10min.
We could not find a pattern, why that was the case. The impression that it had to do with too many other parallel processes, e.g. browser with BBB or IntelliJ IDEA was proved wrong, but stopping all unnecessary processes and performing the import again.)
## Preparation
### Configuring PostgreSQL
The pg_stat_statements PostgreSQL-Extension can be used to measure how long queries take and how often they are called.
The module auto_explain can be used to automatically run EXPLAIN on long-running queries.
To use this extension and module, we extended the PostgreSQL-Docker-image:
```Dockerfile
FROM postgres:15.5-bookworm
RUN apt-get update && \
apt-get install -y postgresql-contrib && \
apt-get clean
COPY etc/postgresql-log-slow-queries.conf /etc/postgresql/postgresql.conf
```
And create an image from it:
```sh
docker build -t postgres-with-contrib:15.5-bookworm .
```
Then we created a config file for PostgreSQL in `etc/postgresql-log-slow-queries.conf`:
```
shared_preload_libraries = 'pg_stat_statements,auto_explain'
log_min_duration_statement = 1000
log_statement = 'all'
log_duration = on
pg_stat_statements.track = all
auto_explain.log_min_duration = '1s' # Logs queries taking longer than 1 second
auto_explain.log_analyze = on # Include actual run times
auto_explain.log_buffers = on # Include buffer usage statistics
auto_explain.log_format = 'json' # Format the log output in JSON
listen_addresses = '*'
```
And a Docker-Compose config in 'docker-compose.yml':
```
version: '3.8'
services:
postgres:
image: postgres-with-contrib:15.5-bookworm
container_name: custom-postgres
environment:
POSTGRES_PASSWORD: password
volumes:
- /home/mi/Projekte/Hostsharing/hsadmin-ng/etc/postgresql-log-slow-queries.conf:/etc/postgresql/postgresql.conf
ports:
- "5432:5432"
command:
- bash
- -c
- >
apt-get update &&
apt-get install -y postgresql-contrib &&
docker-entrypoint.sh postgres -c config_file=/etc/postgresql/postgresql.conf
```
### Activate the pg_stat_statements Extension
The pg_stat_statements extension was activated in our Liquibase-scripts:
```
create extension if not exists "pg_stat_statements";
```
### Running the Tweaked PostgreSQL
Now we can run PostgreSQL with activated slow-query-logging:
```shell
docker-compose up -d
```
### Running the Import
Using an environment like this:
```shell
export HSADMINNG_POSTGRES_JDBC_URL=jdbc:postgresql://localhost:5432/postgres
export HSADMINNG_POSTGRES_ADMIN_USERNAME=postgres
export HSADMINNG_POSTGRES_ADMIN_PASSWORD=password
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
```
We can now run the hosting-assets-import:
```shell
time gw-importHostingAssets
```
### Fetch the Query Statistics
And afterward we can query the statistics in PostgreSQL, e.g.:
```SQL
WITH statements AS (
SELECT * FROM pg_stat_statements pss
)
SELECT calls,
total_exec_time::int/(60*1000) as total_mins,
mean_exec_time::int as mean_millis,
query
FROM statements
WHERE calls > 100 AND shared_blks_hit > 0
ORDER BY total_exec_time DESC
LIMIT 16;
```
### Reset the Query Statistics
```SQL
SELECT pg_stat_statements_reset();
```
## Analysis Result
### RBAC-Access-Rights Detection query
This CTE query was run over 4000 times during a single import and takes in total the whole execution time of the import process:
```SQL
WITH RECURSIVE grants AS (
SELECT descendantUuid, ascendantUuid, $5 AS level
FROM RbacGrants
WHERE assumed
AND ascendantUuid = any(subjectIds)
UNION ALL
SELECT g.descendantUuid, g.ascendantUuid, grants.level + $6 AS level
FROM RbacGrants g
INNER JOIN grants ON grants.descendantUuid = g.ascendantUuid
WHERE g.assumed
),
granted AS (
SELECT DISTINCT descendantUuid
FROM grants
)
SELECT DISTINCT perm.objectUuid
FROM granted
JOIN RbacPermission perm ON granted.descendantUuid = perm.uuid
JOIN RbacObject obj ON obj.uuid = perm.objectUuid
WHERE (requiredOp = $7 OR perm.op = requiredOp)
AND obj.objectTable = forObjectTable
LIMIT maxObjects+$8
```
That query is used to determine access rights of the currently active RBAC-subject(s).
We used `EXPLAIN` with a concrete version (parameters substituted with real values) of that query and got this result:
```
QUERY PLAN
Limit (cost=6549.08..6549.35 rows=54 width=16)
CTE grants
-> Recursive Union (cost=4.32..5845.97 rows=1103 width=36)
-> Bitmap Heap Scan on rbacgrants (cost=4.32..15.84 rows=3 width=36)
Recheck Cond: (ascendantuuid = ANY ('{ad1133dc-fbb7-43c9-8c20-0da3f89a2388}'::uuid[]))
Filter: assumed
-> Bitmap Index Scan on rbacgrants_ascendantuuid_idx (cost=0.00..4.32 rows=3 width=0)
Index Cond: (ascendantuuid = ANY ('{ad1133dc-fbb7-43c9-8c20-0da3f89a2388}'::uuid[]))
-> Nested Loop (cost=0.29..580.81 rows=110 width=36)
-> WorkTable Scan on grants grants_1 (cost=0.00..0.60 rows=30 width=20)
-> Index Scan using rbacgrants_ascendantuuid_idx on rbacgrants g (cost=0.29..19.29 rows=4 width=32)
Index Cond: (ascendantuuid = grants_1.descendantuuid)
Filter: assumed
-> Unique (cost=703.11..703.38 rows=54 width=16)
-> Sort (cost=703.11..703.25 rows=54 width=16)
Sort Key: perm.objectuuid
-> Nested Loop (cost=31.60..701.56 rows=54 width=16)
-> Hash Join (cost=31.32..638.78 rows=200 width=16)
Hash Cond: (perm.uuid = grants.descendantuuid)
-> Seq Scan on rbacpermission perm (cost=0.00..532.92 rows=28392 width=32)
-> Hash (cost=28.82..28.82 rows=200 width=16)
-> HashAggregate (cost=24.82..26.82 rows=200 width=16)
Group Key: grants.descendantuuid
-> CTE Scan on grants (cost=0.00..22.06 rows=1103 width=16)
-> Index Only Scan using rbacobject_objecttable_uuid_key on rbacobject obj (cost=0.28..0.31 rows=1 width=16)
Index Cond: ((objecttable = 'hs_hosting_asset'::text) AND (uuid = perm.objectuuid))
```
### Office-Relation-Query
```SQL
SELECT hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress,c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version
FROM hs_office_relation_rv hore1_0
LEFT JOIN hs_office_person_rv a1_0 ON a1_0.uuid=hore1_0.anchoruuid
LEFT JOIN hs_office_contact_rv c1_0 ON c1_0.uuid=hore1_0.contactuuid
LEFT JOIN hs_office_person_rv h1_0 ON h1_0.uuid=hore1_0.holderuuid
WHERE hore1_0.uuid=$1
```
That query on the `hs_office_relation_rv`-table joins the three references anchor-person, holder-person and contact.
### Total-Query-Time > Total-Import-Runtime
That both queries total up to more than the runtime of the import-process is most likely due to internal parallel query processing.
## Attempts to Mitigate the Problem
### VACUUM ANALYZE
In the middle of the import, we updated the PostgreSQL statistics to recalibrate the query optimizer:
```SQL
VACUUM ANALYZE;
```
This did not improve the performance.
### Improving Joins + Indexes
We were suspicious about the sequential scan over all `rbacpermission` rows which was done by PostgreSQL to execute a HashJoin strategy. Turning off that strategy by
```SQL
ALTER FUNCTION rbac.queryAccessibleObjectUuidsOfSubjectIds SET enable_hashjoin = off;
```
did not improve the performance though. The HashJoin was actually still applied, but no full table scan anymore:
```
[...]
QUERY PLAN
-> Hash Join (cost=36.02..40.78 rows=1 width=16)
Hash Cond: (grants.descendantuuid = perm.uuid)
-> HashAggregate (cost=13.32..15.32 rows=200 width=16)
Group Key: grants.descendantuuid
-> CTE Scan on grants (cost=0.00..11.84 rows=592 width=16)
[...]
```
The HashJoin strategy could be great if the hash-map could be kept for multiple invocations. But during an import process, of course, there are always new rows in the underlying table and the hash-map would be outdated immediately.
Also creating indexes which should suppor the RBAC query, like the following, did not improve performance:
```SQL
create index on RbacPermission (objectUuid, op);
create index on RbacPermission (opTableName, op);
```
### LAZY loading for Relation.anchorPerson/.holderPerson/
At this point, the import took 21mins with these statistics:
| query | calls | total_m | mean_ms |
|-------|-------|---------|---------|
| select hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress, c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 left join public.hs_office_person_rv a1_0 on a1_0.uuid=hore1_0.anchoruuid left join public.hs_office_contact_rv c1_0 on c1_0.uuid=hore1_0.contactuuid left join public.hs_office_person_rv h1_0 on h1_0.uuid=hore1_0.holderuuid where hore1_0.uuid=$1 | 517 | 11 | 1282 |
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 | 973 | 4 | 254 |
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 | 973 | 4 | 253 |
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
| insert into public.hs_hosting_asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
| insert into hs_hosting_asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
| insert into public.hs_office_relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 9 |
| insert into hs_office_relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 9 |
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 8 |
| with recursive grants as ( select descendantUuid, ascendantUuid from RbacGrants where descendantUuid = grantedId union all select ""grant"".descendantUuid, ""grant"".ascendantUuid from RbacGrants ""grant"" inner join grants recur on recur.ascendantUuid = ""grant"".descendantUuid ) select exists ( select $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47540 | 0 | 0 |
| insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing" | 40472 | 0 | 0 |
| insert into public.hs_booking_item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
| insert into hs_booking_item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
The slowest query now was fetching Relations joined with Contact, Anchor-Person and Holder-Person, for all tables using the restricted (RBAC) views (_rv).
We changed these mappings from `EAGER` (default) to `LAZY` to `@ManyToOne(fetch = FetchType.LAZY)` and got this result:
:::small
| query | calls | total (min) | mean (ms) |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------|----------|
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 | 1015 | 4 | 238 |
| select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 where hore1_0.uuid=$1 | 517 | 4 | 439 |
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 | 497 | 2 | 213 |
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
| insert into public.hs_hosting_asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
| insert into hs_hosting_asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
| with recursive grants as ( select descendantUuid, ascendantUuid from RbacGrants where descendantUuid = grantedId union all select ""grant"".descendantUuid, ""grant"".ascendantUuid from RbacGrants ""grant"" inner join grants recur on recur.ascendantUuid = ""grant"".descendantUuid ) select exists ( select $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47538 | 0 | 0 |
insert into public.hs_office_relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 8 |
| insert into hs_office_relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 8 |
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 7 |
| insert into public.hs_booking_item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
| insert into hs_booking_item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing | 40472 | 0 | 0 |
Now, finally, the total runtime of the import was down to 12 minutes. This is repeatable, where originally, the import took about 25mins in most cases and just rarely - and for unknown reasons - 10min.
### Importing UnixUser and EmailAlias Assets
But once UnixUser and EmailAlias assets got added to the import, the total time went up to about 110min.
This was not acceptable, especially not, considering that domains, email-addresses and database-assets are almost 10 times that number and thus the import would go up to over 1100min which is 20 hours.
In a first step, a `HsHostingAssetRawEntity` was created, mapped to the raw table (hs_hosting_asset) not to the RBAC-view (hs_hosting_asset_rv). Unfortunately we did not keep measurements, but that was only part of the problem anyway.
The main problem was, that there is something strange with persisting (`EntityManager.persist`) for EmailAlias assets. Where importing UnixUsers was mostly slow due to RBAC SELECT-permission checks, persisting EmailAliases suddenly created about a million (in numbers 1.000.000) SQL UPDATE statements after the INSERT, all with the same data, just increased version number (used for optimistic locking). We were not able to figure out why this happened.
Keep in mind, it's the same table with the same RBAC-triggers, just a different value in the type column.
Once `EntityManager.persist` was replaced by an explicit SQL INSERT - just for `HsHostingAssetRawEntity`, the total time was down to 17min. Thus importing the UnixUsers and EmailAliases took just 5min, which is an acceptable result. The total import of all HostingAssets is now estimated to about 1 hour (on my developer laptop).
Now, the longest running queries are these:
| No.| calls | total_m | mean_ms | query |
|---:|---------|--------:|--------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 | 13.093 | 4 | 21 | insert into hs_hosting_asset( uuid, type, bookingitemuuid, parentassetuuid, assignedtoassetuuid, alarmcontactuuid, identifier, caption, config, version) values ( $1, $2, $3, $4, $5, $6, $7, $8, cast($9 as jsonb), $10) |
| 2 | 517 | 4 | 502 | select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 where hore1_0.uuid=$1 |
| 3 | 13.144 | 4 | 21 | call buildRbacSystemForHsHostingAsset(NEW) |
| 4 | 96.632 | 3 | 2 | call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) |
| 5 | 120.815 | 3 | 2 | select * from rbac.isGranted(array[granteeId], grantedId) |
| 6 | 123.740 | 3 | 2 | with recursive grants as ( select descendantUuid, ascendantUuid from RbacGrants where descendantUuid = grantedId union all select "grant".descendantUuid, "grant".ascendantUuid from RbacGrants "grant" inner join grants recur on recur.ascendantUuid = "grant".descendantUuid ) select exists ( select $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) |
| 7 | 497 | 2 | 259 | select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 |
| 8 | 497 | 2 | 255 | select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 |
| 9 | 13.144 | 1 | 8 | SELECT createRoleWithGrants( hsHostingAssetTENANT(NEW), permissions => array[$7], incomingSuperRoles => array[ hsHostingAssetAGENT(NEW), hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ hsBookingItemTENANT(newBookingItem), hsHostingAssetTENANT(newParentAsset)] ) |
| 10 | 13.144 | 1 | 5 | SELECT createRoleWithGrants( hsHostingAssetADMIN(NEW), permissions => array[$7], incomingSuperRoles => array[ hsBookingItemAGENT(newBookingItem), hsHostingAssetAGENT(newParentAsset), hsHostingAssetOWNER(NEW)] ) |
That the `INSERT into hs_hosting_asset` (No. 1) takes up the most time, seems to be normal, and 21ms for each call is also fine.
It seems that the trigger effects (eg. No. 3 and No. 4) are included in the measure for the causing INSERT, otherwise summing up the totals would exceed the actual total time of the whole import. And it was to be expected that building the RBAC rules for new business objects takes most of the time.
In production, the `SELECT ... FROM hs_office_relation_rv` (No. 2) with about 0.5 seconds could still be a problem. But once we apply the improvements from the hosting asset area also to the office area, this should not be a problem for the import anymore.
## Further Options To Explore
1. Instead of separate SQL INSERT statements, we could try bulk INSERT.
2. We could use the SQL INSERT method for all entity-classes, or at least for all which have high row counts.
3. For the production code, we could use raw-entities for referenced entities, here usually RBAC SELECT permission is given anyway.
## The Problematically Huge Join
The origin problem was the expensive RBAC check for many SELECT queries.
This consists of two parts:
1. The recursive CTE query to determine which object's UUIDs are visible for the current subject.
This query itself takes currently about 250ms thus is no problem by itself as long as we only need it once per request.
2. Joining the result from 1. with the result if a business query.
The performance of the business query itself is no problem, for the join see the following explanations.
Superusers can see all objects (currently already over 90.000)
and even high level roles of customers with many hosting assets can see several thousand objects.
This is the one side of that problematic join.
The other side of that problematic is the result of the business query.
For example if a user wants to select all of their e-mail-addresses, that might easily half of the visible objects.
Thus, we would have a join of for example 5.000 x 2.500 rows, which is going to be slow.
As there are currently about 84.000 objects are hosting assets and 33.000 e-mail-addresses in our system,
for a superuser we would even run into an 84.0000 x 33.0000 join.
We found some solution approaches:
1. Getting rid of the `rbacrole` and `rbacpermission` table and only having implicit roles with implicit grants (OWNER->ADMIN->AGENT->TENENT->REFERRER) by comparison of ordered enum values and fixed permission assignments (e.g. OWENER->DELETE, ADMIN->UPDATE etc.). We could also get rid of the table `rbacreferece` if we enter users as business objects.
This should dramatically reduce the size of the table `rbackgrant` as well as the recusion levels.
But since we only apply this query once for each business query, that would only improve performance once we have way more objects in our system, but does not help our current problem.
It's quite some effort to implement even just a prototype, so we did not further explore this idea.
2. Adding the object type to the table `rbacObject` to reduce the size of the result of the recursive CTE query.
See chapter below.
3. Inverting the recursion of the CTE-query, combined with the type condition.
Instead of starting the recursion with `currentSubjectOrAssumedRolesUuids()`,
we could start it with the target table name and row-type,
then recurse down to the `currentSubjectOrAssumedRolesUuids()`.
In the end, we need the object UUIDs, though.
But if we start with the join of `rbacObject` with `rbacPermission`,
we need to forward the object UUIDs through the whole recursion.
This idea was not yet further explored.
### Adding The Object Type To The Table `rbacObject`
This optimization idea came from Michael Hierweck and was promising.
The idea is to reduce the size of the result of the recursive CTE query and maybe even speed up that query itself.
To evaluate this, I added a type column to the `rbacObject` table, initially as an enum hsHostingAssetType. Then I entered the type there for all rows from hs_hosting_asset. This means that 83,886 of 92,545 rows in `rbacobject` have a type set, leaving 8,659 without.
If we do this for other types (we currently have 1,271 relations and 927 booking items), it gets more complicated because they are different enum types. As varchar(16), we could lose performance again due to the higher storage space requirements.
But the performance gained is not particularly high anyway.
See the average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN',
joined with business query for all `'EMAIL_ADDRESSES'`:
| | D-1000000-hsh | D-1000300-mih |
|-----------------------------------------------------|------------------|---------------|
| currently (without type comparision in rbacobject): | ~3.30 - ~3.49 | ~0.23 |
| optimized (with type comparision in rbacobject): | ~2.99 - ~3.08 | ~0.21 |
As you can see, the query is no problem at all for normal customers (in the example, yours truly). With Hostsharing (D-1000000-hsh) it is quite slow.
Luckily this experiment also shows that it's not a big problem, having all hosting assets in the same database table.
Implementing this approach would be a bit difficult anyway, because we would need to transfer the type query parameter into the definition of the restricted view. We have not even the slightest idea how this could be done.
See the related queries in [recursive-cte-experiments-for-accessible-uuids.sql](../sql/recursive-cte-experiments-for-accessible-uuids.sql). They might have changed independently since this document was written, but you can still check out the old version from git.
### Rearranging the Parts of the CTE-Query
I also moved the function call which determines into its own WITH-section, with no improvement.
Experimentally I moved the business condition into the CTE SELECT, also with no improvement.
Such rearrangements seem to be successfully done by the PostgreSQL query optimizer.
## Summary
### What we did Achieve?
In a first step, the total import runtime for office entities was reduced from about 25min to about 10min.
In a second step, we reduced the import of booking- and hosting-assets from about 100min (not counting the required office entities) to 5min.
### What did not Help?
Rearranging the CTE query by extracting parts into WITH-clauses did not improve the performance.
Surprisingly little performance gain (<10% improvement) came from reducing the result of the CTE query by moving the hosting asset type into RBAC-system and using it in the inner SELECT query instead of in the outer SELECT query of the application side.
### What did Help?
Merging the recursive CTE query to determine the RBAC SELECT-permission, made it more clear which business-queries take the time.
Avoiding EAGER-loading where not necessary, reduced the total runtime of the import to about the half.
The major improvement came from using direct INSERT statements, which avoided some SELECT statements unnecessarily generated by the EntityManager and also completely bypassed the RBAC SELECT permission checks.
### What Still Has To Be Done?
Where this performance analysis was mostly helping the performance of the legacy data import, we still need measures and improvements for the productive code.
For sure, using more LAZY-loading also helps in the production code. For some more ideas see section _Further Options To Explore_.

711
doc/rbac.md Normal file
View File

@ -0,0 +1,711 @@
## *hsadmin-ng*'s Role-Based-Access-Management (RBAC)
The requirements of *hsadmin-ng* include table-, row- and column-level-security for read and write access to business-objects.
More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object.
Further, roles and business-objects are hierarchical.
To avoid misunderstandings, we are using the term "business-object" what's usually called a "domain-object".
But as we are in the context of a webhosting infrastructure provider, "domain" would have a double meaning.
Our implementation is based on Role-Based-Access-Management (RBAC) in conjunction with views and triggers on the business-objects.
As far as possible, we are using the same terms as defined in the RBAC standard, for our function names though, we chose more expressive names.
In RBAC, subjects can be assigned to roles, roles can be hierarchical and eventually have assigned permissions.
A permission allows a specific operation (e.g. SELECT or UPDATE) on a specific (business-) object.
You can find the entity structure as a UML class diagram as follows:
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
skinparam linetype ortho
package RBAC {
' forward declarations
entity RbacSubject
together {
entity RbacRole
entity RbacPermission
RbacSubject -[hidden]> RbacRole
RbacRole -[hidden]> RbacSubject
}
together {
entity RbacGrant
enum RbacReferenceType
entity RbacReference
}
RbacReference -[hidden]> RbacReferenceType
entity RbacGrant {
ascendantUuid: uuid(RbackReference)
descendantUuid: uuid(RbackReference)
auto
}
RbacGrant o-u-> RbacReference
RbacGrant o-u-> RbacReference
enum RbacReferenceType {
RbacSubject
RbacRole
RbacPermission
}
RbacReferenceType ..> RbacSubject
RbacReferenceType ..> RbacRole
RbacReferenceType ..> RbacPermission
entity RbacReference {
*uuid : uuid <<generated>>
--
type : RbacReferenceType
}
RbacReference o--> RbacReferenceType
entity RbacSubject {
*uuid : uuid <<generated>>
--
name : varchar
}
RbacSubject o-- RbacReference
entity RbacRole {
*uuid : uuid(RbacReference)
--
name : varchar
}
RbacRole o-- RbacReference
together {
enum RbacOperation
entity RbacObject
}
entity RbacPermission {
*uuid : uuid(RbacReference)
--
objectUuid: RbacObject
op: RbacOperation
}
RbacPermission o-- RbacReference
RbacPermission o-- RbacOperation
RbacPermission *-- RbacObject
enum RbacOperation {
INSERT:package
INSERT:domain
...
SELECT
UPDATE
DELETE
}
entity RbacObject {
*uuid : uuid <<generated>>
--
objectTable: varchar
}
RbacObject o- "Business Objects"
}
package "Business Objects" {
entity package
package *--u- RbacObject
entity customer
customer *--u- RbacObject
entity "..." as moreBusinessObjects
moreBusinessObjects *-u- RbacObject
}
@enduml
```
### The RBAC Entity Types
#### RbacReference
An *RbacReference* is a generalization of all entity types which participate in the hierarchical role system, defined via *RbacGrant*.
The primary key of the *RbacReference* and its referred object is always identical.
#### RbacReferenceType
The enum *RbacReferenceType* describes the type of reference.
It's only needed to make it easier to find the referred object in *RbacSubject*, *RbacRole* or *RbacPermission*.
#### RbacSubject
An *RbacSubject* is a type of RBAC-subject which references a login account outside this system, identified by a name (usually an email-address).
*RbacSubject*s can be assigned to multiple *RbacRole*s, through which they can get permissions to *RbacObject*s.
The primary key of the *RbacSubject* is identical to its related *RbacReference*.
#### RbacRole
An *RbacRole* represents a collection of directly or indirectly assigned *RbacPermission*s.
Each *RbacRole* can be assigned to *RbacSubject*s or to another *RbacRole*.
Both kinds of assignments are represented via *RbacGrant*.
*RbacRole* entities can *RbacObject*s, or more precise
#### RbacPermission
An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject*.
#### RbacOperation
An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do.
It can be one of:
- **'INSERT'** - permits inserting new rows related to the row, to which the permission belongs, in the table which is specified an extra column, includes 'SELECT'
- **'SELECT'** - permits selecting the row specified by the permission, is included in all other permissions
- **'UPDATE'** - permits updating (only the updatable columns of) the row specified by the permission, includes 'SELECT'
- **'DELETE'** - permits deleting the row specified by the permission, includes 'SELECT'
This list is extensible according to the needs of the access rule system.
Please notice, that there is no **create** operation to create new instances of unrelated business-object-types.
For such a singleton business-object-type, e.g. *Organization" or "Hostsharing" has to be defined, and its single entity is referred in the permission.
Only with this rule, the foreign key in *RbacPermission* can be defined as `NOT NULL`.
#### RbacGrant
The *RbacGrant* entities represent the access-rights structure from *RbacSubject*s via hierarchical *RbacRoles* down to *RbacPermission*s.
The core SQL queries to determine access rights are all recursive queries on the *RbacGrant* table.
### Role naming
The naming pattern of a role is important to be able to address specific roles.
E.g. if a new package is added, the admin-role of the related customer has to be addressed.
There can be global roles like 'administrators'.
Most roles, though, are specific for certain business-objects and automatically generated as such:
business-object-table#business-object-name.role-stereotype
Where *business-object-table* is the name of the SQL table of the business object (e.g *customer* or 'package'),
*business-object-name* is generated from an immutable business key(e.g. a prefix like 'xyz' or 'xyz00')
and the *role-stereotype* describes a role relative to a referenced business-object as follows:
#### owner
The owner-role is granted to the subject which created the business object.
E.g. for a new *customer* it would be granted to 'administrators' and for a new *package* to the 'customer#...:ADMIN'.
Whoever has the owner-role assigned can do everything with the related business-object, including deleting (or deactivating) it.
In most cases, the permissions to other operations than 'DELETE' are granted through the 'admin' role.
By this, all roles ob sub-objects, which are assigned to the 'admin' role, are also granted to the 'owner'.
#### ADMIN
The admin-role is granted to a role of those subjects who manage the business object.
E.g. a 'package' is manged by the admin of the customer.
Whoever has the admin-role assigned, can usually update the related business-object but not delete (or deactivating) it.
The admin-role also comprises lesser roles, through which the SELECT-permission is granted.
#### AGENT
The agent-role is not used in the examples of this document, because it's for more complex cases.
It's usually granted to those roles and users who represent the related business-object, but are not allowed to update it.
Other than the tenant-role, it usually offers broader visibility of sub-business-objects (joined entities).
E.g. a package-admin is allowed to see the related debitor-business-object,
but not its banking data.
#### TENANT
The tenant-role is granted to everybody who needs to be able to select the business-object and (probably some) related business-objects.
Usually all owners, admins and tenants of sub-objects get this role granted.
Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get SELECT permission.
#### GUEST
(Deprecated)
#### REFERRER
Like the agent-role, the guest-role too is not used in the examples of this document, because it's for more complex cases.
If the referrer-role exists, the SELECT-permission is granted to it, instead of to the tenant-role.
Other than the tenant-role, the referrer-roles does never grant any roles of related objects.
Also, if the referrer-role exists, the tenant-role receives the SELECT-permission through the referrer-role.
### Referenced Business Objects and Role-Depreciation
A general rule is, if one business object *origin* references another object *target* (in other words: one database table joins another table),
**and** a role for *origin* needs also access to *target*,
then usually the *target* role is granted to the *origin* role which is one level lower.
E.g. the admin-role of the *origin* object gets granted the agent-role (or, if it does not exist, then the tenant-role) of the *target* object.
Following this rule, also implies, that the number of indirections to which visibility can be granted is limited.
The admin-role of one object could be granted visibility to another object through at maximum 3 joins (agent->tenant->guest).
But not in all cases role-depreciation takes place.
E.g. often a tenant-role is granted another tenant-role,
because it should be again allowed to select sub-objects.
The same for the agent-role, often it is granted another agent-role.
## Example Users, Roles, Permissions and Business-Objects
The following diagram shows how users, roles and permissions could be granted access to operations on business objects.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
package RbacSubjects {
object UserMike
object UserSuse
object UserPaul
}
package RbacRoles {
object RoleAdministrators
object RoleCustXyz_Owner
object RoleCustXyz_Admin
object RolePackXyz00_Owner
}
RbacSubjects -[hidden]> RbacRoles
package RbacPermissions {
object PermCustXyz_SELECT
object PermCustXyz_UPDATE
object PermCustXyz_DELETE
object PermCustXyz_INSERT:Package
object PermPackXyz00_SELECT
object PermPackXyz00_EDIT
object PermPackXyz00_DELETE
object PermPackXyz00_INSERT:USER
}
RbacRoles -[hidden]> RbacPermissions
package BusinessObjects {
object CustXyz
object PackXyz00
}
RbacPermissions -[hidden]> BusinessObjects
UserMike o---> RoleAdministrators
UserSuse o--> RoleCustXyz_Admin
UserPaul o--> RolePackXyz00_Owner
RoleAdministrators o..> RoleCustXyz_Owner
RoleCustXyz_Owner o-> RoleCustXyz_Admin
RoleCustXyz_Admin o-> RolePackXyz00_Owner
RoleCustXyz_Owner o--> PermCustXyz_UPDATE
RoleCustXyz_Owner o--> PermCustXyz_DELETE
RoleCustXyz_Admin o--> PermCustXyz_SELECT
RoleCustXyz_Admin o--> PermCustXyz_INSERT:Package
RolePackXyz00_Owner o--> PermPackXyz00_SELECT
RolePackXyz00_Owner o--> PermPackXyz00_UPDATE
RolePackXyz00_Owner o--> PermPackXyz00_DELETE
RolePackXyz00_Owner o--> PermPackXyz00_INSERT:User
PermCustXyz_SELECT o--> CustXyz
PermCustXyz_UPDATE o--> CustXyz
PermCustXyz_DELETE o--> CustXyz
PermCustXyz_INSERT:Package o--> CustXyz
PermPackXyz00_SELECT o--> PackXyz00
PermPackXyz00_UPDATE o--> PackXyz00
PermPackXyz00_DELETE o--> PackXyz00
PermPackXyz00_INSERT:User o--> PackXyz00
@enduml
```
## Business-Object-Tables, Triggers and Views
To support the RBAC system, for each business-object-table, some more artifacts are created in the database:
- a `BEFORE INSERT TRIGGER` which creates the related *RbacObject* instance,
- an `AFTER INSERT TRIGGER` which creates the related *RbacRole*s, *RbacPermission*s together with their related *RbacReference*s as well as *RbacGrant*s,
- a restricted view (e.g. *customer_rv*) through which restricted users can access the underlying data.
Not yet implemented, but planned are these actions:
- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'DELETE' permission,
- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'UPDATE' right,
- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has the 'INSERT' right for the parent-business-object.
The restricted view takes the current user from a session property and applies the hierarchy of its roles all the way down to the permissions related to the respective business-object-table.
This way, each user can only select the data they have 'SELECT'-permission for, only create those they have 'add-...'-permission, only update those they have 'UPDATE'- and only delete those they have 'DELETE'-permission to.
### Current User
The current use is taken from the session variable `hsadminng.currentSubject` which contains the name of the user as stored in the
*RbacSubject*s table. Example:
SET LOCAL hsadminng.currentSubject = 'mike@hostsharing.net';
That user is also used for historicization and audit log, but which is a different topic.
### Assuming Roles
If the session variable `hsadminng.assumedRoles` is set to a non-empty value, its content is interpreted as a list of semicolon-separated role names.
Example:
SET LOCAL hsadminng.assumedRoles = 'customer#aab:admin;customer#aac:admin';
In this case, not the current user but the assumed roles are used as a starting point for any further queries.
Roles which are not granted to the current user, directly or indirectly, cannot be assumed.
### Example
A full example is shown here:
BEGIN TRANSACTION;
SET SESSION SESSION AUTHORIZATION restricted;
SET LOCAL hsadminng.currentSubject = 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = 'customer#aab:admin;customer#aac:admin';
SELECT c.prefix, p.name as "package", ema.localPart || '@' || dom.name as "email-address"
FROM emailaddress_rv ema
JOIN domain_rv dom ON dom.uuid = ema.domainuuid
JOIN domain_rv uu ON uu.uuid = dom.domainuuid
JOIN package_rv p ON p.uuid = uu.packageuuid
JOIN customer_rv c ON c.uuid = p.customeruuid;
END TRANSACTION;
## Roles and Their Assignments for Certain Business Objects
To give you an overview of the business-object-types for the following role-examples,
check this diagram:
```plantuml
@startuml
left to right direction
' top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
entity EMailAddress
entity Domain
Domain o-- "*" EMailAddress
entity domain
domain o-- "*" Domain
entity Package
Package o.. "*" domain
entity Customer
Customer o-- "*" Package
@enduml
```
It's mostly an example hierarchy of business-object-types, but resembles a part of Hostsharing's actual hosting infrastructure.
The following diagrams show which roles are created for each business-object-type
and how they relate to roles from other business-object-types.
### Customer Roles
The highest level of the business-object-type-hierarchy is the *Customer*.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
' needs PlantUML 1.2021.14 as Markdown plugin
allow_mixing
entity "BObj customer#xyz" as boCustXyz
together {
entity "Perm customer#xyz *" as permCustomerXyzDELETE
permCustomerXyzDELETE --> boCustXyz
entity "Perm customer#xyz INSERT:package" as permCustomerXyzINSERT:package
permCustomerXyzINSERT:package --> boCustXyz
entity "Perm customer#xyz SELECT" as permCustomerXyzSELECT
permCustomerXyzSELECT--> boCustXyz
}
entity "Role customer#xyz:TENANT" as roleCustXyzTenant
roleCustXyzTenant --> permCustomerXyzSELECT
entity "Role customer#xyz:ADMIN" as roleCustXyzAdmin
roleCustXyzAdmin --> roleCustXyzTenant
roleCustXyzAdmin --> permCustomerXyzINSERT:package
entity "Role customer#xyz:OWNER" as roleCustXyzOwner
roleCustXyzOwner ..> roleCustXyzAdmin
roleCustXyzOwner --> permCustomerXyzDELETE
actor "Customer XYZ Admin" as actorCustXyzAdmin
actorCustXyzAdmin --> roleCustXyzAdmin
entity "Role administrators" as roleAdmins
roleAdmins --> roleCustXyzOwner
actor "Any Hostmaster" as actorHostmaster
actorHostmaster --> roleAdmins
@enduml
```
As you can see, there something special:
From the 'Role customer#xyz:OWNER' to the 'Role customer#xyz:admin' there is a dashed line, whereas all other lines are solid lines.
Solid lines means, that one role is granted to another and automatically assumed in all queries to the restricted views.
The dashed line means that one role is granted to another but not automatically assumed in queries to the restricted views.
The reason here is that otherwise simply too many objects would be accessible to those with the 'administrators' role and all queries would be slowed down vastly.
Grants which are not automatically assumed are still valid grants for `hsadminng.assumedRoles`.
Thus, if you want to access anything below a customer, assume its role first.
There is actually another speciality in the customer roles:
For all others, a user defined by the customer gets the owner role assigned, just for the customer, the owner's role is assigned to the 'administrators' role.
### Package Roles
One example of the business-object-type-level right below is the *Package*.
```plantuml
@startuml
' left to right direction
top to bottom direction
' hide the ugly E in a circle left to the entity name
hide circle
' use right-angled line routing
' skinparam linetype ortho
' needs PlantUML 1.2021.14 as Markdown plugin
allow_mixing
entity "BObj package#xyz00" as boPacXyz00
together {
entity "Perm package#xyz00 *" as permPackageXyzDELETE
permPackageXyzDELETE --> boPacXyz00
entity "Perm package#xyz00 INSERT:domain" as permPacXyz00INSERT:user
permPacXyz00INSERT:user --> boPacXyz00
entity "Perm package#xyz00 UPDATE" as permPacXyz00UPDATE
permPacXyz00UPDATE --> boPacXyz00
entity "Perm package#xyz00 SELECT" as permPacXyz00SELECT
permPacXyz00SELECT --> boPacXyz00
}
package {
entity "Role customer#xyz:TENANT" as roleCustXyzTenant
entity "Role customer#xyz:ADMIN" as roleCustXyzAdmin
entity "Role customer#xyz:OWNER" as roleCustXyzOwner
}
package {
entity "Role package#xyz00:OWNER" as rolePacXyz00Owner
entity "Role package#xyz00:ADMIN" as rolePacXyz00Admin
entity "Role package#xyz00:TENANT" as rolePacXyz00Tenant
}
rolePacXyz00Tenant --> permPacXyz00SELECT
rolePacXyz00Tenant --> roleCustXyzTenant
rolePacXyz00Owner --> rolePacXyz00Admin
rolePacXyz00Owner --> permPackageXyzDELETE
roleCustXyzAdmin --> rolePacXyz00Owner
roleCustXyzAdmin --> roleCustXyzTenant
roleCustXyzOwner ..> roleCustXyzAdmin
rolePacXyz00Admin --> rolePacXyz00Tenant
rolePacXyz00Admin --> permPacXyz00INSERT:user
rolePacXyz00Admin --> permPacXyz00UPDATE
actor "Package XYZ00 Admin" as actorPacXyzAdmin
actorPacXyzAdmin -l-> rolePacXyz00Admin
actor "Customer XYZ Admin" as actorCustXyzAdmin
actorCustXyzAdmin --> roleCustXyzAdmin
entity "Role administrators" as roleAdmins
roleAdmins --> roleCustXyzOwner
actor "Any Hostmaster" as actorHostmaster
actorHostmaster --> roleAdmins
@enduml
```
Initially, the customer's admin role is assigned to the package owner role.
They can use the package's admin role to hand over most management functionality to a third party.
The 'administrators' can get access through an assumed customer's admin role or directly by assuming the package's owner or admin role.
## Performance
We did not define maximum response time in our requirements,
but set a target of 7.000 customers, 15.000 packages, 150.000 Unix users, 100.000 domains and 500.000 email-addresses.
For such a dataset the response time for typical queries from a UI should be acceptable.
Also, when adding data beyond these quantities, increase in response time should be roughly linear or below.
For this, we increased the dataset by 14% and then by another 25%, ending up with 10.000 customers, almost 25.000 packages, over 174.000 unix users, over 120.000 domains and almost 750.000 email-addresses.
The performance test suite comprised 8 SELECT queries issued by an administrator, mostly with two assumed customer owner roles.
The tests started with finding a specific customer and ended with listing all accessible email-addresses joined with their domains, unix-users, packages and customers.
Find the SQL script here: `28-hs-tests.sql`.
### Two View Query Variants
We have tested two variants of the query for the restricted view,
both utilizing a PostgreSQL function like this:
FUNCTION rbac.queryAccessibleObjectUuidsOfSubjectIds(
requiredOp rbac.RbacOp,
forObjectTable varchar,
subjectIds uuid[],
maxObjects integer = 16000)
RETURNS SETOF uuid
The function returns all object uuids for which the given subjectIds (user o assumed roles) have a permission or required operation.
Let's have a look at the two view queries:
#### Using WHERE ... IN
CREATE OR REPLACE VIEW customer_rv AS
SELECT DISTINCT target.*
FROM customer AS target
WHERE target.uuid IN (
SELECT uuid
FROM rbac.queryAccessibleObjectUuidsOfSubjectIds(
'SELECT, 'customer', currentSubjectOrAssumedRolesUuids()));
This view should be automatically updatable.
Where, for updates, we actually have to check for 'UPDATE' instead of 'SELECT' operation, which makes it a bit more complicated.
With the larger dataset, the test suite initially needed over 7 seconds with this view query.
At this point the second variant was tried.
But after the initial query, the execution time was drastically reduced,
even with different query values.
Looks like the query optimizer needed some statistics to find the best path.
#### Using A JOIN
CREATE OR REPLACE VIEW customer_rv AS
SELECT DISTINCT target.*
FROM customer AS target
JOIN rbac.queryAccessibleObjectUuidsOfSubjectIds(
'SELECT, 'customer', currentSubjectOrAssumedRolesUuids()) AS allowedObjId
ON target.uuid = allowedObjId;
This view cannot is not updatable automatically,
but it was quite fast from the beginning.
### Performance Results
The following table shows the average between the second and the third repeat of the test-suite:
| Dataset | using JOIN | using WHERE IN |
|----------------:|-----------:|---------------:|
| 7000 customers | 670ms | 1040ms |
| 10000 customers | 1050ms | 1125ms |
| +43% | +57% | +8% |
The JOIN-variant is still faster, but the growth in execution time exceeded the growth of the dataset.
The WHERE-IN-variant is about 50% slower on the smaller dataset, but almost keeps its performance on the larger dataset.
Both variants a viable option, depending on other needs, e.g. updatable views.
## Access Control to RBAC-Objects
Access Control for business objects checked according to the assigned roles.
But we decided not to create such roles and permissions for the RBAC-Objects itself.
It would have overcomplicated the system and the necessary information can easily be added to the RBAC-Objects itself, mostly the `RbacGrant`s.
### RbacSubject
Users can self-register, thus to create a new RbacSubject entity, no login is required.
But such a user has no access-rights except viewing itself.
Users can view themselves.
And any user can view all other users as long as they have the same roles assigned.
As an exception, users which are assigned to global roles are not visible by other users.
At least an indirect lookup of known user-names (e.g. email address of the user) is possible
by users who have an empowered assignment of any role.
Otherwise, it would not be possible to assign roles to new users.
### RbacRole
All roles are system-defined and cannot be created or modified by any external API.
Users can view only the roles to which are granted to them.
## RbacGrant
Grant can be `empowered`, this means that the grantee user can grant the granted role to other users
and revoke grants to that role.
(TODO: access control part not yet implemented, currently all accessible roles can be granted to other users)
Grants can be `managed`, which means they are created and deleted by system-defined rules.
If a grant is not managed, it was created by an empowered user and can be deleted by empowered users.
Grants can be `assumed`, which means that they are immediately active.
If a grant is not assumed, the grantee user needs to use `assumeRoles` to activate it.
Users can see only grants of roles to which they are (directly?) assigned themselves.
TODO: If a user grants an indirect role to another user, that grant would not be visible to the user.
But if we make indirect grants visible, this would reveal too much information.
We also cannot keep the granting user in the grant because grants must survive deleted users,
e.g. if after an account was transferred to another user.

110
doc/test-concept.md Normal file
View File

@ -0,0 +1,110 @@
## Test-Concept
<!-- generated TOC begin: -->
- [Unit-Tests](#unit-tests)
- [REST-Tests](#rest-tests)
- [Integration-Tests](#integration-tests)
- [Acceptance-Tests](#acceptance-tests)
- [Performance-Tests](#performance-tests)
- [System-Integration-Tests](#system-integration-tests)
<!-- generated TOC end. -->
### General Concepts
The following test concept uses terms like "double" and "mock" (maybe in inflected form like "mocking" or "mocked"), "whitebox-test" and "blackbox-tests" and "test-fixture".
Please look up their definition in the [glossary](glossary.md)
Where our APIs should be designed in a way that it's possible, using a mocking library like *Mockito* often leads to shorter test code.
Most important for a test is, to clearly express what it actually is testing.
For this, it might help to wrap test setup and assertions into test fixture
### Kinds of Tests
Depending on the concrete aspects which we want to test, we are using different kinds of tests as described as follows.
#### Unit-Tests
In this project a *Unit* for *UnitTests* can be a single method (function), a class or even a group of classes which express a common concept.
The unit are technically whitebox-tests and count into test-code-coverage.
But the whitebox-knowledge should only be used for the [test-fixture](./glossary.md#test-fixture).
Unit-Test in this project are implemented with *JUnit Jupiter*, *Mockito* and *AssertJ*.
Unit-Tests do not use any external systems, not even a database.
They just test the unit, not any dependencies or proper integration with dependencies.
Such tests usually run very fast and should test all branches.
These Tests are always named `...UnitTest` and can automatically run in the build-process.
#### REST-Tests
At the level of REST-Controllers, *Spring's* `WebMvcTest`, a special kind of Unit-Test, are utilized to replace simple unit tests.
Such tests issue REST-requests through a mocked REST-Layer and therefore use the controllers similar to a real client.
Otherwise, the implementation technologies are like those of Unit-Tests.
Being unit-tests, also REST-tests are whitebox-tests and count into test-code-coverage.
Like other Unit-Tests, REST-Test do not use any external systems, not even a database.
They just test the REST-related parts of the unit, e.g. URL-Mappings, HTTP-Headers and proper JSON encoding of request and response data.
Other dependencies and integrations with such are not tested on this level.
Such tests usually run very fast, but should focus on REST-specific issues, leaving branch-testing to pure Unit-Tests.
These Tests are always named `...RestTest` and can automatically run in the build-process.
#### Integration-Tests
Integration-Tests in this context mean integration with support systems like databases or messaging-systems, but not integration with external systems.
Integration-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
Such tests are implemented with *JUnit Jupiter* through some sort of `@SpringBootTest`, e.g. `DataJpaTest` and usually utilize *Testcontainers* and *Docker* to wrap the supporting system, e.g. the *PostgreSQL* database.
*Mockito* can also be used for this kind of tests, to separate multiple integrations.
Integration-Tests are relatively slow and therefore should focus on the integration.
Java-internal issues should be tested through Unit-Tests.
These Tests are always named `...IntegrationTest` and can automatically run in the build-process.
##### DataJpaTest / Database-Integration-Tests
In this project, a major part of the program logic is coded in the database as stored procedures, functions and triggers.
This program logic is tested through *integration tests* using `DataJpaTest`
because pure unit tests in the database are not only cumbersome but also easily lead to large test gaps.
#### Acceptance-Tests
We define Acceptance-Tests as test which describe user-stories, respectively high-level business requirements.
Acceptance-Tests run on a fully integrated and deployed system with deployed doubles for external systems.
Acceptance-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
TODO.test: Complete the Acceptance-Tests test concept.
#### Performance-Tests
Performance-critical scenarios have to be identified and a special performance-test has to be implemented.
The implementation-technologie depends on the scenario.
Performance-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
Such tests usually are very slow and should not be automatically run in the build-pipeline but manually, after critical areas have been changed.
#### System-Integration-Tests
We define System-Integration-Tests as test in which this system is deployed in a production-like environment to test integration with external systems.
System-Integration-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
TODO.test: Complete the System-Integration-Tests test concept.

39
etc/allowed-licenses.json Normal file
View File

@ -0,0 +1,39 @@
{
"allowedLicenses": [
{ "moduleLicense": "Apache 2.0" },
{ "moduleLicense": "Apache 2" },
{ "moduleLicense": "Apache License 2.0" },
{ "moduleLicense": "Apache License, Version 2.0" },
{ "moduleLicense": "The Apache Software License, Version 2.0" },
{ "moduleLicense": "BSD License" },
{ "moduleLicense": "BSD-2-Clause" },
{ "moduleLicense": "BSD-3-Clause" },
{ "moduleLicense": "The BSD License" },
{ "moduleLicense": "CDDL 1.1" },
{ "moduleLicense": "CDDL/GPLv2+CE" },
{ "moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0" },
{ "moduleLicense": "EDL 1.0" },
{ "moduleLicense": "Eclipse Distribution License 1.0" },
{ "moduleLicense": "Eclipse Public License - v 1.0" },
{ "moduleLicense": "Eclipse Public License - v 2.0" },
{ "moduleLicense": "Eclipse Public License - v. 2.0" },
{ "moduleLicense": "Eclipse Public License - v1.0" },
{ "moduleLicense": "Eclipse Public License v 2.0" },
{ "moduleLicense": "Eclipse Public License v. 2.0" },
{ "moduleLicense": "GNU Library General Public License v2.1 or later" },
{ "moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception" },
{ "moduleLicense": "GPL2 w/ CPE" },
{ "moduleLicense": "MIT License" },
{ "moduleLicense": "MIT" },
{ "moduleLicense": "The MIT License (MIT)" },
{ "moduleLicense": "The MIT License" },
{ "moduleName": "org.springdoc:springdoc-openapi" }
]
}

27
etc/docker-compose.yml Normal file
View File

@ -0,0 +1,27 @@
version: '3.8'
services:
postgres:
image: postgres-with-contrib:15.5-bookworm
container_name: custom-postgres
environment:
POSTGRES_PASSWORD: password
volumes:
- ./postgresql-log-slow-queries.conf:/etc/postgresql/postgresql.conf
ports:
- "5432:5432"
command:
- bash
- -c
- >
apt-get update &&
apt-get install -y postgresql-contrib &&
docker-entrypoint.sh postgres -c config_file=/etc/postgresql/postgresql.conf
deploy:
resources:
limits:
cpus: '2'
memory: 8G
reservations:
cpus: '1'
memory: 2G

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
<suppress>
<notes><![CDATA[
Cyclic references are not possible if file comes in JSON text format.
]]></notes>
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
<cpe>cpe:/a:fasterxml:jackson-databind</cpe>
</suppress>
<suppress>
<notes><![CDATA[
Internal tooling, not exposed to the Internet.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.pitest/pitest\-command\-line@.*$</packageUrl>
<cpe>cpe:/a:line:line</cpe>
</suppress>
</suppressions>

View File

@ -0,0 +1,10 @@
shared_preload_libraries = 'pg_stat_statements,auto_explain'
log_min_duration_statement = 1000
log_statement = 'all'
log_duration = on
pg_stat_statements.track = all
auto_explain.log_min_duration = '1s' # Logs queries taking longer than 1 second
auto_explain.log_analyze = on # Include actual run times
auto_explain.log_buffers = on # Include buffer usage statistics
auto_explain.log_format = 'json' # Format the log output in JSON
listen_addresses = '*'

View File

@ -1,53 +1,17 @@
rootProject.name=hsadmin-ng
profile=dev
# Gradle Java Toolchain-support
org.gradle.java.installations.auto-detect=true
org.gradle.java.installations.auto-download=true
# org.gradle.jvm.toolchain.install.adoptopenjdk.baseUri
# org.gradle.java.installations.paths -- uncomment and set if needed
# Build properties
node_version=10.15.3
npm_version=6.4.1
yarn_version=1.13.0
# Spring BOM overrides
# currently none necessary
# Dependency versions
jhipster_dependencies_version=2.1.1
# The spring-boot version should match the one managed by
# https://mvnrepository.com/artifact/io.github.jhipster/jhipster-dependencies/${jhipster_dependencies_version}
spring_boot_version=2.0.8.RELEASE
# The hibernate version should match the one managed by
# https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/${spring-boot.version} -->
hibernate_version=5.2.17.Final
mapstruct_version=1.2.0.Final
liquibase_hibernate5_version=3.6
liquibaseTaskPrefix=liquibase
# jhipster-needle-gradle-property - JHipster will add additional properties here
## below are some of the gradle performance improvement settings that can be used as required, these are not enabled by default
## The Gradle daemon aims to improve the startup and execution time of Gradle.
## The daemon is enabled by default in Gradle 3+ setting this to false will disable this.
## TODO: disable daemon on CI, since builds should be clean and reliable on servers
## https://docs.gradle.org/current/userguide/gradle_daemon.html#sec:ways_to_disable_gradle_daemon
## un comment the below line to disable the daemon
#org.gradle.daemon=false
## Specifies the JVM arguments used for the daemon process.
## The setting is particularly useful for tweaking memory settings.
## Default value: -Xmx1024m -XX:MaxPermSize=256m
## un comment the below line to override the daemon defaults
#org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=256m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
## When configured, Gradle will run in incubating parallel mode.
## This option should only be used with decoupled projects. More details, visit
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
## un comment the below line to enable parallel mode
#org.gradle.parallel=true
## Enables new incubating mode that makes Gradle selective when configuring projects.
## Only relevant projects are configured which results in faster builds for large multi-projects.
## http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
## un comment the below line to enable the selective mode
#org.gradle.configureondemand=true
# TODO: can be removed if all dependencies are JDK 16 compliant, check with `gw clean check`
# and check output for "cannot access class ... because module jdk.compiler does not export ..."
org.gradle.jvmargs= \
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

View File

@ -1,35 +0,0 @@
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:0.9.11"
}
}
apply plugin: com.google.cloud.tools.jib.gradle.JibPlugin
jib {
from {
image = 'openjdk:8-jre-alpine'
}
to {
image = 'hsadminng:latest'
}
container {
entrypoint = ['sh', '-c', 'chmod +x /entrypoint.sh && sync && /entrypoint.sh']
ports = ['8080']
environment = [
SPRING_OUTPUT_ANSI_ENABLED: 'ALWAYS',
JHIPSTER_SLEEP: '0'
]
useCurrentTimestamp = true
}
}
task copyWwwIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
jibDockerBuild.dependsOn copyWwwIntoStatic

View File

@ -1,63 +0,0 @@
import org.gradle.internal.os.OperatingSystem
apply plugin: 'org.springframework.boot'
apply plugin: 'com.moowork.node'
dependencies {
compile "org.springframework.boot:spring-boot-devtools"
compile "com.h2database:h2"
}
def profiles = 'dev'
if (project.hasProperty('no-liquibase')) {
profiles += ',no-liquibase'
}
if (project.hasProperty('tls')) {
profiles += ',tls'
}
springBoot {
buildInfo {
properties {
time = null
}
}
}
bootRun {
args = []
}
task webpackBuildDev(type: NpmTask) {
inputs.files(fileTree('src/main/webapp/'))
def webpackDevFiles = fileTree('webpack//')
webpackDevFiles.exclude('webpack.prod.js')
inputs.files(webpackDevFiles)
outputs.files(fileTree("build/www/"))
dependsOn npmInstall
args = ["run", "webpack:build"]
}
task copyIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
processResources {
filesMatching('**/application.yml') {
filter {
it.replace('#project.version#', version)
}
filter {
it.replace('#spring.profiles.active#', profiles)
}
}
}
processResources.dependsOn webpackBuildDev
copyIntoStatic.dependsOn processResources
bootJar.dependsOn copyIntoStatic

View File

@ -1,63 +0,0 @@
apply plugin: 'org.springframework.boot'
apply plugin: 'com.gorylenko.gradle-git-properties'
apply plugin: 'com.moowork.node'
dependencies {
testCompile "com.h2database:h2"
}
def profiles = 'prod'
if (project.hasProperty('no-liquibase')) {
profiles += ',no-liquibase'
}
if (project.hasProperty('swagger')) {
profiles += ',swagger'
}
springBoot {
buildInfo()
}
bootRun {
args = []
}
task webpack_test(type: NpmTask, dependsOn: 'npm_install') {
args = ["run", "webpack:test"]
}
task webpack(type: NpmTask, dependsOn: 'npm_install') {
args = ["run", "webpack:prod"]
}
task copyIntoStatic (type: Copy) {
from 'build/www/'
into 'build/resources/main/static'
}
processResources {
filesMatching('**/application.yml') {
filter {
it.replace('#project.version#', version)
}
filter {
it.replace('#spring.profiles.active#', profiles)
}
}
}
generateGitProperties {
onlyIf {
!source.isEmpty()
}
}
gitProperties {
keys = ['git.branch', 'git.commit.id.abbrev', 'git.commit.id.describe']
}
test.dependsOn webpack_test
processResources.dependsOn webpack
copyIntoStatic.dependsOn processResources
bootJar.dependsOn copyIntoStatic

View File

@ -1,47 +0,0 @@
apply plugin: "org.sonarqube"
apply plugin: 'jacoco'
jacoco {
toolVersion = '0.8.2'
}
jacocoTestReport {
reports {
xml.enabled true
}
}
sonarqube {
properties {
property "sonar.host.url", "http://localhost:9001"
property "sonar.exclusions", "src/main/webapp/content/**/*.*,src/main/webapp/i18n/*.js, build/www/**/*.*"
property "sonar.issue.ignore.multicriteria", "S3437,S4502,S4684,UndocumentedApi,BoldAndItalicTagsCheck"
// Rule https://sonarcloud.io/coding_rules?open=Web%3ABoldAndItalicTagsCheck&rule_key=Web%3ABoldAndItalicTagsCheck is ignored. Even if we agree that using the "i" tag is an awful practice, this is what is recommended by http://fontawesome.io/examples/
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.resourceKey", ">src/main/webapp/app/**/*.*"
property "sonar.issue.ignore.multicriteria.BoldAndItalicTagsCheck.ruleKey", "Web:BoldAndItalicTagsCheck"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS3437&rule_key=squid%3AS3437 is ignored, as a JPA-managed field cannot be transient
property "sonar.issue.ignore.multicriteria.S3437.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S3437.ruleKey", "squid:S3437"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AUndocumentedApi&rule_key=squid%3AUndocumentedApi is ignored, as we want to follow "clean code" guidelines and classes, methods and arguments names should be self-explanatory
property "sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey", "squid:UndocumentedApi"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4502&rule_key=squid%3AS4502 is ignored, as for JWT tokens we are not subject to CSRF attack
property "sonar.issue.ignore.multicriteria.S4502.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S4502.ruleKey", "squid:S4502"
// Rule https://sonarcloud.io/coding_rules?open=squid%3AS4684&rule_key=squid%3AS4684
property "sonar.issue.ignore.multicriteria.S4684.resourceKey", "src/main/java/**/*"
property "sonar.issue.ignore.multicriteria.S4684.ruleKey", "squid:S4684"
property "sonar.jacoco.reportPaths", "${project.buildDir}/jacoco/test.exec"
property "sonar.java.codeCoveragePlugin", "jacoco"
property "sonar.typescript.lcov.reportPaths", "${project.buildDir}/test-results/lcov.info"
property "sonar.junit.reportPaths", "${project.buildDir}/test-results"
property "sonar.sources", "${project.projectDir}/src/main/"
property "sonar.tests", "${project.projectDir}/src/test/"
}
}

View File

@ -1,28 +0,0 @@
/*
* Plugin that provides API-first development using OpenAPI-generator to
* generate Spring-MVC endpoint stubs at compile time from an OpenAPI definition file
*/
apply plugin: 'org.openapi.generator'
openApiGenerate {
generatorName = "spring"
inputSpec = "$rootDir/src/main/resources/swagger/api.yml".toString()
outputDir = "$buildDir/openapi".toString()
apiPackage = "org.hostsharing.hsadminng.web.api"
modelPackage = "org.hostsharing.hsadminng.web.api.model"
apiFilesConstrainedTo = [""]
modelFilesConstrainedTo = [""]
supportingFilesConstrainedTo = ["ApiUtil.java"]
configOptions = [delegatePattern: "true"]
validateSpec = true
}
sourceSets {
main {
java {
srcDir file("${project.buildDir.path}/openapi/src/main/java")
}
}
}
compileJava.dependsOn("openApiGenerate")

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,3 +0,0 @@
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}

309
gradlew vendored
View File

@ -1,78 +1,127 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

56
gradlew.bat vendored
View File

@ -1,4 +1,20 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -9,19 +25,23 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +55,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,38 +65,26 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

1
lombok.config Normal file
View File

@ -0,0 +1 @@
lombok.addLombokGeneratedAnnotation = true

18180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,126 +0,0 @@
{
"name": "hsadmin-ng",
"version": "0.0.0",
"description": "Description for hsadminNg",
"private": true,
"license": "UNLICENSED",
"cacheDirectories": [
"node_modules"
],
"dependencies": {
"@angular/common": "7.2.4",
"@angular/compiler": "7.2.4",
"@angular/core": "7.2.4",
"@angular/forms": "7.2.4",
"@angular/platform-browser": "7.2.4",
"@angular/platform-browser-dynamic": "7.2.4",
"@angular/router": "7.2.4",
"@fortawesome/angular-fontawesome": "0.3.0",
"@fortawesome/fontawesome-svg-core": "1.2.14",
"@fortawesome/free-solid-svg-icons": "5.7.1",
"@ng-bootstrap/ng-bootstrap": "4.0.2",
"@ngx-translate/core": "11.0.1",
"@ngx-translate/http-loader": "4.0.0",
"bootstrap": "4.2.1",
"core-js": "2.6.4",
"moment": "2.24.0",
"ng-jhipster": "0.9.1",
"ngx-cookie": "2.0.1",
"ngx-infinite-scroll": "7.0.1",
"ngx-webstorage": "2.0.1",
"rxjs": "6.4.0",
"swagger-ui": "2.2.10",
"tslib": "1.9.3",
"zone.js": "0.8.29"
},
"devDependencies": {
"@angular/cli": "7.3.1",
"@angular/compiler-cli": "7.2.4",
"@ngtools/webpack": "7.3.1",
"@types/jest": "24.0.0",
"@types/node": "10.12.24",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"autoprefixer": "9.4.7",
"browser-sync": "2.26.3",
"browser-sync-webpack-plugin": "2.2.2",
"cache-loader": "2.0.1",
"codelyzer": "4.5.0",
"copy-webpack-plugin": "4.6.0",
"css-loader": "2.1.0",
"file-loader": "3.0.1",
"fork-ts-checker-webpack-plugin": "0.5.2",
"friendly-errors-webpack-plugin": "1.7.0",
"generator-jhipster": "5.8.2",
"html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0",
"husky": "1.3.1",
"jest": "24.1.0",
"jest-junit": "6.2.1",
"jest-preset-angular": "6.0.2",
"jest-sonar-reporter": "2.0.0",
"lint-staged": "8.1.3",
"merge-jsons-webpack-plugin": "1.0.18",
"mini-css-extract-plugin": "0.5.0",
"moment-locales-webpack-plugin": "1.0.7",
"optimize-css-assets-webpack-plugin": "5.0.1",
"prettier": "1.16.4",
"reflect-metadata": "0.1.13",
"rimraf": "2.6.3",
"simple-progress-webpack-plugin": "1.1.2",
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.2",
"thread-loader": "2.1.2",
"to-string-loader": "1.1.5",
"ts-loader": "5.3.3",
"tslint": "5.12.1",
"tslint-config-prettier": "1.18.0",
"tslint-loader": "3.6.0",
"typescript": "3.2.4",
"postcss-loader": "3.0.0",
"webpack": "4.29.3",
"webpack-cli": "3.2.3",
"webpack-dev-server": "3.1.14",
"webpack-merge": "4.2.1",
"webpack-notifier": "1.7.0",
"webpack-visualizer-plugin": "0.1.11",
"workbox-webpack-plugin": "3.6.3",
"write-file-webpack-plugin": "4.5.0"
},
"engines": {
"node": ">=8.9.0"
},
"lint-staged": {
"{,src/**/}*.{md,json,ts,css,scss}": [
"prettier --write",
"git add"
]
},
"scripts": {
"prettier:format": "prettier --write \"{,src/**/}*.{md,json,ts,css,scss}\"",
"lint": "tslint --project tsconfig.json -e 'node_modules/**'",
"lint:fix": "npm run lint -- --fix",
"ngc": "ngc -p tsconfig-aot.json",
"cleanup": "rimraf build/{aot,www}",
"clean-www": "rimraf build//www/app/{src,build/}",
"start": "npm run webpack:dev",
"start-tls": "npm run webpack:dev -- --env.tls",
"serve": "npm run start",
"build": "npm run webpack:prod",
"test": "npm run lint && jest --coverage --logHeapUsage -w=2 --config src/test/javascript/jest.conf.js",
"test:watch": "npm run test -- --watch",
"webpack:dev": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --env.stats=minimal",
"webpack:dev-verbose": "npm run webpack-dev-server -- --config webpack/webpack.dev.js --inline --hot --port=9060 --watch-content-base --profile --progress --env.stats=normal",
"webpack:build:main": "npm run webpack -- --config webpack/webpack.dev.js --env.stats=minimal",
"webpack:build": "npm run cleanup && npm run webpack:build:main",
"webpack:prod:main": "npm run webpack -- --config webpack/webpack.prod.js --profile",
"webpack:prod": "npm run cleanup && npm run webpack:prod:main && npm run clean-www",
"webpack:test": "npm run test",
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"webpack": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js"
},
"jestSonar": {
"reportPath": "build/test-results/jest",
"reportFile": "TESTS-results-sonar.xml"
}
}

View File

@ -1,5 +0,0 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -1,7 +0,0 @@
{
"*": {
"target": "http://localhost:8080",
"secure": false,
"loglevel": "debug"
}
}

View File

@ -1 +1,14 @@
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
gradlePluginPortal()
mavenCentral()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = 'hsadmin-ng'

28
sql/29-hs-statistics.sql Normal file
View File

@ -0,0 +1,28 @@
-- ========================================================
-- Some Business Table Statistics
-- --------------------------------------------------------
drop view if exists "BusinessTableStatisticsV";
create view "BusinessTableStatisticsV" as
select no,
to_char("count", '999 999 999') as "count",
to_char("required", '999 999 999') as "required",
to_char("count"::float / "required"::float, '990.999') as "factor",
"table"
from (select 1 as no, count(*) as "count", 7000 as "required", 'customers' as "table"
from customer
union
select 2 as no, count(*) as "count", 15000 as "required", 'packages' as "table"
from package
union
select 3 as no, count(*) as "count", 150000 as "required", 'domain' as "table"
from domain
union
select 4 as no, count(*) as "count", 100000 as "required", 'domain' as "table"
from domain
union
select 5 as no, count(*) as "count", 500000 as "required", 'emailaddress' as "table"
from emailaddress) totals
order by totals.no;
select * from "BusinessTableStatisticsV";

39
sql/historization.sql Normal file
View File

@ -0,0 +1,39 @@
-- ========================================================
-- Historization twiddle
-- --------------------------------------------------------
rollback;
begin transaction;
call defineContext('historization testing', null, 'superuser-alex@hostsharing.net',
-- 'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); -- prod+test
'hs_booking_project#D-1000313-D-1000313defaultproject:ADMIN'); -- prod+test
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); -- prod
-- 'hs_booking_project#D-1000300-mimdefaultproject:ADMIN'); -- test
-- update hs_hosting_asset set caption='lug00 b' where identifier = 'lug00' and type = 'MANAGED_WEBSPACE'; -- prod
-- update hs_hosting_asset set caption='hsh00 A ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
-- update hs_hosting_asset set caption='hsh00 B ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
-- insert into hs_hosting_asset
-- (uuid, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, identifier, caption, config, alarmcontactuuid)
-- values
-- (uuid_generate_v4(), null, 'EMAIL_ADDRESS', 'bbda5895-0569-4e20-bb4c-34f3a38f3f63'::uuid, null,
-- 'new@thi.example.org', 'some new E-Mail-Address', '{}'::jsonb, null);
delete from hs_hosting_asset where uuid='5aea68d2-3b55-464f-8362-b05c76c5a681'::uuid;
commit;
-- single version at point in time
-- set hsadminng.tx_history_txid to (select max(txid) from base.tx_context where txtimestamp<='2024-08-27 12:13:13.450821');
set hsadminng.tx_history_txid to '';
set hsadminng.tx_history_timestamp to '2024-08-29 12:42';
-- all versions
select base.tx_history_txid(), txc.txtimestamp, txc.currentSubject, txc.currentTask, haex.*
from hs_hosting_asset_ex haex
join base.tx_context txc on haex.txid=txc.txid
where haex.identifier = 'test@thi.example.org';
select uuid, version, type, identifier, caption from hs_hosting_asset_hv p where identifier = 'test@thi.example.org';
select pg_current_xact_id();

50
sql/rbac-tests.sql Normal file
View File

@ -0,0 +1,50 @@
-- ========================================================
-- Some Tests
-- --------------------------------------------------------
select rbac.isGranted(rbac.findRoleId('administrators'), rbac.findRoleId('test.package#aaa00:OWNER'));
select rbac.isGranted(rbac.findRoleId('test.package#aaa00:OWNER'), rbac.findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('test.package#aaa00:OWNER'), findRoleId('administrators'));
-- call rbac.grantRoleToRole(findRoleId('administrators'), findRoleId('test.package#aaa00:OWNER'));
select count(*)
FROM rbac.queryAllPermissionsOfSubjectIdForObjectUuids(rbac.findRbacSubject('superuser-fran@hostsharing.net'),
ARRAY(select uuid from test.customer where reference < 1100000));
select count(*)
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-fran@hostsharing.net'));
select *
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('alex@example.com'));
select *
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('rosa@example.com'));
select *
FROM rbac.queryAllRbacSubjectsWithPermissionsFor(rbac.findEffectivePermissionId('customer',
(SELECT uuid FROM rbac.RbacObject WHERE objectTable = 'customer' LIMIT 1),
'add-package'));
select *
FROM rbac.queryAllRbacSubjectsWithPermissionsFor(rbac.findEffectivePermissionId('package',
(SELECT uuid FROM rbac.RbacObject WHERE objectTable = 'package' LIMIT 1),
'DELETE'));
DO LANGUAGE plpgsql
$$
DECLARE
userId uuid;
result bool;
BEGIN
userId = rbac.findRbacSubject('superuser-alex@hostsharing.net');
result = (SELECT * FROM rbac.isPermissionGrantedToSubject(rbac.findPermissionId('package', 94928, 'add-package'), userId));
IF (result) THEN
RAISE EXCEPTION 'expected permission NOT to be granted, but it is';
end if;
result = (SELECT * FROM rbac.isPermissionGrantedToSubject(rbac.findPermissionId('package', 94928, 'SELECT'), userId));
IF (NOT result) THEN
RAISE EXCEPTION 'expected permission to be granted, but it is NOT';
end if;
RAISE LOG 'isPermissionGrantedToSubjectId test passed';
END;
$$;

View File

@ -0,0 +1,89 @@
-- ========================================================
-- Options for SELECT under RBAC rules
-- --------------------------------------------------------
-- access control via view policy and isPermissionGrantedToSubject - way too slow (33 s 617ms for 1 million rows)
SET SESSION AUTHORIZATION DEFAULT;
CREATE ROLE admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;
CREATE ROLE restricted;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted;
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE customer DISABLE ROW LEVEL SECURITY;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
ALTER TABLE customer FORCE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS customer_policy ON customer;
CREATE POLICY customer_policy ON customer
FOR SELECT
TO restricted
USING (
-- id=1000
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid())
);
SET SESSION AUTHORIZATION restricted;
SET hsadminng.currentSubject TO 'alex@example.com';
SELECT * from customer;
-- access control via view-rule and isPermissionGrantedToSubject - way too slow (35 s 580 ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
DROP VIEW cust_view;
CREATE VIEW cust_view AS
SELECT * FROM test.customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT * FROM test.customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid());
SELECT * from cust_view LIMIT 10;
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-alex@hostsharing.net'));
-- access control via view-rule with join to recursive permissions - really fast (38ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
ALTER TABLE test.customer ENABLE ROW LEVEL SECURITY;
DROP VIEW IF EXISTS cust_view;
CREATE OR REPLACE VIEW cust_view AS
SELECT *
FROM test.customer;
CREATE OR REPLACE RULE "_RETURN" AS
ON SELECT TO cust_view
DO INSTEAD
SELECT c.uuid, c.reference, c.prefix FROM test.customer AS c
JOIN rbac.queryAllPermissionsOfSubjectId(rbac.currentSubjectUuid()) AS p
ON p.objectTable='test.customer' AND p.objectUuid=c.uuid;
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
SET hsadminng.currentSubject TO 'alex@example.com';
SELECT * from cust_view;
-- access control via view with join to recursive permissions - really fast (38ms for 1 million rows)
SET SESSION SESSION AUTHORIZATION DEFAULT;
ALTER TABLE customer ENABLE ROW LEVEL SECURITY;
DROP VIEW IF EXISTS cust_view;
CREATE OR REPLACE VIEW cust_view AS
SELECT c.uuid, c.reference, c.prefix
FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(rbac.currentSubjectUuid()) AS p
ON p.objectUuid=c.uuid;
GRANT ALL PRIVILEGES ON cust_view TO restricted;
SET SESSION SESSION AUTHORIZATION restricted;
-- SET hsadminng.currentSubject TO 'alex@example.com';
SET hsadminng.currentSubject TO 'superuser-alex@hostsharing.net';
-- SET hsadminng.currentSubject TO 'aaaaouq@example.com';
SELECT * from cust_view where reference=1144150;
select rr.uuid, rr.type from rbac.RbacGrants g
join rbac.RbacReference RR on g.ascendantUuid = RR.uuid
where g.descendantUuid in (
select uuid from rbac.queryAllPermissionsOfSubjectId(findRbacSubject('alex@example.com'))
where objectTable='test.customer');
call rbac.grantRoleToUser(rbac.findRoleId('test.customer#aaa:ADMIN'), rbac.findRbacSubject('aaaaouq@example.com'));
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('aaaaouq@example.com'));

2
sql/rbac.sql Normal file
View File

@ -0,0 +1,2 @@

View File

@ -0,0 +1,175 @@
-- just a permanent playground to explore optimization of the central recursive CTE query for RBAC
select * from hs_statistics_v;
-- ========================================================
-- This is the extracted recursive CTE query to determine the visible object UUIDs of a single table
-- (and optionally the hosting-asset-type) as a separate VIEW.
-- In the generated code this is part of the hs_hosting_asset_rv VIEW.
drop view if exists hs_hosting_asset_example_gv;
create view hs_hosting_asset_example_gv as
with recursive
recursive_grants as (
select distinct rbacgrants.descendantuuid,
rbacgrants.ascendantuuid,
1 as level,
true
from rbacgrants
where (rbacgrants.ascendantuuid = any (rbac.currentSubjectOrAssumedRolesUuids()))
and rbacgrants.assumed
union all
select distinct g.descendantuuid,
g.ascendantuuid,
grants.level + 1 as level,
assertTrue(grants.level < 22, 'too many grant-levels: ' || grants.level)
from rbacgrants g
join recursive_grants grants on grants.descendantuuid = g.ascendantuuid
where g.assumed
),
grant_count as (
select count(*) as grant_count from recursive_grants
),
count_check as (
select assertTrue((select grant_count from grant_count) < 600000,
'too many grants for current subjects: ' || (select grant_count from grant_count)) as valid
)
select distinct perm.objectuuid
from recursive_grants
join rbacpermission perm on recursive_grants.descendantuuid = perm.uuid
join rbacobject obj on obj.uuid = perm.objectuuid
join count_check cc on cc.valid
where obj.objecttable::text = 'hs_hosting_asset'::text
-- with/without this type condition
-- and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
;
-- -----------------------------------------------------------------------------------------------
-- A query just on the above view, only determining visible objects, no JOIN with business data:
rollback transaction;
begin transaction;
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
SET TRANSACTION READ ONLY;
EXPLAIN ANALYZE select * from hs_hosting_asset_example_gv;
end transaction ;
-- ========================================================
-- An example for a restricted view (_rv) similar to the one generated by our RBAC system,
-- but using the above separate VIEW to determine the visible objects.
drop view if exists hs_hosting_asset_example_rv;
create view hs_hosting_asset_example_rv as
with accessible_hs_hosting_asset_uuids as (
select * from hs_hosting_asset_example_gv
)
select target.*
from hs_hosting_asset target
where (target.uuid in (select accessible_hs_hosting_asset_uuids.objectuuid
from accessible_hs_hosting_asset_uuids));
-- -------------------------------------------------------------------------------
-- performing several queries on the above view to determine average performance:
rollback transaction;
DO language plpgsql $$
DECLARE
start_time timestamp;
end_time timestamp;
total_time interval;
letter char(1);
BEGIN
start_time := clock_timestamp();
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
SET TRANSACTION READ ONLY;
FOR i IN 0..25 LOOP
letter := chr(i+ascii('a'));
PERFORM count(*) from (
-- An example for a business query based on the view:
select type, uuid, identifier, caption
from hs_hosting_asset_example_rv
where type = 'EMAIL_ADDRESS'
and identifier like letter || '%'
-- end of the business query example.
) AS timed;
END LOOP;
end_time := clock_timestamp();
total_time := end_time - start_time;
RAISE NOTICE 'average execution time: %', total_time/26;
END;
$$;
-- average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN'
-- joined with business query for all 'EMAIL_ADDRESSES':
-- D-1000000-hsh D-1000300-mih
-- - without type comparison in rbacobject: ~3.30 - ~3.49 ~0.23
-- - with type comparison in rbacobject: ~2.99 - ~3.08 ~0.21
-- -------------------------------------------------------------------------------
-- and a single query, so EXPLAIN can be used
rollback transaction;
begin transaction;
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
SET TRANSACTION READ ONLY;
EXPLAIN SELECT * from (
-- An example for a business query based on the view:
select type, uuid, identifier, caption
from hs_hosting_asset_example_rv
where type = 'EMAIL_ADDRESS'
-- and identifier like 'b%'
-- end of the business query example.
) ha;
end transaction;
-- =============================================================================
-- extending the rbacobject table:
alter table rbacobject
-- just for performance testing, we would need a joined enum or a varchar(16) which would make it slow
add column type hshostingassettype;
-- and fill the type column with hs_hosting_asset types:
rollback transaction;
begin transaction;
call defineContext('setting rbacobject.type from hs_hosting_asset.type', null, 'superuser-alex@hostsharing.net');
UPDATE rbacobject
SET type = hs.type
FROM hs_hosting_asset hs
WHERE rbacobject.uuid = hs.uuid;
end transaction;
-- check the result:
select
(select count(*) as "total" from rbacobject),
(select count(*) as "not null" from rbacobject where type is not null),
(select count(*) as "null" from rbacobject where type is null);

View File

@ -1,14 +0,0 @@
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
classes/
generated-sources/
generated-test-sources/
h2db/
maven-archiver/
maven-status/
reports/
surefire-reports/
test-classes/
test-results/
www/
!*.jar
!*.war

View File

@ -1,20 +0,0 @@
FROM openjdk:8-jre-alpine
ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \
JHIPSTER_SLEEP=0 \
JAVA_OPTS=""
# Add a jhipster user to run our application so that it doesn't need to run as root
RUN adduser -D -s /bin/sh jhipster
WORKDIR /home/jhipster
ADD entrypoint.sh entrypoint.sh
RUN chmod 755 entrypoint.sh && chown jhipster:jhipster entrypoint.sh
USER jhipster
ENTRYPOINT ["./entrypoint.sh"]
EXPOSE 8080
ADD *.war app.war

View File

@ -1,15 +0,0 @@
version: '2'
services:
hsadminng-app:
image: hsadminng
environment:
- _JAVA_OPTIONS=-Xmx512m -Xms256m
- SPRING_PROFILES_ACTIVE=prod,swagger
- SPRING_DATASOURCE_URL=jdbc:postgresql://hsadminng-postgresql:5432/hsadminNg
- JHIPSTER_SLEEP=10 # gives time for the database to boot before the application
ports:
- 8080:8080
hsadminng-postgresql:
extends:
file: postgresql.yml
service: hsadminng-postgresql

View File

@ -1,4 +0,0 @@
#!/bin/sh
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar "${HOME}/app.war" "$@"

View File

@ -1,15 +0,0 @@
version: '2'
services:
jenkins:
image: jenkins:latest
ports:
- 49001:8080
- 50000:50000
# uncomment for docker in docker
#privileged: true
#volumes:
# enable persistent volume (warning: make sure that the local jenkins_home folder is created)
#- ~/volumes/jenkins_home:/var/jenkins_home
# mount docker sock and binary for docker in docker (only works on linux)
#- /var/run/docker.sock:/var/run/docker.sock
#- /usr/bin/docker:/usr/bin/docker

View File

@ -1,11 +0,0 @@
version: '2'
services:
hsadminng-postgresql:
image: postgres:10.4
# volumes:
# - ~/volumes/jhipster/hsadminNg/postgresql/:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=hsadminNg
- POSTGRES_PASSWORD=
ports:
- 5432:5432

View File

@ -1,7 +0,0 @@
version: '2'
services:
hsadminng-sonar:
image: sonarqube:7.1
ports:
- 9001:9000
- 9092:9092

View File

@ -1,6 +0,0 @@
version: '2'
services:
swagger-editor:
image: swaggerapi/swagger-editor:latest
ports:
- 7742:8080

View File

@ -0,0 +1,12 @@
package net.hostsharing.hsadminng;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HsadminNgApplication {
public static void main(String[] args) {
SpringApplication.run(HsadminNgApplication.class, args);
}
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JsonObjectMapperConfiguration {
@Bean
@Primary
public Jackson2ObjectMapperBuilder customObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modules(new JsonNullableModule(), new JavaTimeModule())
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}

View File

@ -0,0 +1,14 @@
package net.hostsharing.hsadminng.config;
import org.hibernate.dialect.PostgreSQLDialect;
import static org.hibernate.dialect.DatabaseVersion.make;
@SuppressWarnings("unused") // configured in application.yml
public class PostgresCustomDialect extends PostgreSQLDialect {
public PostgresCustomDialect() {
super(make(15, 5));
}
}

View File

@ -0,0 +1,161 @@
package net.hostsharing.hsadminng.context;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static java.util.function.Predicate.not;
import static org.springframework.transaction.annotation.Propagation.MANDATORY;
@Service
@AllArgsConstructor
public class Context {
private static final Set<String> HEADERS_TO_IGNORE = Set.of(
"accept-encoding",
"connection",
"content-length",
"host",
"user-agent");
@PersistenceContext
private EntityManager em;
@Autowired(required = false)
private HttpServletRequest request;
@Transactional(propagation = MANDATORY)
public void define(final String currentSubject) {
define(currentSubject, null);
}
@Transactional(propagation = MANDATORY)
public void define(final String currentSubject, final String assumedRoles) {
define(toTask(request), toCurl(request), currentSubject, assumedRoles);
}
@Transactional(propagation = MANDATORY)
public void define(
final String currentTask,
final String currentRequest,
final String currentSubject,
final String assumedRoles) {
final var query = em.createNativeQuery("""
call base.defineContext(
cast(:currentTask as varchar(127)),
cast(:currentRequest as text),
cast(:currentSubject as varchar(63)),
cast(:assumedRoles as varchar(1023)));
""");
query.setParameter("currentTask", shortenToMaxLength(currentTask, 127));
query.setParameter("currentRequest", currentRequest);
query.setParameter("currentSubject", currentSubject);
query.setParameter("assumedRoles", assumedRoles != null ? assumedRoles : "");
query.executeUpdate();
}
public String fetchCurrentTask() {
return (String) em.createNativeQuery("select current_setting('hsadminng.currentTask');").getSingleResult();
}
public String fetchCurrentSubject() {
return String.valueOf(em.createNativeQuery("select base.currentSubject()").getSingleResult());
}
public UUID fetchCurrentSubjectUuid() {
return (UUID) em.createNativeQuery("select rbac.currentSubjectUuid()", UUID.class).getSingleResult();
}
public String[] fetchAssumedRoles() {
return (String[]) em.createNativeQuery("select base.assumedRoles() as roles", String[].class).getSingleResult();
}
public UUID[] fetchCurrentSubjectOrAssumedRolesUuids() {
return (UUID[]) em.createNativeQuery("select rbac.currentSubjectOrAssumedRolesUuids() as uuids", UUID[].class).getSingleResult();
}
public static String getCallerMethodNameFromStackFrame(final int skipFrames) {
final Optional<StackWalker.StackFrame> caller =
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.walk(frames -> frames
.skip(skipFrames)
.filter(c -> c.getDeclaringClass() != Context.class)
.filter(c -> c.getDeclaringClass()
.getPackageName()
.startsWith("net.hostsharing.hsadminng"))
.filter(c -> !c.getDeclaringClass().getName().contains("$$SpringCGLIB$$"))
.findFirst());
return caller.map(
c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName())
.orElse("unknown");
}
private String toTask(final HttpServletRequest request) {
if (isRequestScopeAvailable()) {
return request.getMethod() + " " + request.getRequestURI();
} else {
return getCallerMethodNameFromStackFrame(2);
}
}
@SneakyThrows
private String toCurl(final HttpServletRequest request) {
if (!isRequestScopeAvailable()) {
return null;
}
var curlCommand = "curl -0 -v";
// append method
curlCommand += " -X " + request.getMethod();
// append request url
curlCommand += " " + request.getRequestURI();
// append headers
final var headers = Collections.list(request.getHeaderNames()).stream()
.filter(not(HEADERS_TO_IGNORE::contains))
.collect(Collectors.toSet());
for (String headerName : headers) {
final var headerValue = request.getHeader(headerName);
curlCommand += " \\" + System.lineSeparator() + String.format("-H '%s:%s'", headerName, headerValue);
}
// body
final String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
if (!StringUtils.isEmpty(body)) {
curlCommand += " \\" + System.lineSeparator() + "--data-binary @- ";
curlCommand +=
"<< EOF" + System.lineSeparator() + System.lineSeparator() + body + System.lineSeparator() + "EOF";
}
return curlCommand;
}
private boolean isRequestScopeAvailable() {
return RequestContextHolder.getRequestAttributes() != null;
}
private static String shortenToMaxLength(final String raw, final int maxLength) {
if (raw == null) {
return "";
}
if (raw.length() <= maxLength) {
return raw;
}
return raw.substring(0, maxLength - 3) + "...";
}
}

View File

@ -0,0 +1,45 @@
package net.hostsharing.hsadminng.context;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class HttpServletRequestBodyCache extends ServletInputStream {
private InputStream inputStream;
public HttpServletRequestBodyCache(byte[] body) {
this.inputStream = new ByteArrayInputStream(body);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public boolean isFinished() {
try {
return available() == 0;
} catch (IOException e) {
return true;
}
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(final ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.context;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class HttpServletRequestBodyCachingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new HttpServletRequestWithCachedBody(request), response);
}
}

View File

@ -0,0 +1,33 @@
package net.hostsharing.hsadminng.context;
import org.springframework.util.StreamUtils;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class HttpServletRequestWithCachedBody extends HttpServletRequestWrapper {
private byte[] cachedBody;
public HttpServletRequestWithCachedBody(HttpServletRequest request) throws IOException {
super(request);
final var requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new HttpServletRequestBodyCache(this.cachedBody);
}
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
}

View File

@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.errors;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@Getter
public class CustomErrorResponse {
static ResponseEntity<CustomErrorResponse> errorResponse(
final WebRequest request,
final HttpStatus httpStatus,
final String message) {
return new ResponseEntity<>(
new CustomErrorResponse(request.getContextPath(), httpStatus, message), httpStatus);
}
static String firstMessageLine(final Throwable exception) {
if (exception.getMessage() != null) {
return line(exception.getMessage(), 0);
}
return "ERROR: [500] " + exception.getClass().getName();
}
static String line(final String message, final int lineNo) {
return message.split("\\r|\\n|\\r\\n", 0)[lineNo];
}
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
private final LocalDateTime timestamp;
private final String path;
private final int statusCode;
private final String statusPhrase;
private final String message;
CustomErrorResponse(final String path, final HttpStatus status, final String message) {
this.timestamp = LocalDateTime.now();
this.path = path;
this.statusCode = status.value();
this.statusPhrase = status.getReasonPhrase();
this.message = message.startsWith("ERROR: [") ? message : "ERROR: [" + statusCode + "] " + message;
}
}

View File

@ -0,0 +1,24 @@
package net.hostsharing.hsadminng.errors;
import jakarta.validation.constraints.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DisplayAs {
class DisplayName {
public static String of(final Class<?> clazz) {
final var displayNameAnnot = clazz.getAnnotation(DisplayAs.class);
return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName();
}
public static String of(@NotNull final Object instance) {
return of(instance.getClass());
}
}
String value() default "";
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.errors;
import jakarta.validation.ValidationException;
import java.util.List;
import static java.lang.String.join;
public class MultiValidationException extends ValidationException {
private MultiValidationException(final List<String> violations) {
super(
violations.size() > 1
? "[\n" + join(",\n", violations) + "\n]"
: "[" + join(",\n", violations) + "]"
);
}
public static void throwIfNotEmpty(final List<String> violations) {
if (!violations.isEmpty()) {
throw new MultiValidationException(violations);
}
}
}

View File

@ -0,0 +1,19 @@
package net.hostsharing.hsadminng.errors;
import java.util.UUID;
public class ReferenceNotFoundException extends RuntimeException {
private final Class<?> entityClass;
private final UUID uuid;
public <E> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
super(exc);
this.entityClass = entityClass;
this.uuid = uuid;
}
@Override
public String getMessage() {
return "Cannot resolve " + entityClass.getSimpleName() +" with uuid " + uuid;
}
}

View File

@ -0,0 +1,188 @@
package net.hostsharing.hsadminng.errors;
import org.iban4j.Iban4jException;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.validation.FieldError;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException;
import java.util.*;
import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.errors.CustomErrorResponse.*;
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(DataIntegrityViolationException.class)
protected ResponseEntity<CustomErrorResponse> handleConflict(
final RuntimeException exc, final WebRequest request) {
final var rawMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
var message = line(rawMessage, 0);
if (message.contains("violates foreign key constraint")) {
return errorResponse(request, HttpStatus.BAD_REQUEST, line(rawMessage, 1).replaceAll(" *Detail: *", ""));
}
return errorResponse(request, HttpStatus.CONFLICT, message);
}
@ExceptionHandler(JpaSystemException.class)
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
final RuntimeException exc, final WebRequest request) {
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
return errorResponse(request, httpStatus(exc, message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
}
@ExceptionHandler(NoSuchElementException.class)
protected ResponseEntity<CustomErrorResponse> handleNoSuchElementException(
final RuntimeException exc, final WebRequest request) {
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
return errorResponse(request, HttpStatus.NOT_FOUND, message);
}
@ExceptionHandler(ReferenceNotFoundException.class)
protected ResponseEntity<CustomErrorResponse> handleReferenceNotFoundException(
final ReferenceNotFoundException exc, final WebRequest request) {
return errorResponse(request, HttpStatus.BAD_REQUEST, exc.getMessage());
}
@ExceptionHandler({ JpaObjectRetrievalFailureException.class, EntityNotFoundException.class })
protected ResponseEntity<CustomErrorResponse> handleJpaObjectRetrievalFailureException(
final RuntimeException exc, final WebRequest request) {
final var message =
userReadableEntityClassName(
line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0));
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
}
@ExceptionHandler({ Iban4jException.class, ValidationException.class })
protected ResponseEntity<CustomErrorResponse> handleValidationExceptions(
final Throwable exc, final WebRequest request) {
final String fullMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
final var message = exc instanceof MultiValidationException ? fullMessage : line(fullMessage, 0);
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
}
@ExceptionHandler(Throwable.class)
protected ResponseEntity<CustomErrorResponse> handleOtherExceptions(
final Throwable exc, final WebRequest request) {
final var causingException = NestedExceptionUtils.getMostSpecificCause(exc);
final var message = firstMessageLine(causingException);
return errorResponse(request, httpStatus(causingException, message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
}
@Override
@SuppressWarnings("unchecked,rawtypes")
protected ResponseEntity handleExceptionInternal(
Exception exc, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
final var response = super.handleExceptionInternal(exc, body, headers, statusCode, request);
return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
}
@Override
@SuppressWarnings("unchecked,rawtypes")
protected ResponseEntity handleHttpMessageNotReadable(
HttpMessageNotReadableException exc, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
final var message = line(exc.getMessage(), 0);
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
}
@Override
@SuppressWarnings("unchecked,rawtypes")
protected ResponseEntity handleMethodArgumentNotValid(
MethodArgumentNotValidException exc,
HttpHeaders headers,
HttpStatusCode statusCode,
WebRequest request) {
final var errorList = exc
.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() + " but is \""
+ fieldError.getRejectedValue() + "\"")
.toList();
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
}
@SuppressWarnings("unchecked,rawtypes")
@Override
protected ResponseEntity handleHandlerMethodValidationException(
final HandlerMethodValidationException exc,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
final var errorList = exc
.getAllValidationResults()
.stream()
.map(ParameterValidationResult::getResolvableErrors)
.flatMap(Collection::stream)
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() + " but is \""
+ fieldError.getRejectedValue() + "\"")
.toList();
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
}
private String userReadableEntityClassName(final String exceptionMessage) {
final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) ";
final var pattern = Pattern.compile(regex);
final var matcher = pattern.matcher(exceptionMessage);
if (matcher.find()) {
final var entityName = matcher.group(1);
final var entityClass = resolveClass(entityName);
if (entityClass.isPresent()) {
return (entityClass.get().isAnnotationPresent(DisplayAs.class)
? exceptionMessage.replace(entityName, entityClass.get().getAnnotation(DisplayAs.class).value())
: exceptionMessage.replace(entityName, entityClass.get().getSimpleName()))
.replace(" with id ", " with uuid ");
}
}
return exceptionMessage;
}
private static Optional<Class<?>> resolveClass(final String entityName) {
try {
return Optional.of(ClassLoader.getSystemClassLoader().loadClass(entityName));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
}
private Optional<HttpStatus> httpStatus(final Throwable causingException, final String message) {
if ( EntityNotFoundException.class.isInstance(causingException) ) {
return Optional.of(HttpStatus.BAD_REQUEST);
}
if (message.startsWith("ERROR: [")) {
for (HttpStatus status : HttpStatus.values()) {
if (message.startsWith("ERROR: [" + status.value() + "]")) {
return Optional.of(status);
}
}
return Optional.of(HttpStatus.INTERNAL_SERVER_ERROR);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,6 @@
@NonNullApi
@NonNullFields
package net.hostsharing.hsadminng.errors;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@ -0,0 +1,130 @@
package net.hostsharing.hsadminng.hash;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.function.BiFunction;
import java.util.random.RandomGenerator;
import lombok.Getter;
/**
* Usage-example to generate hash:
* HashGenerator.using(LINUX_SHA512).withRandomSalt().hash("plaintext password");
*
* Usage-example to verify hash:
* HashGenerator.fromHash("hashed password).verify("plaintext password");
*/
@Getter
public final class HashGenerator {
private static final RandomGenerator random = new SecureRandom();
private static final Queue<String> predefinedSalts = new PriorityQueue<>();
public static final int RANDOM_SALT_LENGTH = 16;
private static final String RANDOM_SALT_CHARACTERS =
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789/.";
private static boolean couldBeHashEnabled; // TODO.impl: remove after legacy data is migrated
public enum Algorithm {
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),
LINUX_YESCRYPT(LinuxEtcShadowHashGenerator::hash, "y", "j9T$") {
@Override
String enrichedSalt(final String salt) {
return prefix + "$" + (salt.startsWith(optionalParam) ? salt : optionalParam + salt);
}
},
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*"),
SCRAM_SHA256(PostgreSQLScramSHA256::hash, "SCRAM-SHA-256");
final BiFunction<HashGenerator, String, String> implementation;
final String prefix;
final String optionalParam;
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix, final String optionalParam) {
this.implementation = implementation;
this.prefix = prefix;
this.optionalParam = optionalParam;
}
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix) {
this(implementation, prefix, null);
}
static Algorithm byPrefix(final String prefix) {
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
}
String enrichedSalt(final String salt) {
return prefix + "$" + salt;
}
}
private final Algorithm algorithm;
private String salt;
public static HashGenerator using(final Algorithm algorithm) {
return new HashGenerator(algorithm);
}
private HashGenerator(final Algorithm algorithm) {
this.algorithm = algorithm;
}
public static void enableCouldBeHash(final boolean enable) {
couldBeHashEnabled = enable;
}
public boolean couldBeHash(final String value) {
return couldBeHashEnabled && value.startsWith(algorithm.prefix);
}
public String hash(final String plaintextPassword) {
if (plaintextPassword == null) {
throw new IllegalStateException("no password given");
}
final var hash = algorithm.implementation.apply(this, plaintextPassword);
if (hash.length() < plaintextPassword.length()) {
throw new AssertionError("generated hash too short: " + hash);
}
return hash;
}
public String hashIfNotYetHashed(final String plaintextPasswordOrHash) {
return couldBeHash(plaintextPasswordOrHash)
? plaintextPasswordOrHash
: hash(plaintextPasswordOrHash);
}
public static void nextSalt(final String salt) {
predefinedSalts.add(salt);
}
public HashGenerator withSalt(final String salt) {
this.salt = salt;
return this;
}
public HashGenerator withRandomSalt() {
if (!predefinedSalts.isEmpty()) {
return withSalt(predefinedSalts.poll());
}
final var stringBuilder = new StringBuilder(RANDOM_SALT_LENGTH);
for (int i = 0; i < RANDOM_SALT_LENGTH; ++i) {
int randomIndex = random.nextInt(RANDOM_SALT_CHARACTERS.length());
stringBuilder.append(RANDOM_SALT_CHARACTERS.charAt(randomIndex));
}
return withSalt(stringBuilder.toString());
}
public static void main(String[] args) {
System.out.println(
HashGenerator.using(Algorithm.LINUX_YESCRYPT).withRandomSalt().hash("my plaintext domain transfer passphrase")
);
}
}

View File

@ -0,0 +1,36 @@
package net.hostsharing.hsadminng.hash;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class LinuxEtcShadowHashGenerator {
public static String hash(final HashGenerator generator, final String payload) {
if (generator.getSalt() == null) {
throw new IllegalStateException("no salt given");
}
return NativeCryptLibrary.INSTANCE.crypt(payload, "$" + generator.getAlgorithm().enrichedSalt(generator.getSalt()));
}
public static void verify(final String givenHash, final String payload) {
final var parts = givenHash.split("\\$");
if (parts.length < 3 || parts.length > 5) {
throw new IllegalArgumentException("hash with unknown hash method: " + givenHash);
}
final var algorithm = HashGenerator.Algorithm.byPrefix(parts[1]);
final var salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
final var calculatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload);
if (!calculatedHash.equals(givenHash)) {
throw new IllegalArgumentException("invalid password");
}
}
public interface NativeCryptLibrary extends Library {
NativeCryptLibrary INSTANCE = Native.load("crypt", NativeCryptLibrary.class);
String crypt(String password, String salt);
}
}

View File

@ -0,0 +1,35 @@
package net.hostsharing.hsadminng.hash;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MySQLNativePasswordHashGenerator {
public static String hash(final HashGenerator generator, final String password) {
// TODO.impl: if a random salt is generated or not should be part of the algorithm definition
// if (generator.getSalt() != null) {
// throw new IllegalStateException("salt not supported");
// }
try {
final var sha1 = MessageDigest.getInstance("SHA-1");
final var firstHash = sha1.digest(password.getBytes());
final var secondHash = sha1.digest(firstHash);
return "*" + bytesToHex(secondHash).toUpperCase();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-1 algorithm not found", e);
}
}
private static String bytesToHex(byte[] bytes) {
final var hexString = new StringBuilder();
for (byte b : bytes) {
final var hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}

View File

@ -0,0 +1,61 @@
package net.hostsharing.hsadminng.hash;
import lombok.SneakyThrows;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
public class PostgreSQLScramSHA256 {
private static final String PBKDF_2_WITH_HMAC_SHA256 = "PBKDF2WithHmacSHA256";
private static final String HMAC_SHA256 = "HmacSHA256";
private static final String SHA256 = "SHA-256";
private static final int ITERATIONS = 4096;
public static final int KEY_LENGTH_IN_BITS = 256;
private static final PostgreSQLScramSHA256 scram = new PostgreSQLScramSHA256();
@SneakyThrows
public static String hash(final HashGenerator generator, final String password) {
if (generator.getSalt() == null) {
throw new IllegalStateException("no salt given");
}
final byte[] salt = generator.getSalt().getBytes(Charset.forName("latin1")); // Base64.getEncoder().encode(generator.getSalt().getBytes());
final byte[] saltedPassword = scram.generateSaltedPassword(password, salt);
final byte[] clientKey = scram.hmacSHA256(saltedPassword, "Client Key".getBytes());
final byte[] storedKey = MessageDigest.getInstance(SHA256).digest(clientKey);
final byte[] serverKey = scram.hmacSHA256(saltedPassword, "Server Key".getBytes());
return "SCRAM-SHA-256${iterations}:{base64EncodedSalt}${base64EncodedStoredKey}:{base64EncodedServerKey}"
.replace("{iterations}", Integer.toString(ITERATIONS))
.replace("{base64EncodedSalt}", base64(salt))
.replace("{base64EncodedStoredKey}", base64(storedKey))
.replace("{base64EncodedServerKey}", base64(serverKey));
}
private static String base64(final byte[] salt) {
return Base64.getEncoder().encodeToString(salt);
}
private byte[] generateSaltedPassword(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
final var spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH_IN_BITS);
return SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA256).generateSecret(spec).getEncoded();
}
private byte[] hmacSHA256(byte[] key, byte[] message)
throws NoSuchAlgorithmException, InvalidKeyException {
final var mac = Mac.getInstance(HMAC_SHA256);
mac.init(new SecretKeySpec(key, HMAC_SHA256));
return mac.doFinal(message);
}
}

View File

@ -0,0 +1,55 @@
package net.hostsharing.hsadminng.hs.booking.debitor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.UUID;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
// a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity
@Entity
@Table(name = "hs_booking_debitor_xv")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DisplayAs("BookingDebitor")
public class HsBookingDebitorEntity implements Stringifyable {
public static final String DEBITOR_NUMBER_TAG = "D-";
private static Stringify<HsBookingDebitorEntity> stringify =
stringify(HsBookingDebitorEntity.class, "booking-debitor")
.withIdProp(HsBookingDebitorEntity::toShortString)
.withProp(HsBookingDebitorEntity::getDefaultPrefix)
.quotedValues(false);
@Id
private UUID uuid;
@Column(name = "debitornumber")
private Integer debitorNumber;
@Column(name = "defaultprefix", columnDefinition = "char(3) not null")
private String defaultPrefix;
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return DEBITOR_NUMBER_TAG + debitorNumber;
}
}

View File

@ -0,0 +1,14 @@
package net.hostsharing.hsadminng.hs.booking.debitor;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
Optional<HsBookingDebitorEntity> findByUuid(UUID id);
List<HsBookingDebitorEntity> findByDebitorNumber(int debitorNumber);
}

View File

@ -0,0 +1,172 @@
package net.hostsharing.hsadminng.hs.booking.item;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PostLoad;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static java.util.Collections.emptyMap;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@SuperBuilder(builderMethodName = "baseBuilder", toBuilder = true)
public abstract class HsBookingItem implements Stringifyable, BaseEntity<HsBookingItem>, PropertiesProvider {
private static Stringify<HsBookingItem> stringify = stringify(HsBookingItem.class)
.withProp(HsBookingItem::getType)
.withProp(HsBookingItem::getCaption)
.withProp(HsBookingItem::getProject)
.withProp(e -> e.getValidity().asString())
.withProp(HsBookingItem::getResources)
.quotedValues(false);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "projectuuid")
private HsBookingProjectRealEntity project;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parentitemuuid")
private HsBookingItemRealEntity parentItem;
@NotNull
@Column(name = "type")
@Enumerated(EnumType.STRING)
private HsBookingItemType type;
@Builder.Default
@Type(PostgreSQLRangeType.class)
@Column(name = "validity", columnDefinition = "daterange")
private Range<LocalDate> validity = Range.closedInfinite(LocalDate.now());
@Column(name = "caption")
private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(columnDefinition = "resources")
private Map<String, Object> resources = new HashMap<>();
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
@JoinColumn(name = "parentitemuuid", referencedColumnName = "uuid")
private List<HsBookingItemRealEntity> subBookingItems;
@Transient
private PatchableMapWrapper<Object> resourcesWrapper;
@Transient
private boolean isLoaded;
@PostLoad
public void markAsLoaded() {
this.isLoaded = true;
}
public PatchableMapWrapper<Object> getResources() {
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper;}, resources);
}
public void putResources(Map<String, Object> newResources) {
getResources().assign(newResources);
}
public void setValidFrom(final LocalDate validFrom) {
setValidity(toPostgresDateRange(validFrom, getValidTo()));
}
public void setValidTo(final LocalDate validTo) {
setValidity(toPostgresDateRange(getValidFrom(), validTo));
}
public LocalDate getValidFrom() {
return lowerInclusiveFromPostgresDateRange(getValidity());
}
public LocalDate getValidTo() {
return upperInclusiveFromPostgresDateRange(getValidity());
}
@Override
public PatchableMapWrapper<Object> directProps() {
return getResources();
}
@Override
public Object getContextValue(final String propName) {
final var v = resources.get(propName);
if (v != null) {
return v;
}
if (parentItem != null) {
return parentItem.getResources().get(propName);
}
return emptyMap();
}
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return ofNullable(getRelatedProject()).map(HsBookingProject::toShortString).orElse("D-???????-?") +
":" + caption;
}
public HsBookingProject getRelatedProject() {
return project != null ? project
: parentItem != null ? parentItem.getRelatedProject()
: null; // can be the case for technical assets like IP-numbers
}
}

View File

@ -0,0 +1,138 @@
package net.hostsharing.hsadminng.hs.booking.item;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsApi;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@RestController
public class HsBookingItemController implements HsBookingItemsApi {
@Autowired
private Context context;
@Autowired
private Mapper mapper;
@Autowired
private HsBookingItemRbacRepository bookingItemRepo;
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid(
final String currentSubject,
final String assumedRoles,
final UUID projectUuid) {
context.define(currentSubject, assumedRoles);
final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid);
final var resources = mapper.mapList(entities, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
}
@Override
@Transactional
public ResponseEntity<HsBookingItemResource> addBookingItem(
final String currentSubject,
final String assumedRoles,
final HsBookingItemInsertResource body) {
context.define(currentSubject, assumedRoles);
final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
final var saved = HsBookingItemEntityValidatorRegistry.validated(em, bookingItemRepo.save(entityToSave));
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/booking/items/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped);
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<HsBookingItemResource> getBookingItemByUuid(
final String currentSubject,
final String assumedRoles,
final UUID bookingItemUuid) {
context.define(currentSubject, assumedRoles);
final var result = bookingItemRepo.findByUuid(bookingItemUuid);
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading
return result
.map(bookingItemEntity -> ResponseEntity.ok(
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@Override
@Transactional
public ResponseEntity<Void> deleteBookingIemByUuid(
final String currentSubject,
final String assumedRoles,
final UUID bookingItemUuid) {
context.define(currentSubject, assumedRoles);
final var result = bookingItemRepo.deleteByUuid(bookingItemUuid);
return result == 0
? ResponseEntity.notFound().build()
: ResponseEntity.noContent().build();
}
@Override
@Transactional
public ResponseEntity<HsBookingItemResource> patchBookingItem(
final String currentSubject,
final String assumedRoles,
final UUID bookingItemUuid,
final HsBookingItemPatchResource body) {
context.define(currentSubject, assumedRoles);
final var current = bookingItemRepo.findByUuid(bookingItemUuid).orElseThrow();
new HsBookingItemEntityPatcher(current).apply(body);
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current));
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setValidFrom(entity.getValidity().lower());
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
};
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
entity.putResources(KeyValueMap.from(resource.getResources()));
};
}

View File

@ -0,0 +1,28 @@
package net.hostsharing.hsadminng.hs.booking.item;
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.KeyValueMap;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import java.util.Optional;
public class HsBookingItemEntityPatcher implements EntityPatcher<HsBookingItemPatchResource> {
private final HsBookingItem entity;
public HsBookingItemEntityPatcher(final HsBookingItem entity) {
this.entity = entity;
}
@Override
public void apply(final HsBookingItemPatchResource resource) {
OptionalFromJson.of(resource.getCaption())
.ifPresent(entity::setCaption);
Optional.ofNullable(resource.getResources())
.ifPresent(r -> entity.getResources().patch(KeyValueMap.from(resource.getResources())));
OptionalFromJson.of(resource.getValidTo())
.ifPresent(entity::setValidTo);
}
}

View File

@ -0,0 +1,84 @@
package net.hostsharing.hsadminng.hs.booking.item;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.io.IOException;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
@Entity
@Table(name = "hs_booking_item_rv")
@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
@AttributeOverrides({
@AttributeOverride(name = "uuid", column = @Column(name = "uuid"))
})
public class HsBookingItemRbacEntity extends HsBookingItem {
public static RbacView rbac() {
return rbacViewFor("bookingItem", HsBookingItemRbacEntity.class)
.withIdentityView(SQL.projection("caption"))
.withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "caption", "validity", "resources")
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.toRole(GLOBAL, ADMIN).grantPermission(DELETE)
.importEntityAlias("project", HsBookingProject.class, usingDefaultCase(),
dependsOnColumn("projectUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("project", ADMIN).grantPermission(INSERT)
.importEntityAlias("parentItem", HsBookingItemRbacEntity.class, usingDefaultCase(),
dependsOnColumn("parentItemUuid"),
directlyFetchedByDependsOnColumn(),
NULLABLE)
.toRole("parentItem", ADMIN).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.incomingSuperRole("project", AGENT);
with.incomingSuperRole("parentItem", AGENT);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(AGENT)
.createSubRole(TENANT, (with) -> {
with.outgoingSubRole("project", TENANT);
with.outgoingSubRole("parentItem", TENANT);
with.permission(SELECT);
})
.limitDiagramTo("bookingItem", "project", "rbac.global");
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("6-hs-booking/630-booking-item/6303-hs-booking-item-rbac");
}
}

Some files were not shown because too many files have changed in this diff Show More