Compare commits
1276 Commits
Author | SHA1 | Date |
---|---|---|
Efstratios Giannopoulos | 5257e41109 | |
Thomas Georgios Giannos | d5e43e12bf | |
Sofia Papacharalampous | 1f2408d15d | |
Sofia Papacharalampous | ce07fb0894 | |
Sofia Papacharalampous | 87ffbfb880 | |
amentis | 8fc4f939b9 | |
Sofia Papacharalampous | 25d8503f26 | |
Efstratios Giannopoulos | f12ddd7090 | |
Efstratios Giannopoulos | 94cc0b87cd | |
Sofia Papacharalampous | 928babb9d5 | |
Sofia Papacharalampous | 4e791e78af | |
amentis | e0317e95b8 | |
amentis | 15b4c7fbc6 | |
Efstratios Giannopoulos | f98742f577 | |
Sofia Papacharalampous | f75d9ba777 | |
Sofia Papacharalampous | d772365b23 | |
Diamantis Tziotzios | 5f8121e687 | |
amentis | c11966f658 | |
Efstratios Giannopoulos | 39ea1b6c88 | |
Efstratios Giannopoulos | 0ceadf2ab1 | |
Efstratios Giannopoulos | 1e568bb2dd | |
Alexandros Mandilaras | 0e0e40afeb | |
Alexandros Mandilaras | 2bb5369a00 | |
Alexandros Mandilaras | 1f43bac181 | |
Alexandros Mandilaras | dea932ca95 | |
Efstratios Giannopoulos | 0fa6f4dc83 | |
Efstratios Giannopoulos | f9c89e4f92 | |
amentis | cf5333c788 | |
Efstratios Giannopoulos | 906cee7621 | |
Efstratios Giannopoulos | 871c3bc166 | |
Efstratios Giannopoulos | a5160d3ecd | |
Efstratios Giannopoulos | 5a6ab32768 | |
Efstratios Giannopoulos | 1c5c699195 | |
Efstratios Giannopoulos | 53a04ba970 | |
amentis | e3479eb313 | |
Diamantis Tziotzios | 8c24740382 | |
Diamantis Tziotzios | 6068878cd4 | |
Diamantis Tziotzios | abd4f0e5e2 | |
Diamantis Tziotzios | 6b75bcb06c | |
Sofia Papacharalampous | 1d68d217bd | |
Diamantis Tziotzios | 29e0d032ca | |
amentis | 6b7a33c0b4 | |
Efstratios Giannopoulos | 6ecd52dd2f | |
Efstratios Giannopoulos | a84332001e | |
amentis | c27ba6ee6a | |
amentis | 9bd50f379f | |
amentis | 43c958b2b1 | |
amentis | cdae5b2bd3 | |
amentis | 02c3ed7563 | |
Efstratios Giannopoulos | 125b0aa408 | |
Efstratios Giannopoulos | 74526f7927 | |
Sofia Papacharalampous | 29836cf362 | |
Sofia Papacharalampous | 5523ca2fe1 | |
amentis | 19cb076b03 | |
Diamantis Tziotzios | ab010026a7 | |
Diamantis Tziotzios | 02fc5b7bea | |
Efstratios Giannopoulos | 0569e8bd6d | |
Sofia Papacharalampous | a556aef2cd | |
amentis | a6b1a371a1 | |
Diamantis Tziotzios | 29d38630e1 | |
Sofia Papacharalampous | bf868d306a | |
Sofia Papacharalampous | d2816b9454 | |
Sofia Papacharalampous | 658b5d948d | |
Diamantis Tziotzios | ed55841164 | |
Diamantis Tziotzios | 060bfbed20 | |
Diamantis Tziotzios | d59676d6dc | |
Alexandros Mandilaras | fad3448e3e | |
Alexandros Mandilaras | 48e9ca17b1 | |
Alexandros Mandilaras | 6b57cdd20b | |
Alexandros Mandilaras | 7a168c2024 | |
amentis | fe6bbca30b | |
Alexandros Mandilaras | a71b85bb6b | |
Diamantis Tziotzios | a5099d8d5d | |
Diamantis Tziotzios | 03067fe6f6 | |
Alexandros Mandilaras | e1b09c9250 | |
Alexandros Mandilaras | a11e2c300d | |
Alexandros Mandilaras | c898227035 | |
Alexandros Mandilaras | 40d9850727 | |
Alexandros Mandilaras | c63f8cf928 | |
Efstratios Giannopoulos | 399e8e2f2a | |
Efstratios Giannopoulos | abc60aa11e | |
Alexandros Mandilaras | ffd521f4dc | |
Sofia Papacharalampous | 325356a2ae | |
Sofia Papacharalampous | 8d2d936a73 | |
Sofia Papacharalampous | 6013cb5225 | |
Alexandros Mandilaras | bdfcb8cdf6 | |
Alexandros Mandilaras | 79431f0865 | |
amentis | f2e6189fac | |
Alexandros Mandilaras | 9ce9632c37 | |
amentis | 51d941b0f7 | |
amentis | e89bfdf0f4 | |
Alexandros Mandilaras | c1a241e4a8 | |
Alexandros Mandilaras | c0ac0bb728 | |
Alexandros Mandilaras | ef5125bb04 | |
Alexandros Mandilaras | 8833905325 | |
Alexandros Mandilaras | 565e8a3b1e | |
Alexandros Mandilaras | b676bafdab | |
Alexandros Mandilaras | 6ffc0a0a10 | |
Alexandros Mandilaras | 83855540d2 | |
Alexandros Mandilaras | 3e8cb9b17e | |
Efstratios Giannopoulos | a4252a6157 | |
Efstratios Giannopoulos | c8809ee457 | |
Efstratios Giannopoulos | 17b787c142 | |
Efstratios Giannopoulos | 45e3426969 | |
Alexandros Mandilaras | ad069b5568 | |
Alexandros Mandilaras | 1f142c672f | |
Efstratios Giannopoulos | 7ebe22a45f | |
Efstratios Giannopoulos | 5176916ce7 | |
amentis | 4503bc0bcf | |
Sofia Papacharalampous | 405bf0c9b2 | |
Sofia Papacharalampous | 9ba20bdd17 | |
Alexandros Mandilaras | 328ac3b1f6 | |
Efstratios Giannopoulos | 4963d8ffc3 | |
Efstratios Giannopoulos | 6a89feb31b | |
Efstratios Giannopoulos | 028b1e0c94 | |
Efstratios Giannopoulos | efb816c627 | |
Alexandros Mandilaras | 2cc9d2bee0 | |
Alexandros Mandilaras | 9f99f622ef | |
Alexandros Mandilaras | 42af117718 | |
amentis | b6138da7ab | |
Efstratios Giannopoulos | bf08b6abb7 | |
Efstratios Giannopoulos | e61f06c192 | |
Sofia Papacharalampous | 3f69f4ebe2 | |
Sofia Papacharalampous | f82501344c | |
Sofia Papacharalampous | 0c197ffcd4 | |
Efstratios Giannopoulos | ff722b1d97 | |
Efstratios Giannopoulos | c35fb3ad3c | |
Efstratios Giannopoulos | 0d3e72028f | |
Sofia Papacharalampous | 4ffbafc85f | |
Sofia Papacharalampous | 023c0dd7b6 | |
amentis | 139ee2d259 | |
amentis | fea69ff087 | |
Sofia Papacharalampous | f0ec8b5fae | |
Sofia Papacharalampous | 5519c6890f | |
Diamantis Tziotzios | 76a355891f | |
amentis | 0ded82e5cb | |
amentis | b4f54bc330 | |
amentis | 2b2e04012d | |
Sofia Papacharalampous | dc5928cf49 | |
Sofia Papacharalampous | 192018051d | |
Sofia Papacharalampous | 3e86a43fb0 | |
Efstratios Giannopoulos | f7aaa04c0c | |
Efstratios Giannopoulos | 399efde673 | |
amentis | 805702459f | |
amentis | 4de68e34b2 | |
Sofia Papacharalampous | d94759ff5d | |
Sofia Papacharalampous | c71d0e96c3 | |
Sofia Papacharalampous | ef6970ffe6 | |
Efstratios Giannopoulos | ab993c43af | |
Thomas Georgios Giannos | 5ec8417f4e | |
Thomas Georgios Giannos | cfd508ccc5 | |
Thomas Georgios Giannos | fcda984d7e | |
Thomas Georgios Giannos | 6eed99c1cc | |
Thomas Georgios Giannos | f9839b14ed | |
Sofia Papacharalampous | 93c90ddcef | |
Sofia Papacharalampous | 5decf6f4b1 | |
amentis | 1aec9d4eff | |
Sofia Papacharalampous | 8cec0c3552 | |
Sofia Papacharalampous | 527130cb02 | |
amentis | e7f140cb51 | |
Efstratios Giannopoulos | 3fe87a024a | |
Efstratios Giannopoulos | c1a3ac89d6 | |
Efstratios Giannopoulos | bbe2d91830 | |
Thomas Georgios Giannos | bc44887675 | |
Sofia Papacharalampous | 199ffef673 | |
Efstratios Giannopoulos | ea6ea25116 | |
Efstratios Giannopoulos | 671f8f75ba | |
amentis | 330231f217 | |
Sofia Papacharalampous | 7c83fd0e67 | |
Sofia Papacharalampous | 5a5e190534 | |
amentis | 26cbe562ad | |
Sofia Papacharalampous | 6f7ed58523 | |
Sofia Papacharalampous | ca4e7e6feb | |
Efstratios Giannopoulos | 424d18526b | |
Sofia Papacharalampous | 87bd9e5b24 | |
Sofia Papacharalampous | 5e5bd8aab0 | |
Sofia Papacharalampous | 519870efce | |
Sofia Papacharalampous | da0633e4c0 | |
Efstratios Giannopoulos | f47c049b8c | |
Efstratios Giannopoulos | b3db6f1085 | |
Efstratios Giannopoulos | a5e69fe777 | |
Efstratios Giannopoulos | a5f31ab880 | |
Diamantis Tziotzios | fcc3dd7609 | |
Sofia Papacharalampous | 2bad85019b | |
Sofia Papacharalampous | 7fe36b1505 | |
Efstratios Giannopoulos | 3e37c91035 | |
Efstratios Giannopoulos | 83b5ec5a80 | |
amentis | 27e6677a21 | |
Sofia Papacharalampous | bc16f8a2c4 | |
Sofia Papacharalampous | 340692a3b5 | |
Sofia Papacharalampous | 9d5d891701 | |
Efstratios Giannopoulos | 9fad0abf73 | |
Efstratios Giannopoulos | cc407c202a | |
amentis | ea97f8ca88 | |
Efstratios Giannopoulos | 77d2dcdc39 | |
Efstratios Giannopoulos | 9acc26667b | |
amentis | ec65f0d76d | |
Sofia Papacharalampous | 950b7ae918 | |
Sofia Papacharalampous | 91ab347f40 | |
amentis | cbc3990cdf | |
Sofia Papacharalampous | 34fe15eb2f | |
Sofia Papacharalampous | cc68ed5188 | |
Sofia Papacharalampous | 72c9137d8b | |
Sofia Papacharalampous | 69b71ee253 | |
amentis | f4bf80aeb5 | |
Efstratios Giannopoulos | 621ddd0aee | |
Efstratios Giannopoulos | c5aac53ec8 | |
Efstratios Giannopoulos | 20f6ae27ed | |
Sofia Papacharalampous | 52aaa9c213 | |
amentis | b642ec52bc | |
Sofia Papacharalampous | 2e9d1366e9 | |
Sofia Papacharalampous | 7e84ed0138 | |
Sofia Papacharalampous | 5edcb24983 | |
Sofia Papacharalampous | 8550d95128 | |
amentis | 7a3bdedb8c | |
amentis | c1509baf38 | |
Efstratios Giannopoulos | eba8bc9cb6 | |
Efstratios Giannopoulos | c0aa671337 | |
Sofia Papacharalampous | 1a74939321 | |
Efstratios Giannopoulos | aa6768e67d | |
Sofia Papacharalampous | 0b8f2e2fa7 | |
amentis | 2339b51c24 | |
Diamantis Tziotzios | ba26a1bd67 | |
Diamantis Tziotzios | 0778842f38 | |
Sofia Papacharalampous | f8b7bfc96c | |
Efstratios Giannopoulos | 102141aa0d | |
Efstratios Giannopoulos | 21e6015f89 | |
amentis | 7d42777aa0 | |
amentis | 76d1d50850 | |
Sofia Papacharalampous | db68644eff | |
Sofia Papacharalampous | 2673857d74 | |
Sofia Papacharalampous | 9af7cd64f1 | |
Diamantis Tziotzios | cf17c87a09 | |
Efstratios Giannopoulos | 0c2d2e2f6a | |
Thomas Georgios Giannos | adfc2f5dac | |
Efstratios Giannopoulos | 198158a2c3 | |
amentis | 2e7606a497 | |
amentis | 6d96e56ec8 | |
Efstratios Giannopoulos | 3e155de0d4 | |
Sofia Papacharalampous | ccf3fa3111 | |
Efstratios Giannopoulos | fc0cb21e57 | |
Sofia Papacharalampous | ba7d690e39 | |
Sofia Papacharalampous | 662afe9827 | |
Diamantis Tziotzios | f4cbc3742c | |
Diamantis Tziotzios | 04efb9c0b1 | |
amentis | 993406e3f0 | |
Diamantis Tziotzios | 4d50aefacd | |
Diamantis Tziotzios | 7fcada8839 | |
Efstratios Giannopoulos | 308f1364af | |
Efstratios Giannopoulos | 09c428bd99 | |
Efstratios Giannopoulos | eb770ed65e | |
Efstratios Giannopoulos | d0ad9a8be0 | |
amentis | d8ba1a00c7 | |
Efstratios Giannopoulos | 21fb68128d | |
amentis | 49e6e9ac9f | |
amentis | 839f74868e | |
amentis | 62f1946279 | |
Diamantis Tziotzios | d086e3128e | |
amentis | 833abddea2 | |
amentis | c6a38efd83 | |
amentis | c5b1c89a7e | |
Diamantis Tziotzios | 1d5403fc25 | |
Diamantis Tziotzios | 55361e6a60 | |
Diamantis Tziotzios | d884f577f4 | |
Diamantis Tziotzios | 1408cc0a59 | |
amentis | 7ec9c0b3c6 | |
Sofia Papacharalampous | 8206602387 | |
Sofia Papacharalampous | fe323d9b6a | |
Sofia Papacharalampous | 7615e135e9 | |
Efstratios Giannopoulos | 9b30fb60d7 | |
Sofia Papacharalampous | 8be851763b | |
Sofia Papacharalampous | dff8785e7b | |
Sofia Papacharalampous | 5387a2c3df | |
amentis | 12bf3aca8f | |
amentis | 34189c5dcd | |
Sofia Papacharalampous | e4516ef0ed | |
Diamantis Tziotzios | 2a515f095b | |
Diamantis Tziotzios | 9176e68e33 | |
Efstratios Giannopoulos | 30e3bbf9de | |
Sofia Papacharalampous | f7289126d0 | |
Sofia Papacharalampous | a55128543a | |
Sofia Papacharalampous | d310e24d7c | |
amentis | d69b30df49 | |
Sofia Papacharalampous | 5e1cbc94a0 | |
Sofia Papacharalampous | 190317a413 | |
amentis | d4d06a9d47 | |
Sofia Papacharalampous | 49164dac9b | |
Sofia Papacharalampous | c2c42297e5 | |
Efstratios Giannopoulos | a90a4e1180 | |
Efstratios Giannopoulos | 704c9f2018 | |
Diamantis Tziotzios | 15cc2d16ca | |
Sofia Papacharalampous | e6b4cd7786 | |
Sofia Papacharalampous | aed0f9de8e | |
Sofia Papacharalampous | 0d40d56484 | |
Sofia Papacharalampous | 0d9f6ec3c4 | |
amentis | ffb5d5ee7e | |
Sofia Papacharalampous | b0cd53d74b | |
amentis | 4e61353f77 | |
Diamantis Tziotzios | 76e251c14d | |
Sofia Papacharalampous | 9d1088d3a8 | |
Sofia Papacharalampous | 923872d3b8 | |
Efstratios Giannopoulos | 598e27bf74 | |
Sofia Papacharalampous | efd6f5df47 | |
Sofia Papacharalampous | 6ca7b792e7 | |
amentis | af329498cd | |
Efstratios Giannopoulos | 37e128ec15 | |
Efstratios Giannopoulos | 67240affcf | |
Sofia Papacharalampous | abb7aca6da | |
Sofia Papacharalampous | f1f67b9790 | |
amentis | efc0db5500 | |
Sofia Papacharalampous | 0dbd652cf3 | |
Sofia Papacharalampous | 279a7f97d2 | |
Efstratios Giannopoulos | e61f1afaf5 | |
Diamantis Tziotzios | 0dffbec480 | |
Diamantis Tziotzios | 11911bd16e | |
Efstratios Giannopoulos | f663fc24b7 | |
Efstratios Giannopoulos | 709fecf1f1 | |
amentis | ba33b29e41 | |
Efstratios Giannopoulos | 48a310e561 | |
Efstratios Giannopoulos | 2f283ae594 | |
Diamantis Tziotzios | 8719b56920 | |
Sofia Papacharalampous | 09351701ab | |
Sofia Papacharalampous | 8e5c6e3579 | |
Sofia Papacharalampous | 15824ab952 | |
Sofia Papacharalampous | a85c8a32ba | |
Sofia Papacharalampous | 3e4302bfe2 | |
Sofia Papacharalampous | 024680c0e6 | |
Diamantis Tziotzios | cdd753a5d8 | |
Diamantis Tziotzios | 674ce60a29 | |
amentis | b66932b413 | |
Sofia Papacharalampous | c425da9a5f | |
Sofia Papacharalampous | 031e07b6e4 | |
Sofia Papacharalampous | 28ae763eb8 | |
Sofia Papacharalampous | 558e1331f2 | |
Efstratios Giannopoulos | 5801818a69 | |
Efstratios Giannopoulos | c480e8e508 | |
Sofia Papacharalampous | 551249af33 | |
Sofia Papacharalampous | 5f52e646a5 | |
Sofia Papacharalampous | ff40bee420 | |
amentis | ed3f3ebb27 | |
Efstratios Giannopoulos | b49975931b | |
Efstratios Giannopoulos | f19ac6c45e | |
Sofia Papacharalampous | 553fd05040 | |
Sofia Papacharalampous | 6444575de3 | |
Sofia Papacharalampous | 7bfff54d30 | |
amentis | 9a99c0c300 | |
amentis | 54e194e078 | |
Sofia Papacharalampous | 030f59ac06 | |
Sofia Papacharalampous | c0aaa00eb5 | |
Sofia Papacharalampous | 765440b509 | |
Sofia Papacharalampous | 66bfbe24d8 | |
amentis | 119ddebbcc | |
amentis | f71bc64ce5 | |
Sofia Papacharalampous | 327991c473 | |
amentis | 65d62b2849 | |
Efstratios Giannopoulos | 388857cdce | |
Efstratios Giannopoulos | 266c05ee80 | |
amentis | b618bc8612 | |
Sofia Papacharalampous | c479d86ee4 | |
Sofia Papacharalampous | c0b197b645 | |
amentis | 498ffca73d | |
Thomas Georgios Giannos | 5d24a38472 | |
amentis | 3045130b27 | |
Sofia Papacharalampous | 5b23cc0aa6 | |
Sofia Papacharalampous | 025933ee61 | |
Sofia Papacharalampous | e20b992128 | |
Thomas Georgios Giannos | c62a945a6a | |
Thomas Georgios Giannos | 00815b4b80 | |
Efstratios Giannopoulos | 1bdafbc9e6 | |
Efstratios Giannopoulos | dadda72a09 | |
Efstratios Giannopoulos | cef1e295f7 | |
Diamantis Tziotzios | f1a2c9f4c0 | |
Diamantis Tziotzios | 4b585bc8c4 | |
Diamantis Tziotzios | f1c70325a3 | |
amentis | 9d6fbcd3ee | |
Alexandros Mandilaras | f1290a1e56 | |
Alexandros Mandilaras | 2c4a0efe9c | |
Diamantis Tziotzios | dfd2f3eeff | |
Sofia Papacharalampous | ea3b1b1558 | |
Sofia Papacharalampous | 17071f9114 | |
Efstratios Giannopoulos | e9cbf27295 | |
Sofia Papacharalampous | 9aaaf226bb | |
Sofia Papacharalampous | cf1cc0a4fb | |
Efstratios Giannopoulos | db68cd32e1 | |
Efstratios Giannopoulos | cbfe4ec4f2 | |
Thomas Georgios Giannos | cf5acb2656 | |
Thomas Georgios Giannos | 1b7ecf201e | |
Sofia Papacharalampous | 99a870ff84 | |
Efstratios Giannopoulos | dcb112f843 | |
Efstratios Giannopoulos | ae56e2dbf2 | |
Diamantis Tziotzios | 27aa7163e8 | |
Sofia Papacharalampous | 162f680062 | |
Diamantis Tziotzios | 83565a0e52 | |
Sofia Papacharalampous | 4e80c5cd5d | |
Efstratios Giannopoulos | 5f88e835b7 | |
Efstratios Giannopoulos | 52e1c42cd2 | |
Sofia Papacharalampous | e7cb7f44d3 | |
Sofia Papacharalampous | f754dbe661 | |
amentis | a45dec7a5f | |
Efstratios Giannopoulos | cfbd3233ec | |
Efstratios Giannopoulos | a0554724a8 | |
amentis | b8f14e957e | |
Thomas Georgios Giannos | 2cbb6297cb | |
Efstratios Giannopoulos | e3ac2394c2 | |
Efstratios Giannopoulos | c592226e77 | |
Efstratios Giannopoulos | a399b183f6 | |
Thomas Georgios Giannos | 3fc805ab85 | |
Thomas Georgios Giannos | 7347fbbd5a | |
amentis | 06e6ee406b | |
Efstratios Giannopoulos | 097d2832be | |
Efstratios Giannopoulos | 403e618d04 | |
Diamantis Tziotzios | 60899bfea3 | |
amentis | 83ee9a7e45 | |
Efstratios Giannopoulos | a73eef6529 | |
Efstratios Giannopoulos | 49f3da1314 | |
Thomas Georgios Giannos | d844d35392 | |
Thomas Georgios Giannos | f37407063f | |
amentis | 2c8982bfef | |
Thomas Georgios Giannos | 8141d40ecf | |
Thomas Georgios Giannos | 50dd06b3f1 | |
Thomas Georgios Giannos | 9d706c6e11 | |
Thomas Georgios Giannos | fa97c55862 | |
amentis | bb6037afba | |
Efstratios Giannopoulos | f92f3c1a4d | |
Efstratios Giannopoulos | 535c07a1ad | |
Diamantis Tziotzios | 69a1ea5584 | |
amentis | 5851c2ff26 | |
amentis | 043d4d2e96 | |
amentis | 6c82fcf8b0 | |
amentis | 4ebabd68ab | |
Efstratios Giannopoulos | 0530373c83 | |
amentis | 5fc7241dc2 | |
amentis | b84cd93ce6 | |
amentis | f0c076fe4f | |
amentis | a5c194ac95 | |
Efstratios Giannopoulos | b1ebc6b35a | |
Efstratios Giannopoulos | 25adaf23e2 | |
Diamantis Tziotzios | 806f763e2b | |
amentis | 5da3c4f9b9 | |
Efstratios Giannopoulos | 46862e06cc | |
amentis | 96696f1a14 | |
Efstratios Giannopoulos | 656afc2045 | |
amentis | b8c5edf4fb | |
Efstratios Giannopoulos | 1a8180c64e | |
Efstratios Giannopoulos | ef50193175 | |
Efstratios Giannopoulos | 8237e7936d | |
amentis | fd77a44039 | |
Diamantis Tziotzios | 067e81207f | |
amentis | be796bc68f | |
amentis | 9989f5ca69 | |
amentis | dad32207e3 | |
Thomas Georgios Giannos | 141d6346fb | |
Diamantis Tziotzios | 90bcc40dde | |
amentis | 57908f342b | |
amentis | 69b00a7a26 | |
Efstratios Giannopoulos | 0e7813cded | |
Efstratios Giannopoulos | e0deaef950 | |
Efstratios Giannopoulos | 098b80b3f4 | |
amentis | adcf169b17 | |
Efstratios Giannopoulos | dabbbf3fa4 | |
Diamantis Tziotzios | 3f8716ab0a | |
amentis | fdf8e13af9 | |
Efstratios Giannopoulos | a28323dcaf | |
Thomas Georgios Giannos | 6ebbb885fb | |
amentis | 40f56986b4 | |
amentis | 9f42381992 | |
amentis | a387349aad | |
Thomas Georgios Giannos | add1bb582f | |
Thomas Georgios Giannos | 9982f89767 | |
Alexandros Mandilaras | 3e5ea407e4 | |
Alexandros Mandilaras | ec4cf780eb | |
Alexandros Mandilaras | b8cd2e3757 | |
Alexandros Mandilaras | 63bc7f3685 | |
Alexandros Mandilaras | 3d2018f59a | |
Alexandros Mandilaras | d7c480f0ee | |
Alexandros Mandilaras | a95ed902d5 | |
Alexandros Mandilaras | 530623b931 | |
Alexandros Mandilaras | 5bb31d0a59 | |
Efstratios Giannopoulos | f7babf1f0b | |
Efstratios Giannopoulos | bc5f394ece | |
Efstratios Giannopoulos | 1bd8a9f09d | |
Thomas Georgios Giannos | 14f5f689f9 | |
Thomas Georgios Giannos | 5c1f6a096b | |
Diamantis Tziotzios | 446268719d | |
Diamantis Tziotzios | c9c0ea614b | |
Thomas Georgios Giannos | c6c3ed4a5f | |
Thomas Georgios Giannos | 0634324151 | |
Diamantis Tziotzios | 443ce153ee | |
amentis | cc6359e0fc | |
amentis | 38eef575d0 | |
amentis | 4738a92f6e | |
amentis | a1394e6629 | |
Alexandros Mandilaras | 347ed854ae | |
Alexandros Mandilaras | c1c12ded5c | |
Alexandros Mandilaras | eac744bd95 | |
Alexandros Mandilaras | b0124d3800 | |
Alexandros Mandilaras | 7e7229424a | |
Alexandros Mandilaras | d19fdc0898 | |
Efstratios Giannopoulos | 07cdb91b9e | |
Alexandros Mandilaras | d6cd18729f | |
amentis | be672aab57 | |
amentis | 0cbcae3afe | |
Alexandros Mandilaras | ece652e828 | |
Alexandros Mandilaras | 6e1f1d110a | |
Alexandros Mandilaras | 26eb185ce9 | |
Alexandros Mandilaras | 2fb3332390 | |
Alexandros Mandilaras | ece54563d0 | |
Alexandros Mandilaras | 6f1f14a058 | |
Alexandros Mandilaras | 16b53ba11d | |
Alexandros Mandilaras | 6f2c8101f2 | |
Alexandros Mandilaras | fcf6ee4087 | |
Alexandros Mandilaras | 2bbb2c35f6 | |
amentis | c55f160d9b | |
amentis | b009829d6f | |
amentis | d3659aad24 | |
Efstratios Giannopoulos | 61560ec8ad | |
Efstratios Giannopoulos | d50613f416 | |
Efstratios Giannopoulos | eb228fa329 | |
amentis | 057f444ff1 | |
Efstratios Giannopoulos | 9f36710276 | |
Efstratios Giannopoulos | a44fe43cf3 | |
amentis | fd30b89f93 | |
amentis | b343b76c9e | |
Diamantis Tziotzios | 4f5b3cf647 | |
Diamantis Tziotzios | bc8da3676a | |
amentis | 720e5f96f3 | |
Diamantis Tziotzios | 244c8f2afd | |
amentis | aabcdd8c65 | |
Thomas Georgios Giannos | 20498001b3 | |
amentis | 6e443c69fa | |
Efstratios Giannopoulos | 384b9883a8 | |
Efstratios Giannopoulos | f826c2297b | |
Thomas Georgios Giannos | bd436e55b1 | |
Efstratios Giannopoulos | 40316781a2 | |
Efstratios Giannopoulos | 28d08d99e8 | |
Diamantis Tziotzios | 8b267ef5ff | |
Diamantis Tziotzios | 366dcf6e01 | |
Thomas Georgios Giannos | 30870c690f | |
Efstratios Giannopoulos | fef2df6014 | |
Efstratios Giannopoulos | 957c1fad58 | |
Thomas Georgios Giannos | 1eef4bfb1c | |
Diamantis Tziotzios | 9f5002802b | |
Thomas Georgios Giannos | 5c98648e56 | |
amentis | 075573ffaf | |
amentis | 1a6e776b41 | |
Thomas Georgios Giannos | 80096e9164 | |
Thomas Georgios Giannos | 510ec954fa | |
Diamantis Tziotzios | 174e57f62b | |
amentis | 313f5e3b3c | |
Efstratios Giannopoulos | 87dd56ff2c | |
Efstratios Giannopoulos | 4f1b05ab20 | |
Diamantis Tziotzios | e5946f90cd | |
Diamantis Tziotzios | 0f4a80ef1e | |
Diamantis Tziotzios | 6f74689d3f | |
amentis | 2574cb424e | |
amentis | bd8a67f6bb | |
Diamantis Tziotzios | f6151b085f | |
Diamantis Tziotzios | 98d6636855 | |
Thomas Georgios Giannos | 45c8eb9768 | |
Thomas Georgios Giannos | 5d0c8d97e5 | |
Thomas Georgios Giannos | fbf5f65318 | |
Thomas Georgios Giannos | f255f61ee6 | |
Efstratios Giannopoulos | 9d4e814d1c | |
Efstratios Giannopoulos | ab653e4f94 | |
amentis | 2204119503 | |
Diamantis Tziotzios | 0c2fb8cbef | |
Diamantis Tziotzios | 3a514af843 | |
amentis | 305729eb48 | |
amentis | 0a473bfd04 | |
Thomas Georgios Giannos | ce1cb02d43 | |
Efstratios Giannopoulos | f8251aaf43 | |
Thomas Georgios Giannos | 6e767f1e5e | |
amentis | 02cd51b751 | |
amentis | 3deb17dbea | |
Diamantis Tziotzios | 8bf262693f | |
Efstratios Giannopoulos | 2b436be2cd | |
Thomas Georgios Giannos | 176d13d443 | |
amentis | bc9691fa5d | |
Thomas Georgios Giannos | 5802c2149d | |
George Kalampokis | ed2a29d14b | |
Efstratios Giannopoulos | a52a3758d5 | |
George Kalampokis | fbe9683842 | |
Efstratios Giannopoulos | 51285f722b | |
George Kalampokis | a22644be56 | |
amentis | b4ccd71f85 | |
Efstratios Giannopoulos | f7dc3e5a32 | |
Efstratios Giannopoulos | fd974d3ee4 | |
George Kalampokis | 3ac4ed1237 | |
amentis | fc756a1780 | |
amentis | 8bf1e0c4d6 | |
Diamantis Tziotzios | 70e3c9fabc | |
Thomas Georgios Giannos | 3de2c8cbbe | |
Thomas Georgios Giannos | e4c1efe98c | |
Diamantis Tziotzios | acc754823f | |
George Kalampokis | 02a1437a91 | |
Efstratios Giannopoulos | 5f6eab6e11 | |
Efstratios Giannopoulos | 55fb0f3e00 | |
Diamantis Tziotzios | fde91b6677 | |
Efstratios Giannopoulos | cba0741149 | |
Efstratios Giannopoulos | 8524213d14 | |
Diamantis Tziotzios | 8d5b399dba | |
Efstratios Giannopoulos | 46c19f61a1 | |
Thomas Georgios Giannos | 1fc4e8add5 | |
Diamantis Tziotzios | bef333ebc9 | |
amentis | da62f79a73 | |
Thomas Georgios Giannos | 20ffacad0d | |
Thomas Georgios Giannos | 30abd31974 | |
Diamantis Tziotzios | bd2a935606 | |
Diamantis Tziotzios | 30902bbab8 | |
Efstratios Giannopoulos | 4a3f002672 | |
Thomas Georgios Giannos | 23c19888ca | |
Diamantis Tziotzios | 5373e2d867 | |
Diamantis Tziotzios | 8b5a54814d | |
amentis | 3c32e57aad | |
Efstratios Giannopoulos | b4f55a721c | |
Efstratios Giannopoulos | bcd42d66b7 | |
amentis | 6eea7d8088 | |
Thomas Georgios Giannos | 8f7a3cf768 | |
Efstratios Giannopoulos | 7e63abca50 | |
Efstratios Giannopoulos | 6a0d1a7c8b | |
Diamantis Tziotzios | 2ba9e146e5 | |
Diamantis Tziotzios | 078ca9e0cb | |
amentis | f604eac172 | |
amentis | 59e70de4f8 | |
Diamantis Tziotzios | 95568c5f7c | |
Diamantis Tziotzios | 4c1372257a | |
amentis | 285978fb58 | |
Diamantis Tziotzios | 59c425a240 | |
Diamantis Tziotzios | 5377b1db18 | |
Diamantis Tziotzios | f296bc44f9 | |
Efstratios Giannopoulos | 4d1d712215 | |
Efstratios Giannopoulos | 16222fc02c | |
Efstratios Giannopoulos | 268b82eb49 | |
amentis | 7d66c1e857 | |
Efstratios Giannopoulos | 2f48b3bae2 | |
Efstratios Giannopoulos | aebc6056f9 | |
Thomas Georgios Giannos | 80a3aa5616 | |
Diamantis Tziotzios | 953fa2710e | |
Diamantis Tziotzios | 4542189a26 | |
amentis | 8fb3556acb | |
Diamantis Tziotzios | 37d349fa9b | |
amentis | ea6e2765ea | |
Efstratios Giannopoulos | d89d8d4aa8 | |
Thomas Georgios Giannos | a55d7c3692 | |
Efstratios Giannopoulos | f58832e3e9 | |
George Kalampokis | ac932ffb05 | |
George Kalampokis | c9121ebb1a | |
Efstratios Giannopoulos | 2726d232dd | |
amentis | 962840d738 | |
Efstratios Giannopoulos | c05ccef28e | |
Efstratios Giannopoulos | 447059530d | |
Diamantis Tziotzios | 4644d24874 | |
Diamantis Tziotzios | ae543324a4 | |
amentis | b6a85e151d | |
amentis | f8fa4e519b | |
Thomas Georgios Giannos | ecb82f5812 | |
Thomas Georgios Giannos | a369d36677 | |
Thomas Georgios Giannos | 39982aa264 | |
Thomas Georgios Giannos | b6231feacd | |
amentis | 9b27d38026 | |
amentis | 8828be7dfa | |
Thomas Georgios Giannos | bca0144111 | |
amentis | 0faa5f731a | |
Thomas Georgios Giannos | 7a318da46a | |
Thomas Georgios Giannos | ca4a9db3f8 | |
amentis | 7d38ab742d | |
amentis | d0faa6713d | |
amentis | b5c31590d3 | |
Efstratios Giannopoulos | c30f499f30 | |
Efstratios Giannopoulos | d5292f5cec | |
amentis | f49f6d1c04 | |
Thomas Georgios Giannos | 6502221343 | |
amentis | 470de047d8 | |
Thomas Georgios Giannos | 21b5201bf2 | |
amentis | 101a0aace9 | |
amentis | c26c472ea8 | |
amentis | dcf5a5d690 | |
Diamantis Tziotzios | ef02fa806e | |
amentis | d60ecc4f1c | |
Thomas Georgios Giannos | 53062a879f | |
Thomas Georgios Giannos | a56a9075b1 | |
Efstratios Giannopoulos | 698c27747d | |
Diamantis Tziotzios | d4739ed1d0 | |
Efstratios Giannopoulos | 409b96f518 | |
Efstratios Giannopoulos | c94d57f3a0 | |
Diamantis Tziotzios | c4dac0e7d0 | |
amentis | 9356352e7d | |
Diamantis Tziotzios | 5e32f1e74d | |
Diamantis Tziotzios | 7ad8077bc8 | |
amentis | ace6ce1119 | |
George Kalampokis | 17dc3e47f1 | |
George Kalampokis | 3f436846f6 | |
George Kalampokis | e39d8d25b6 | |
George Kalampokis | 3749365e87 | |
George Kalampokis | cd696bf55a | |
amentis | 3bd60509e7 | |
Thomas Georgios Giannos | d0df9c7e79 | |
George Kalampokis | 01d78cd491 | |
George Kalampokis | aa75b3e3ab | |
Efstratios Giannopoulos | 70c94c5bdc | |
Efstratios Giannopoulos | b76a158824 | |
Efstratios Giannopoulos | 200948d962 | |
Diamantis Tziotzios | 85e299a4b8 | |
Thomas Georgios Giannos | aab7571763 | |
George Kalampokis | 7b963858f2 | |
Efstratios Giannopoulos | b04c0aebab | |
Efstratios Giannopoulos | 37aa6061a4 | |
Efstratios Giannopoulos | 1828e28485 | |
Efstratios Giannopoulos | d1cad30fcb | |
amentis | 412c778d8c | |
Diamantis Tziotzios | d760209d20 | |
Efstratios Giannopoulos | 04381aec20 | |
Efstratios Giannopoulos | f6b7b6e00d | |
Efstratios Giannopoulos | 9b079e4d4d | |
Efstratios Giannopoulos | 4da8c2a6a9 | |
Diamantis Tziotzios | 0e61925f8d | |
amentis | 0d65c38668 | |
Diamantis Tziotzios | c7a3b58019 | |
amentis | 951f34e0cc | |
Efstratios Giannopoulos | 141ef488e5 | |
Efstratios Giannopoulos | 4b9b5cc056 | |
amentis | 9e413d77f2 | |
Thomas Georgios Giannos | a2edcd6a04 | |
Thomas Georgios Giannos | 93ce7ecd6d | |
Thomas Georgios Giannos | 2fb387825d | |
Efstratios Giannopoulos | df7a43d2de | |
Efstratios Giannopoulos | 4d60698327 | |
George Kalampokis | 3d5be3a00a | |
amentis | 479159ff86 | |
Efstratios Giannopoulos | ab8ad0be51 | |
amentis | 46ba3dcb62 | |
amentis | cb7d5b342f | |
amentis | b2e2fc593f | |
amentis | 17d97bb3b3 | |
amentis | 708fa69c53 | |
amentis | 29bea9f246 | |
amentis | c7ea2ce2db | |
Efstratios Giannopoulos | d0157fa0e5 | |
Efstratios Giannopoulos | e123338a2c | |
Efstratios Giannopoulos | 59aae92a0e | |
amentis | da657b667b | |
Efstratios Giannopoulos | 1f92872158 | |
amentis | 3f85f1c9af | |
amentis | c36aff22e8 | |
George Kalampokis | 742fa49a7c | |
Efstratios Giannopoulos | 3fc04a1e3a | |
Efstratios Giannopoulos | 371dcd5a61 | |
Efstratios Giannopoulos | 614fa9314a | |
Efstratios Giannopoulos | 658310d8ce | |
George Kalampokis | 71746419fc | |
amentis | 043b43b9be | |
Thomas Georgios Giannos | e8553b1605 | |
amentis | 34df0b640c | |
amentis | 8807d42121 | |
amentis | a5f88db680 | |
Thomas Georgios Giannos | 9dd0405dc8 | |
Thomas Georgios Giannos | b7d1424e0c | |
Thomas Georgios Giannos | 897d099c2c | |
amentis | 7ba374d844 | |
amentis | a3fb03b41b | |
Efstratios Giannopoulos | c9640bcb54 | |
Efstratios Giannopoulos | 1a567c9a81 | |
amentis | 6a077d2b09 | |
Efstratios Giannopoulos | f8e1b4ab8b | |
amentis | 065cd249c9 | |
Thomas Georgios Giannos | 0e1a9c88b1 | |
Efstratios Giannopoulos | c056e04a89 | |
Efstratios Giannopoulos | a53396e38a | |
Efstratios Giannopoulos | 26ab1fb612 | |
Efstratios Giannopoulos | 5b18809da0 | |
Efstratios Giannopoulos | 73b0272672 | |
Efstratios Giannopoulos | f253fd965c | |
Thomas Georgios Giannos | 29022ee1cd | |
Thomas Georgios Giannos | 18a4684859 | |
Efstratios Giannopoulos | ccd7d069cb | |
Thomas Georgios Giannos | eb2b5d4df3 | |
amentis | 659c213360 | |
Efstratios Giannopoulos | 503fe2c3dc | |
Efstratios Giannopoulos | a4ced2be21 | |
Thomas Georgios Giannos | c4ec41bec1 | |
amentis | 8bccb9a224 | |
amentis | 438333786b | |
Thomas Georgios Giannos | e5410f39fc | |
Efstratios Giannopoulos | b87612c783 | |
Efstratios Giannopoulos | 71343c51e5 | |
amentis | 88b4bb0d2f | |
Thomas Georgios Giannos | 8d5b3d0426 | |
Thomas Georgios Giannos | 3f554554fd | |
Thomas Georgios Giannos | 1462b9944d | |
amentis | 28238c7a6d | |
George Kalampokis | 1fb516c8ef | |
George Kalampokis | e6278bff41 | |
Diamantis Tziotzios | de67f17603 | |
Diamantis Tziotzios | 7b28f499a8 | |
Diamantis Tziotzios | 244e0e83a2 | |
Diamantis Tziotzios | 2d377f6003 | |
Thomas Georgios Giannos | 2a6b047327 | |
Thomas Georgios Giannos | 63ffeabe62 | |
Diamantis Tziotzios | 17150126cb | |
amentis | 62970ebd35 | |
amentis | a8ee89fbc4 | |
Diamantis Tziotzios | 4178e2c619 | |
George Kalampokis | ea371cb1a1 | |
George Kalampokis | acc4557836 | |
Thomas Georgios Giannos | 306053acba | |
George Kalampokis | 8d7f6582ee | |
Diamantis Tziotzios | 128fc3d29e | |
Diamantis Tziotzios | c6508a0082 | |
amentis | b3b619d354 | |
amentis | 35b0d58ec9 | |
George Kalampokis | 3b12e89ac2 | |
George Kalampokis | 8f6d0cc33d | |
Thomas Georgios Giannos | aa648cda1d | |
Thomas Georgios Giannos | f9a47f2de7 | |
Efstratios Giannopoulos | f09e031e0c | |
Efstratios Giannopoulos | 4fa4cdc671 | |
Efstratios Giannopoulos | 68c56d70a4 | |
Thomas Georgios Giannos | 3e97d0fdd1 | |
Thomas Georgios Giannos | 6e0821f703 | |
Thomas Georgios Giannos | 54a83e98ac | |
Efstratios Giannopoulos | bd64566e8c | |
amentis | ab68a0bd4d | |
amentis | 18a1d43f01 | |
George Kalampokis | f06d3de1ec | |
George Kalampokis | 625a322467 | |
amentis | 6a6840fc79 | |
Thomas Georgios Giannos | 0fd552f29c | |
Thomas Georgios Giannos | 25e9f2a5c7 | |
amentis | 653991a066 | |
Thomas Georgios Giannos | a9eb2ff8a7 | |
amentis | fd1d6c9b1a | |
amentis | bcd42e7ec1 | |
amentis | 3990be182c | |
Thomas Georgios Giannos | a213ca3e84 | |
George Kalampokis | f405fdc32a | |
George Kalampokis | 0b45dd6981 | |
amentis | e2c352bf00 | |
Thomas Georgios Giannos | e4d2cde296 | |
amentis | a72770868e | |
Efstratios Giannopoulos | 8eda8402ab | |
Efstratios Giannopoulos | 048980ac39 | |
Thomas Georgios Giannos | baae057c6a | |
Thomas Georgios Giannos | 9f781853a7 | |
Thomas Georgios Giannos | 24bfd69e4d | |
amentis | f47f9721fe | |
Thomas Georgios Giannos | f46543f1a4 | |
Thomas Georgios Giannos | b46c4e19e2 | |
amentis | a94473be49 | |
amentis | 86628a59a7 | |
amentis | f6100a8ea2 | |
Thomas Georgios Giannos | a42e858f71 | |
amentis | 2a00fe7ec1 | |
amentis | 28fad3ccc4 | |
Efstratios Giannopoulos | aa8c73b6ed | |
Thomas Georgios Giannos | e70db447c8 | |
amentis | 880e8c5aa8 | |
Thomas Georgios Giannos | 0c34502f08 | |
Thomas Georgios Giannos | 038c78fb04 | |
Thomas Georgios Giannos | e7c7d1f991 | |
Thomas Georgios Giannos | 08a5b49d1d | |
amentis | 903388c3ce | |
amentis | 0c6e800118 | |
amentis | 5e72a4197b | |
Diamantis Tziotzios | 56bd12a4e3 | |
Efstratios Giannopoulos | c1b197ea30 | |
Thomas Georgios Giannos | b9eba9edcb | |
Thomas Georgios Giannos | 8227f60cda | |
Thomas Georgios Giannos | 9ba8751564 | |
amentis | 5825aa642a | |
Thomas Georgios Giannos | 37c170ea54 | |
amentis | dd7007128b | |
amentis | 46dccbfbdb | |
amentis | 908c530922 | |
Efstratios Giannopoulos | 85bd0de2d5 | |
amentis | bee0e9f152 | |
amentis | 2dac2326ee | |
amentis | ce0075f30a | |
Diamantis Tziotzios | 9e13eb4d01 | |
amentis | 9685b323e1 | |
Efstratios Giannopoulos | dfb4761276 | |
Efstratios Giannopoulos | c286838a30 | |
amentis | d2d6013389 | |
Efstratios Giannopoulos | 2fb99d2fe7 | |
Efstratios Giannopoulos | 2dee73ce1f | |
amentis | 5047d3b154 | |
Efstratios Giannopoulos | bc0d39a72f | |
Diamantis Tziotzios | 46e56111f0 | |
Diamantis Tziotzios | ad1539af59 | |
Efstratios Giannopoulos | 1a76942b5f | |
Diamantis Tziotzios | a72c7001f9 | |
Efstratios Giannopoulos | e441fc958c | |
Efstratios Giannopoulos | 418d36dd67 | |
Diamantis Tziotzios | 84726a250f | |
Diamantis Tziotzios | 7c9e36341b | |
Efstratios Giannopoulos | df87dbf76c | |
Diamantis Tziotzios | 567b09a26b | |
Diamantis Tziotzios | 6e997b300d | |
amentis | 3ea60a4b43 | |
amentis | 04d97fc2e2 | |
amentis | 63e5cde313 | |
Efstratios Giannopoulos | 5965acc4aa | |
Efstratios Giannopoulos | 3a70b4d700 | |
Efstratios Giannopoulos | 2d5c9df855 | |
Efstratios Giannopoulos | 2a766dc37f | |
amentis | 4417532935 | |
Diamantis Tziotzios | 40ae38e8d9 | |
Efstratios Giannopoulos | 17d57dbc7a | |
Efstratios Giannopoulos | 345c020869 | |
Efstratios Giannopoulos | b40134e6a7 | |
amentis | f1f987a157 | |
amentis | 37349d8fca | |
Thomas Georgios Giannos | e3d00fb72e | |
Thomas Georgios Giannos | 351e2cb78d | |
Efstratios Giannopoulos | 0decfb1474 | |
amentis | 388af4410d | |
Thomas Georgios Giannos | baaddb6284 | |
amentis | 10923d4d48 | |
amentis | cabb35cf19 | |
amentis | 8bcc11d9a1 | |
amentis | 63a8820681 | |
Diamantis Tziotzios | d436fb4b2c | |
amentis | 2c914ead90 | |
Diamantis Tziotzios | b7ff88be3a | |
Diamantis Tziotzios | 7430477903 | |
Thomas Georgios Giannos | bf07fed0f8 | |
Thomas Georgios Giannos | 2fae4f41a7 | |
Thomas Georgios Giannos | f5f5d6345f | |
Thomas Georgios Giannos | b4d7312751 | |
amentis | 3dd7a42224 | |
Thomas Georgios Giannos | 5b5f547b27 | |
Thomas Georgios Giannos | c6642a726e | |
amentis | 439efbd98a | |
amentis | 126d47f9e4 | |
amentis | 3d5c718d52 | |
Thomas Georgios Giannos | 319e1f0dca | |
Thomas Georgios Giannos | b3bc801cd4 | |
Efstratios Giannopoulos | d3d57e8838 | |
Efstratios Giannopoulos | 337556265e | |
Thomas Georgios Giannos | e6cba30e4c | |
Thomas Georgios Giannos | b00b048632 | |
Thomas Georgios Giannos | 693f1dba7f | |
Diamantis Tziotzios | eb19b9a7d5 | |
Diamantis Tziotzios | 00e5c69fac | |
Efstratios Giannopoulos | 1b92af8762 | |
Efstratios Giannopoulos | 75d31cf627 | |
Efstratios Giannopoulos | c5461dbc62 | |
Thomas Georgios Giannos | df2086a0a7 | |
Thomas Georgios Giannos | 92956f6598 | |
Thomas Georgios Giannos | 0166bf8ece | |
amentis | 61218ed220 | |
Thomas Georgios Giannos | 6fb601929a | |
amentis | 516e639153 | |
amentis | 39802c6e5b | |
Thomas Georgios Giannos | c3bb9b8ffa | |
Efstratios Giannopoulos | 8dbf275f3a | |
amentis | 6b4424c634 | |
Diamantis Tziotzios | e6b22a5b27 | |
Diamantis Tziotzios | 396c6e5b95 | |
Efstratios Giannopoulos | aa0a86b4d1 | |
Diamantis Tziotzios | 8a20687491 | |
Efstratios Giannopoulos | c67e4c8c9f | |
Thomas Georgios Giannos | ccd650217b | |
Efstratios Giannopoulos | c6f10b6a29 | |
Thomas Georgios Giannos | 8ee149f094 | |
amentis | 3d18ed1fa6 | |
amentis | d554e87093 | |
Thomas Georgios Giannos | 0934774b86 | |
Diamantis Tziotzios | 9d37900a86 | |
Thomas Georgios Giannos | 58d224b739 | |
Efstratios Giannopoulos | e698b198e7 | |
Thomas Georgios Giannos | c60e90d638 | |
Thomas Georgios Giannos | a6d175dcb3 | |
Diamantis Tziotzios | 1914444ff8 | |
Efstratios Giannopoulos | e4801f94ed | |
Diamantis Tziotzios | 07daf34077 | |
Thomas Georgios Giannos | eae9a5f7d3 | |
Thomas Georgios Giannos | f38febc22b | |
Diamantis Tziotzios | 3dbfc18fd6 | |
Efstratios Giannopoulos | 9433beaaa6 | |
Efstratios Giannopoulos | e47a370088 | |
Thomas Georgios Giannos | 1e92d86356 | |
Thomas Georgios Giannos | 2936cbbb7a | |
Diamantis Tziotzios | c779bd00f0 | |
Efstratios Giannopoulos | d426711904 | |
Efstratios Giannopoulos | be4e723911 | |
amentis | 18452725bc | |
Efstratios Giannopoulos | 93bbde9623 | |
Diamantis Tziotzios | dbd4e6b549 | |
amentis | f2c03c71cb | |
Diamantis Tziotzios | 231efd05ac | |
Diamantis Tziotzios | 6b95730280 | |
Efstratios Giannopoulos | ac91b62d63 | |
Efstratios Giannopoulos | 6f2d4f016d | |
amentis | 4187943654 | |
amentis | 62a5a6fe1d | |
amentis | c19920afb2 | |
Thomas Georgios Giannos | ad75ee1238 | |
Thomas Georgios Giannos | 17b0f9bc06 | |
Efstratios Giannopoulos | db6cb44ff8 | |
Efstratios Giannopoulos | a7f70568bb | |
Thomas Georgios Giannos | dc381c197d | |
Efstratios Giannopoulos | 08ea46f79b | |
Efstratios Giannopoulos | 8536d8d03a | |
Efstratios Giannopoulos | f92b0c238a | |
Efstratios Giannopoulos | 91af36cdcd | |
Efstratios Giannopoulos | c0e57b363d | |
amentis | e1985368aa | |
Efstratios Giannopoulos | e62d765796 | |
Efstratios Giannopoulos | 64d92e864d | |
Thomas Georgios Giannos | f6a6bab04d | |
Diamantis Tziotzios | 033492a25c | |
Diamantis Tziotzios | 8908936ec6 | |
Thomas Georgios Giannos | 99bf7d29f9 | |
Thomas Georgios Giannos | eb9ae3af48 | |
Efstratios Giannopoulos | ff93e4e39b | |
Thomas Georgios Giannos | 4d01463c72 | |
Thomas Georgios Giannos | caf7ec09c4 | |
Efstratios Giannopoulos | 6de7268465 | |
Efstratios Giannopoulos | 3b3dc6fffd | |
Thomas Georgios Giannos | 3a033a655b | |
Thomas Georgios Giannos | ea7fa62922 | |
Thomas Georgios Giannos | ee4636e508 | |
Thomas Georgios Giannos | 75471e5587 | |
Efstratios Giannopoulos | 91f7dbd785 | |
Efstratios Giannopoulos | a97976296b | |
Diamantis Tziotzios | 63f9fead96 | |
Thomas Georgios Giannos | 765eff356d | |
Thomas Georgios Giannos | 6e65d22fce | |
Diamantis Tziotzios | df58a254b2 | |
amentis | f2ee851bab | |
Diamantis Tziotzios | f1d2b9d8d4 | |
Diamantis Tziotzios | 22bd07451a | |
Efstratios Giannopoulos | dec8f6354c | |
Efstratios Giannopoulos | 7c8b385146 | |
Efstratios Giannopoulos | 6c49b7e7c2 | |
amentis | e9ae5126f8 | |
Thomas Georgios Giannos | 9cff8fb762 | |
Efstratios Giannopoulos | 5cfdc4be07 | |
Thomas Georgios Giannos | 3aa6cdaf29 | |
Efstratios Giannopoulos | 332ee8c59e | |
Efstratios Giannopoulos | e103f40763 | |
Efstratios Giannopoulos | 0e1408e532 | |
Efstratios Giannopoulos | 1324029804 | |
Efstratios Giannopoulos | f0264eefb9 | |
Thomas Georgios Giannos | 34ce4f6928 | |
Thomas Georgios Giannos | d50a058296 | |
Thomas Georgios Giannos | 5edaf1c74e | |
Thomas Georgios Giannos | ff35ad6aab | |
Thomas Georgios Giannos | 1cee5fb49f | |
Thomas Georgios Giannos | 0c4cf467d8 | |
amentis | c2c1d834cc | |
amentis | 3ecb1ed982 | |
amentis | 33672e5ce8 | |
Thomas Georgios Giannos | 009dad8cba | |
Efstratios Giannopoulos | 41fc3407eb | |
Efstratios Giannopoulos | 7ef668eede | |
Efstratios Giannopoulos | c1b9e72c2f | |
Thomas Georgios Giannos | dab5986688 | |
Efstratios Giannopoulos | 0bbad595f5 | |
Thomas Georgios Giannos | e22ba13caf | |
Efstratios Giannopoulos | e81e6dd89a | |
Efstratios Giannopoulos | 8cd8da6d98 | |
Thomas Georgios Giannos | 24aba556ef | |
Thomas Georgios Giannos | 3e15749b56 | |
Thomas Georgios Giannos | 4e99fb08e9 | |
Thomas Georgios Giannos | 0e6a59ef3d | |
Efstratios Giannopoulos | 0644e61962 | |
Thomas Georgios Giannos | 4a8c3640de | |
Thomas Georgios Giannos | 001cd0828b | |
amentis | cfb7da7968 | |
amentis | 5001968da7 | |
amentis | 88bf760165 | |
Efstratios Giannopoulos | df5ae4a278 | |
Efstratios Giannopoulos | ac8d36d1d5 | |
Efstratios Giannopoulos | 1469e22185 | |
Thomas Georgios Giannos | f878601609 | |
Thomas Georgios Giannos | ee68224f58 | |
Thomas Georgios Giannos | ea58ed2e44 | |
Efstratios Giannopoulos | 05f73889be | |
Efstratios Giannopoulos | 662c5ad7e8 | |
Diamantis Tziotzios | a9eb2b4513 | |
George Kalampokis | 1ef613c87e | |
George Kalampokis | 9e27558df7 | |
Thomas Georgios Giannos | d3cd37d57a | |
Thomas Georgios Giannos | a5f46e9328 | |
George Kalampokis | 49e34f9abc | |
George Kalampokis | 5ceed08b9c | |
Efstratios Giannopoulos | 20873ca13e | |
Efstratios Giannopoulos | 87e22040c5 | |
George Kalampokis | e42bdcfc0a | |
George Kalampokis | c6cdcc4b15 | |
Efstratios Giannopoulos | d8b1211a12 | |
Efstratios Giannopoulos | 81528dfe55 | |
amentis | 11bb1a3c8c | |
Thomas Georgios Giannos | 9b9cf0de4d | |
amentis | fda8fcef2c | |
George Kalampokis | c636b3e6b4 | |
George Kalampokis | 7bb8e850e8 | |
Thomas Georgios Giannos | b389ebf160 | |
Thomas Georgios Giannos | 2f20675348 | |
Efstratios Giannopoulos | 9cf91aab50 | |
Efstratios Giannopoulos | 08caf64d02 | |
amentis | c37530c022 | |
Efstratios Giannopoulos | b6278ab1ef | |
Efstratios Giannopoulos | d38f22ccbf | |
Thomas Georgios Giannos | e02237e599 | |
Thomas Georgios Giannos | 02d8132f3d | |
Thomas Georgios Giannos | 281475bb85 | |
Thomas Georgios Giannos | 752935f6e5 | |
Efstratios Giannopoulos | 732dfd9bfb | |
Thomas Georgios Giannos | c3808b5548 | |
Efstratios Giannopoulos | ac15e33a87 | |
George Kalampokis | 3c799063cc | |
Efstratios Giannopoulos | 4e08d9f073 | |
amentis | be9761b103 | |
Efstratios Giannopoulos | 7e391582b5 | |
Diamantis Tziotzios | c08f05bc04 | |
Efstratios Giannopoulos | 2b074f268d | |
amentis | 477e4e498e | |
amentis | f744573f02 | |
Diamantis Tziotzios | 557c444bc1 | |
Efstratios Giannopoulos | c06253bb20 | |
amentis | 49d400662b | |
amentis | a0ac8df5ba | |
Diamantis Tziotzios | 36c17b5853 | |
Diamantis Tziotzios | 9201f265d2 | |
Diamantis Tziotzios | 7214745162 | |
Efstratios Giannopoulos | bdcaee004d | |
Efstratios Giannopoulos | 57facf98e5 | |
amentis | 9d3cc9e3d2 | |
amentis | 92c165e904 | |
Thomas Georgios Giannos | 65e8635c2f | |
Thomas Georgios Giannos | 5d882c7e88 | |
Thomas Georgios Giannos | e31970c7ea | |
Thomas Georgios Giannos | 8d1c988135 | |
Thomas Georgios Giannos | 60ca51fe00 | |
amentis | 7508cf8698 | |
Diamantis Tziotzios | e4203a3bb4 | |
George Kalampokis | d983e1e38e | |
amentis | aecac1995d | |
amentis | f006587f4a | |
amentis | 5a55e5d739 | |
amentis | bbb21fadba | |
Diamantis Tziotzios | 4eca9b32c9 | |
Diamantis Tziotzios | 129dbd163a | |
Diamantis Tziotzios | 51cc5f4fd9 | |
Diamantis Tziotzios | d1bac40f0f | |
amentis | 45f8f51288 | |
amentis | 032b30ed04 | |
Thomas Georgios Giannos | 9a78d98751 | |
Thomas Georgios Giannos | c169a103b7 | |
amentis | 1b66d19bde | |
amentis | 7df098a010 | |
Thomas Georgios Giannos | 079382556f | |
Thomas Georgios Giannos | 7ab1313b83 | |
amentis | bf8edfad92 | |
Efstratios Giannopoulos | a68ddd42ce | |
Efstratios Giannopoulos | de566729f1 | |
Thomas Georgios Giannos | e149b61b58 | |
Thomas Georgios Giannos | 1e733cf704 | |
Thomas Georgios Giannos | 3e7b9ca90b | |
Thomas Georgios Giannos | e27468b596 | |
Thomas Georgios Giannos | 485f7311cd | |
Thomas Georgios Giannos | e3536285aa | |
Efstratios Giannopoulos | a68668f07e | |
Thomas Georgios Giannos | dc380cdc01 | |
Diamantis Tziotzios | 2472f6de42 | |
Efstratios Giannopoulos | 167d3d4a4b | |
amentis | 10615c6fa9 | |
Efstratios Giannopoulos | 3b9b348d4d | |
Efstratios Giannopoulos | fe7d6f59b1 | |
Thomas Georgios Giannos | f4d84aa807 | |
Thomas Georgios Giannos | 074ca35970 | |
Thomas Georgios Giannos | eeaa35fab5 | |
Diamantis Tziotzios | 172caabcd2 | |
Diamantis Tziotzios | 6a8cb2c853 | |
Alexandros Mandilaras | 65e80c9877 | |
Thomas Georgios Giannos | 61f2810fcc | |
Diamantis Tziotzios | 6d6a4e0341 | |
Diamantis Tziotzios | 0c1509a1a1 | |
Thomas Georgios Giannos | 10df3db7aa | |
Thomas Georgios Giannos | c338a93d71 | |
Efstratios Giannopoulos | 6cde8ae2a9 | |
Efstratios Giannopoulos | 3ad7441bb5 | |
Diamantis Tziotzios | 3422b147c8 | |
Efstratios Giannopoulos | 30bff76163 | |
Efstratios Giannopoulos | 88088d64fd | |
Diamantis Tziotzios | 9ed94450bd | |
Diamantis Tziotzios | 4d3098860d | |
amentis | 0bd3ff6898 | |
Thomas Georgios Giannos | a94cce47e8 | |
George Kalampokis | 7d4b4c171b | |
Efstratios Giannopoulos | 0b3178177a | |
Thomas Georgios Giannos | 2a4e61f08f | |
Thomas Georgios Giannos | 727702023f | |
Efstratios Giannopoulos | 16604a8a50 | |
Thomas Georgios Giannos | 07ff58fbd9 | |
Efstratios Giannopoulos | a235eb98f4 | |
Efstratios Giannopoulos | ce8ff28955 | |
Efstratios Giannopoulos | 52e59ac11c | |
George Kalampokis | dc752f73ca | |
George Kalampokis | 5885e3ae5c | |
George Kalampokis | 093837c395 | |
amentis | 7cba7b2346 | |
Thomas Georgios Giannos | a38342e534 | |
Diamantis Tziotzios | 8574f33e56 | |
Efstratios Giannopoulos | 941f2d69cc | |
Efstratios Giannopoulos | 5c17027df2 | |
amentis | 5203ec4b72 | |
amentis | b2119abc08 | |
Thomas Georgios Giannos | b7d22b5b07 | |
amentis | 1cda821d9c | |
Diamantis Tziotzios | e97479aa03 | |
amentis | ad91234fd9 | |
amentis | 17bb6bd4b0 | |
Efstratios Giannopoulos | 66c650495f | |
Efstratios Giannopoulos | 27bcc31e54 | |
amentis | 3dee4857f8 | |
amentis | f67ada842e | |
Thomas Georgios Giannos | 651319ac5c | |
amentis | b8affa3033 | |
Efstratios Giannopoulos | 43f77a4497 | |
Efstratios Giannopoulos | b3a73a50aa | |
Diamantis Tziotzios | 9fd30b1198 | |
Diamantis Tziotzios | 3eaf326c50 | |
Efstratios Giannopoulos | 7ed111e936 | |
Efstratios Giannopoulos | 419c4d64f8 | |
Efstratios Giannopoulos | bd2807fbdf | |
Efstratios Giannopoulos | 925f195f5a | |
Efstratios Giannopoulos | 6443654343 | |
Efstratios Giannopoulos | 191f887530 | |
Thomas Georgios Giannos | b537810406 | |
Thomas Georgios Giannos | 234aa359c0 | |
Efstratios Giannopoulos | d5c62539c5 | |
George Kalampokis | 5976084473 | |
George Kalampokis | 959185a6c3 | |
George Kalampokis | a9a3273f62 | |
Efstratios Giannopoulos | 9c8488febb | |
Efstratios Giannopoulos | d14107062a | |
Efstratios Giannopoulos | 5009d658ed | |
Efstratios Giannopoulos | 055d29b95f | |
Diamantis Tziotzios | 958fc468fa | |
Diamantis Tziotzios | 6b2fae03dd | |
George Kalampokis | b845450ebd | |
Efstratios Giannopoulos | 968f45d5b3 | |
Efstratios Giannopoulos | 8b05ee1d0f | |
George Kalampokis | de2f5e67fd | |
George Kalampokis | 755c79b7af | |
George Kalampokis | d45867585f | |
George Kalampokis | 166bea73bf | |
Thomas Georgios Giannos | 55aecd9c57 | |
George Kalampokis | cab2f1bc64 | |
George Kalampokis | b5cbe6a3d8 | |
George Kalampokis | 70308db668 | |
Thomas Georgios Giannos | 51fbbc933a | |
Thomas Georgios Giannos | c7778e80b9 | |
Thomas Georgios Giannos | 39163ed6bc | |
Thomas Georgios Giannos | 0bb4c7ca15 | |
Thomas Georgios Giannos | 0b901205ee | |
Diamantis Tziotzios | 922c2110ac | |
Diamantis Tziotzios | 5a46345f15 | |
Diamantis Tziotzios | 96c2940eb0 | |
Diamantis Tziotzios | 203ee4fb29 | |
George Kalampokis | 05a724d757 | |
George Kalampokis | 3d612812b0 | |
Diamantis Tziotzios | 66052f8af6 | |
Bernaldo Mihasi | b7f68f1a7e | |
Bernaldo Mihasi | f841c2a2cc | |
Bernaldo Mihasi | 03f2bc862e | |
Bernaldo Mihasi | 4373bf4b00 | |
Bernaldo Mihasi | 09dcf5328d | |
Bernaldo Mihasi | c58de51b33 | |
Bernaldo Mihasi | 94c0f7ac9c | |
Thomas Georgios Giannos | 3f14528f16 | |
Thomas Georgios Giannos | 074e374d9a | |
Thomas Georgios Giannos | 7c96078570 |
|
@ -46,5 +46,11 @@ ELK.Docker/shared/data-elk/
|
||||||
.settings/
|
.settings/
|
||||||
bin/
|
bin/
|
||||||
*.classpath
|
*.classpath
|
||||||
|
.run
|
||||||
openDMP/dmp-backend/uploads/
|
openDMP/dmp-backend/uploads/
|
||||||
openDMP/dmp-backend/tmp/
|
openDMP/dmp-backend/tmp/
|
||||||
|
logs/
|
||||||
|
dmp-backend/web/src/main/resources/certificates/
|
||||||
|
/storage/
|
||||||
|
dmp-backend/target/classes/
|
||||||
|
dmp-backend/core/target/maven-archiver/
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
####################################### Build stage #######################################
|
||||||
|
FROM maven:3.9-eclipse-temurin-21-alpine AS build-stage
|
||||||
|
|
||||||
|
ARG MAVEN_ACCOUNT_USR
|
||||||
|
ARG MAVEN_ACCOUNT_PSW
|
||||||
|
ARG REVISION
|
||||||
|
ARG PROFILE
|
||||||
|
ARG DEV_PROFILE_URL
|
||||||
|
ENV server_username=$MAVEN_ACCOUNT_USR
|
||||||
|
ENV server_password=$MAVEN_ACCOUNT_PSW
|
||||||
|
|
||||||
|
COPY pom.xml /build/
|
||||||
|
COPY annotation /build/annotation/
|
||||||
|
COPY annotation-web /build/annotation-web/
|
||||||
|
COPY settings.xml /root/.m2/settings.xml
|
||||||
|
RUN rm -f /build/annotation-web/src/main/resources/config/app.env
|
||||||
|
RUN rm -f /build/annotation-web/src/main/resources/config/*-devel.yml
|
||||||
|
RUN rm -f /build/annotation-web/src/main/resources/logging/*.xml
|
||||||
|
RUN rm -f /build/annotation-web/src/main/resources/certificates/*.crt
|
||||||
|
|
||||||
|
WORKDIR /build/
|
||||||
|
|
||||||
|
RUN mvn -Drevision=${REVISION} -DdevProfileUrl=${DEV_PROFILE_URL} -P${PROFILE} dependency:go-offline
|
||||||
|
# Build project
|
||||||
|
RUN mvn -Drevision=${REVISION} -DdevProfileUrl=${DEV_PROFILE_URL} -P${PROFILE} clean package
|
||||||
|
|
||||||
|
######################################## Run Stage ########################################
|
||||||
|
FROM eclipse-temurin:21-jre-ubi9-minimal
|
||||||
|
|
||||||
|
ARG PROFILE
|
||||||
|
ARG REVISION
|
||||||
|
ENV SERVER_PORT=8080
|
||||||
|
EXPOSE ${SERVER_PORT}
|
||||||
|
|
||||||
|
COPY --from=build-stage /build/annotation-web/target/annotation-web-${REVISION}.jar /app/annotation-web.jar
|
||||||
|
|
||||||
|
ENTRYPOINT ["java","-Dspring.config.additional-location=file:/config/","-Dspring.profiles.active=${PROFILE}","-Djava.security.egd=file:/dev/./urandom","-jar","/app/annotation-web.jar"]
|
|
@ -0,0 +1,33 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>annotation-service-parent</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>annotation-web</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>21</maven.compiler.release>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<revision>1.0.0-SNAPSHOT</revision>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.orm</groupId>
|
||||||
|
<artifactId>hibernate-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>annotation</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>oidc-authz</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>cache</artifactId>
|
||||||
|
<version>2.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>exceptions-web</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>cors-web</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,26 @@
|
||||||
|
package gr.cite.annotation.web;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
|
@SpringBootApplication(
|
||||||
|
scanBasePackages = {
|
||||||
|
"eu.eudat",
|
||||||
|
"gr.cite",
|
||||||
|
"gr.cite.queueoutbox",
|
||||||
|
"gr.cite.queueinbox",
|
||||||
|
"gr.cite.annotation.integrationevent",
|
||||||
|
"gr.cite.tools",
|
||||||
|
"gr.cite.commons"})
|
||||||
|
@EntityScan({
|
||||||
|
"gr.cite.annotation.data"})
|
||||||
|
@EnableAsync
|
||||||
|
public class AnnotationApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AnnotationApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package gr.cite.annotation.web;
|
||||||
|
|
||||||
|
import gr.cite.annotation.web.scope.tenant.TenantInterceptor;
|
||||||
|
import gr.cite.annotation.web.scope.tenant.TenantScopeClaimInterceptor;
|
||||||
|
import gr.cite.annotation.web.scope.tenant.TenantScopeHeaderInterceptor;
|
||||||
|
import gr.cite.annotation.web.scope.user.UserInterceptor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebConfiguration implements WebMvcConfigurer {
|
||||||
|
private final TenantInterceptor tenantInterceptor;
|
||||||
|
private final TenantScopeHeaderInterceptor scopeHeaderInterceptor;
|
||||||
|
private final TenantScopeClaimInterceptor scopeClaimInterceptor;
|
||||||
|
private final UserInterceptor userInterceptor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WebConfiguration(
|
||||||
|
TenantInterceptor tenantInterceptor,
|
||||||
|
TenantScopeHeaderInterceptor scopeHeaderInterceptor,
|
||||||
|
TenantScopeClaimInterceptor scopeClaimInterceptor,
|
||||||
|
UserInterceptor userInterceptor
|
||||||
|
) {
|
||||||
|
this.tenantInterceptor = tenantInterceptor;
|
||||||
|
this.scopeHeaderInterceptor = scopeHeaderInterceptor;
|
||||||
|
this.scopeClaimInterceptor = scopeClaimInterceptor;
|
||||||
|
this.userInterceptor = userInterceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
int order = 1;
|
||||||
|
registry.addWebRequestInterceptor(scopeHeaderInterceptor).order(order++);
|
||||||
|
registry.addWebRequestInterceptor(scopeClaimInterceptor).order(order++);
|
||||||
|
registry.addWebRequestInterceptor(userInterceptor).order(order++);
|
||||||
|
registry.addWebRequestInterceptor(tenantInterceptor).order(order++);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package gr.cite.annotation.web.authorization;
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedAuthorizationRequirement;
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedResource;
|
||||||
|
import gr.cite.commons.web.authz.handler.AuthorizationHandler;
|
||||||
|
import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext;
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
|
||||||
|
import gr.cite.commons.web.oidc.principal.MyPrincipal;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("affiliatedAuthorizationHandler")
|
||||||
|
public class AffiliatedAuthorizationHandler extends AuthorizationHandler<AffiliatedAuthorizationRequirement> {
|
||||||
|
|
||||||
|
private final CustomPermissionAttributesConfiguration myConfiguration;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public AffiliatedAuthorizationHandler(CustomPermissionAttributesConfiguration myConfiguration) {
|
||||||
|
this.myConfiguration = myConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) {
|
||||||
|
AffiliatedAuthorizationRequirement req = (AffiliatedAuthorizationRequirement) requirement;
|
||||||
|
if (req.getRequiredPermissions() == null)
|
||||||
|
return ACCESS_NOT_DETERMINED;
|
||||||
|
|
||||||
|
AffiliatedResource rs = (AffiliatedResource) resource;
|
||||||
|
|
||||||
|
boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated();
|
||||||
|
if (!isAuthenticated)
|
||||||
|
return ACCESS_NOT_DETERMINED;
|
||||||
|
|
||||||
|
if (myConfiguration.getMyPolicies() == null)
|
||||||
|
return ACCESS_NOT_DETERMINED;
|
||||||
|
|
||||||
|
int hits = 0;
|
||||||
|
Boolean entityAffiliated = rs != null && rs.getAffiliated() != null ? rs.getAffiliated() : null;
|
||||||
|
|
||||||
|
for (String permission : req.getRequiredPermissions()) {
|
||||||
|
CustomPermissionAttributesProperties.MyPermission policy = myConfiguration.getMyPolicies().get(permission);
|
||||||
|
boolean hasPermission = policy != null && policy.getEntityAffiliated() != null && policy.getEntityAffiliated() && entityAffiliated != null && entityAffiliated;
|
||||||
|
if (hasPermission) hits += 1;
|
||||||
|
}
|
||||||
|
if ((req.getMatchAll() && req.getRequiredPermissions().size() == hits) || (!req.getMatchAll() && hits > 0))
|
||||||
|
return ACCESS_GRANTED;
|
||||||
|
|
||||||
|
return ACCESS_NOT_DETERMINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends AuthorizationRequirement> supporting() {
|
||||||
|
return AffiliatedAuthorizationRequirement.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package gr.cite.annotation.web.authorization;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(CustomPermissionAttributesProperties.class)
|
||||||
|
public class CustomPermissionAttributesConfiguration {
|
||||||
|
|
||||||
|
private final CustomPermissionAttributesProperties properties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CustomPermissionAttributesConfiguration(CustomPermissionAttributesProperties properties) {
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, CustomPermissionAttributesProperties.MyPermission> getMyPolicies() {
|
||||||
|
return properties.getPolicies();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package gr.cite.annotation.web.authorization;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "permissions")
|
||||||
|
@ConditionalOnProperty(prefix = "permissions", name = "enabled", havingValue = "true")
|
||||||
|
public class CustomPermissionAttributesProperties {
|
||||||
|
|
||||||
|
private final HashMap<String, MyPermission> policies;
|
||||||
|
|
||||||
|
@ConstructorBinding
|
||||||
|
public CustomPermissionAttributesProperties(HashMap<String, MyPermission> policies) {
|
||||||
|
this.policies = policies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, MyPermission> getPolicies() {
|
||||||
|
return policies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MyPermission {
|
||||||
|
|
||||||
|
private final Boolean entityAffiliated;
|
||||||
|
|
||||||
|
@ConstructorBinding
|
||||||
|
public MyPermission(Boolean entityAffiliated) {
|
||||||
|
this.entityAffiliated = entityAffiliated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEntityAffiliated() {
|
||||||
|
return entityAffiliated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package gr.cite.annotation.web.authorization;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.handler.AuthorizationHandler;
|
||||||
|
import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext;
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
|
||||||
|
import gr.cite.commons.web.oidc.principal.MyPrincipal;
|
||||||
|
import gr.cite.annotation.authorization.OwnedAuthorizationRequirement;
|
||||||
|
import gr.cite.annotation.authorization.OwnedResource;
|
||||||
|
import gr.cite.annotation.common.scope.user.UserScope;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component("ownedAuthorizationHandler")
|
||||||
|
public class OwnedAuthorizationHandler extends AuthorizationHandler<OwnedAuthorizationRequirement> {
|
||||||
|
|
||||||
|
private final UserScope userScope;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public OwnedAuthorizationHandler(UserScope userScope) {
|
||||||
|
this.userScope = userScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) {
|
||||||
|
OwnedAuthorizationRequirement req = (OwnedAuthorizationRequirement) requirement;
|
||||||
|
|
||||||
|
OwnedResource rs = (OwnedResource) resource;
|
||||||
|
|
||||||
|
boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated();
|
||||||
|
if (!isAuthenticated) return ACCESS_NOT_DETERMINED;
|
||||||
|
|
||||||
|
if (this.userScope.getUserIdSafe() == null) return ACCESS_NOT_DETERMINED;
|
||||||
|
|
||||||
|
if (rs != null && rs.getUserIds() != null && rs.getUserIds().contains(this.userScope.getUserIdSafe())) return ACCESS_GRANTED;
|
||||||
|
|
||||||
|
return ACCESS_NOT_DETERMINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends AuthorizationRequirement> supporting() {
|
||||||
|
return OwnedAuthorizationRequirement.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package gr.cite.annotation.web.config;
|
||||||
|
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||||
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class AppMessageSourceConfiguration {
|
||||||
|
@Bean
|
||||||
|
public MessageSource messageSource() {
|
||||||
|
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||||
|
messageSource.setBasename("classpath:messages/messages");
|
||||||
|
messageSource.setDefaultEncoding("UTF-8");
|
||||||
|
return messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocalValidatorFactoryBean getValidator() {
|
||||||
|
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
|
||||||
|
bean.setValidationMessageSource(messageSource());
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package gr.cite.annotation.web.config;
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedAuthorizationRequirement;
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedResource;
|
||||||
|
import gr.cite.annotation.web.authorization.AffiliatedAuthorizationHandler;
|
||||||
|
import gr.cite.commons.web.authz.handler.AuthorizationHandler;
|
||||||
|
import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler;
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirementMapper;
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationResource;
|
||||||
|
import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyConfigurer;
|
||||||
|
import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyResolverStrategy;
|
||||||
|
import gr.cite.commons.web.oidc.configuration.WebSecurityProperties;
|
||||||
|
import gr.cite.annotation.authorization.OwnedAuthorizationRequirement;
|
||||||
|
import gr.cite.annotation.authorization.OwnedResource;
|
||||||
|
import gr.cite.annotation.web.authorization.OwnedAuthorizationHandler;
|
||||||
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
|
private final WebSecurityProperties webSecurityProperties;
|
||||||
|
private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
|
||||||
|
private final Filter apiKeyFilter;
|
||||||
|
private final OwnedAuthorizationHandler ownedAuthorizationHandler;
|
||||||
|
private final AffiliatedAuthorizationHandler affiliatedAuthorizationHandler;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SecurityConfiguration(WebSecurityProperties webSecurityProperties,
|
||||||
|
@Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver,
|
||||||
|
@Qualifier("apiKeyFilter") Filter apiKeyFilter,
|
||||||
|
@Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler,
|
||||||
|
@Qualifier("affiliatedAuthorizationHandler") AffiliatedAuthorizationHandler affiliatedAuthorizationHandler) {
|
||||||
|
this.webSecurityProperties = webSecurityProperties;
|
||||||
|
this.authenticationManagerResolver = authenticationManagerResolver;
|
||||||
|
this.apiKeyFilter = apiKeyFilter;
|
||||||
|
this.ownedAuthorizationHandler = ownedAuthorizationHandler;
|
||||||
|
this.affiliatedAuthorizationHandler = affiliatedAuthorizationHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
HttpSecurity tempHttp = http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.cors(httpSecurityCorsConfigurer -> {})
|
||||||
|
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||||
|
.addFilterBefore(apiKeyFilter, AbstractPreAuthenticatedProcessingFilter.class)
|
||||||
|
.authorizeHttpRequests(authRequest ->
|
||||||
|
authRequest.requestMatchers(buildAntPatterns(webSecurityProperties.getAllowedEndpoints())).anonymous()
|
||||||
|
.requestMatchers(buildAntPatterns(webSecurityProperties.getAuthorizedEndpoints())).authenticated())
|
||||||
|
.sessionManagement( sessionManagementConfigurer-> sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.NEVER))
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
|
||||||
|
return tempHttp.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AuthorizationPolicyConfigurer authorizationPolicyConfigurer() {
|
||||||
|
return new AuthorizationPolicyConfigurer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthorizationPolicyResolverStrategy strategy() {
|
||||||
|
return AuthorizationPolicyResolverStrategy.STRICT_CONSENSUS_BASED;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here you can register your custom authorization handlers, which will get used as well as the existing ones
|
||||||
|
//This is optional and can be omitted
|
||||||
|
//If not set / set to null, only the default authorization handlers will be used
|
||||||
|
@Override
|
||||||
|
public List<AuthorizationHandler<? extends AuthorizationRequirement>> addCustomHandlers() {
|
||||||
|
return List.of(affiliatedAuthorizationHandler, ownedAuthorizationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here you can register your custom authorization requirements (if any)
|
||||||
|
//This is optional and can be omitted
|
||||||
|
//If not set / set to null, only the default authorization requirements will be used
|
||||||
|
@Override
|
||||||
|
public List<? extends AuthorizationRequirement> extendRequirements() {
|
||||||
|
return List.of(
|
||||||
|
// new TimeOfDayAuthorizationRequirement(new TimeOfDay("08:00","16:00"), true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Here you can select handlers you want to disable by providing the classes they are implemented by
|
||||||
|
//You can disable any handler (including any custom one)
|
||||||
|
//This is optional and can be omitted
|
||||||
|
//If not set / set to null, all the handlers will be invoked, based on their requirement support
|
||||||
|
//In the example below, the default client handler will be ignored by the resolver
|
||||||
|
@Override
|
||||||
|
public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
AuthorizationRequirementMapper authorizationRequirementMapper() {
|
||||||
|
return new AuthorizationRequirementMapper() {
|
||||||
|
@Override
|
||||||
|
public AuthorizationRequirement map(AuthorizationResource resource, boolean matchAll, String[] permissions) {
|
||||||
|
Class<?> type = resource.getClass();
|
||||||
|
if (!AuthorizationResource.class.isAssignableFrom(type)) throw new IllegalArgumentException("resource");
|
||||||
|
|
||||||
|
if (OwnedResource.class.equals(type)) {
|
||||||
|
return new OwnedAuthorizationRequirement();
|
||||||
|
}
|
||||||
|
if (AffiliatedResource.class.equals(type)) {
|
||||||
|
return new AffiliatedAuthorizationRequirement(matchAll, permissions);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("resource");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] buildAntPatterns(Set<String> endpoints) {
|
||||||
|
if (endpoints == null) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
return endpoints.stream()
|
||||||
|
.filter(endpoint -> endpoint != null && !endpoint.isBlank())
|
||||||
|
.map(endpoint -> "/" + stripUnnecessaryCharacters(endpoint) + "/**")
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stripUnnecessaryCharacters(String endpoint) {
|
||||||
|
endpoint = endpoint.strip();
|
||||||
|
if (endpoint.startsWith("/")) {
|
||||||
|
endpoint = endpoint.substring(1);
|
||||||
|
}
|
||||||
|
if (endpoint.endsWith("/")) {
|
||||||
|
endpoint = endpoint.substring(0, endpoint.length() - 1);
|
||||||
|
}
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
package gr.cite.annotation.web.controllerhandler;
|
||||||
|
|
||||||
|
import gr.cite.annotation.common.JsonHandlingService;
|
||||||
|
import gr.cite.tools.exception.*;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(GlobalExceptionHandler.class));
|
||||||
|
|
||||||
|
private final JsonHandlingService jsonHandlingService;
|
||||||
|
|
||||||
|
public GlobalExceptionHandler(JsonHandlingService jsonHandlingService) {
|
||||||
|
this.jsonHandlingService = jsonHandlingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public ResponseEntity<?> handleUnexpectedErrors(Exception exception, WebRequest request) throws Exception {
|
||||||
|
HandledException handled = this.handleException(exception, request);
|
||||||
|
this.log(handled.getLevel(), exception, MessageFormat.format("returning code {0} and payload {1}", handled.getStatusCode(), handled.getMessage()));
|
||||||
|
return new ResponseEntity<>(handled.getMessage(), handled.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void log(System.Logger.Level level, Exception e, String message) {
|
||||||
|
if (level != null) {
|
||||||
|
switch (level) {
|
||||||
|
case TRACE:
|
||||||
|
logger.trace(message, e);
|
||||||
|
break;
|
||||||
|
case DEBUG:
|
||||||
|
logger.debug(message, e);
|
||||||
|
break;
|
||||||
|
case INFO:
|
||||||
|
logger.info(message, e);
|
||||||
|
break;
|
||||||
|
case WARNING:
|
||||||
|
logger.warn(message, e);
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
logger.error(message, e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HandledException handleException(Exception exception, WebRequest request) throws Exception {
|
||||||
|
HttpStatus statusCode;
|
||||||
|
Map<String, Object> result;
|
||||||
|
System.Logger.Level logLevel;
|
||||||
|
|
||||||
|
switch (exception){
|
||||||
|
case MyNotFoundException myNotFoundException -> {
|
||||||
|
logLevel = System.Logger.Level.DEBUG;
|
||||||
|
statusCode = HttpStatus.NOT_FOUND;
|
||||||
|
int code = myNotFoundException.getCode();
|
||||||
|
if (code > 0) {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("code", code),
|
||||||
|
Map.entry("error", myNotFoundException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", myNotFoundException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case MyUnauthorizedException myUnauthorizedException -> {
|
||||||
|
logLevel = System.Logger.Level.DEBUG;
|
||||||
|
statusCode = HttpStatus.UNAUTHORIZED;
|
||||||
|
int code = myUnauthorizedException.getCode();
|
||||||
|
if (code > 0) {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("code", code),
|
||||||
|
Map.entry("error", myUnauthorizedException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", myUnauthorizedException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case MyForbiddenException myForbiddenException -> {
|
||||||
|
logLevel = System.Logger.Level.DEBUG;
|
||||||
|
statusCode = HttpStatus.FORBIDDEN;
|
||||||
|
int code = myForbiddenException.getCode();
|
||||||
|
if (code > 0) {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("code", code),
|
||||||
|
Map.entry("error", myForbiddenException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", myForbiddenException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case MyValidationException myValidationException -> {
|
||||||
|
logLevel = System.Logger.Level.DEBUG;
|
||||||
|
statusCode = HttpStatus.BAD_REQUEST;
|
||||||
|
int code = myValidationException.getCode();
|
||||||
|
if (code > 0) {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("code", code),
|
||||||
|
Map.entry("error", myValidationException.getMessage()),
|
||||||
|
Map.entry("message", myValidationException.getErrors())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", myValidationException.getMessage()),
|
||||||
|
Map.entry("message", myValidationException.getErrors())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case MyApplicationException myApplicationException -> {
|
||||||
|
logLevel = System.Logger.Level.ERROR;
|
||||||
|
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
int code = myApplicationException.getCode();
|
||||||
|
if (code > 0) {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("code", code),
|
||||||
|
Map.entry("error", myApplicationException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", myApplicationException.getMessage())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
logLevel = System.Logger.Level.ERROR;
|
||||||
|
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
result = Map.ofEntries(
|
||||||
|
Map.entry("error", "System error")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
String serialization = this.jsonHandlingService.toJsonSafe(result);
|
||||||
|
return new HandledException(statusCode, serialization, logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HandledException{
|
||||||
|
public HttpStatus statusCode;
|
||||||
|
public String message;
|
||||||
|
public System.Logger.Level level;
|
||||||
|
|
||||||
|
public HandledException(HttpStatus statusCode, String message, System.Logger.Level level) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.message = message;
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpStatus getStatusCode() {
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusCode(HttpStatus statusCode) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public System.Logger.Level getLevel() {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLevel(System.Logger.Level level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
package gr.cite.annotation.web.controllers;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import gr.cite.annotation.audit.AuditableAction;
|
||||||
|
import gr.cite.annotation.authorization.AuthorizationFlags;
|
||||||
|
import gr.cite.annotation.data.AnnotationEntity;
|
||||||
|
import gr.cite.annotation.model.Annotation;
|
||||||
|
import gr.cite.annotation.model.builder.AnnotationBuilder;
|
||||||
|
import gr.cite.annotation.model.censorship.AnnotationCensor;
|
||||||
|
import gr.cite.annotation.model.persist.AnnotationPersist;
|
||||||
|
import gr.cite.annotation.query.AnnotationQuery;
|
||||||
|
import gr.cite.annotation.query.lookup.AnnotationLookup;
|
||||||
|
import gr.cite.annotation.service.annotation.AnnotationService;
|
||||||
|
import gr.cite.annotation.web.model.QueryResult;
|
||||||
|
import gr.cite.tools.auditing.AuditService;
|
||||||
|
import gr.cite.tools.data.builder.BuilderFactory;
|
||||||
|
import gr.cite.tools.data.censor.CensorFactory;
|
||||||
|
import gr.cite.tools.data.query.QueryFactory;
|
||||||
|
import gr.cite.tools.exception.MyApplicationException;
|
||||||
|
import gr.cite.tools.exception.MyForbiddenException;
|
||||||
|
import gr.cite.tools.exception.MyNotFoundException;
|
||||||
|
import gr.cite.tools.fieldset.FieldSet;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import gr.cite.tools.logging.MapLogEntry;
|
||||||
|
import gr.cite.tools.validation.ValidationFilterAnnotation;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.xml.bind.JAXBException;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "api/annotation", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public class AnnotationController {
|
||||||
|
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(AnnotationController.class));
|
||||||
|
|
||||||
|
private final CensorFactory censorFactory;
|
||||||
|
|
||||||
|
private final QueryFactory queryFactory;
|
||||||
|
|
||||||
|
private final BuilderFactory builderFactory;
|
||||||
|
|
||||||
|
private final AuditService auditService;
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
private final AnnotationService annotationService;
|
||||||
|
|
||||||
|
public AnnotationController(CensorFactory censorFactory, QueryFactory queryFactory, BuilderFactory builderFactory, AuditService auditService, MessageSource messageSource, AnnotationService annotationService) {
|
||||||
|
this.censorFactory = censorFactory;
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
this.builderFactory = builderFactory;
|
||||||
|
this.auditService = auditService;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
this.annotationService = annotationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("query")
|
||||||
|
public QueryResult<Annotation> query(@RequestBody AnnotationLookup lookup) {
|
||||||
|
logger.debug("querying {}", Annotation.class.getSimpleName());
|
||||||
|
|
||||||
|
this.censorFactory.censor(AnnotationCensor.class).censor(lookup.getProject(), null);
|
||||||
|
|
||||||
|
AnnotationQuery query = lookup.enrich(this.queryFactory).authorize(AuthorizationFlags.OwnerOrPermissionAssociated);
|
||||||
|
List<AnnotationEntity> data = query.collect();
|
||||||
|
List<Annotation> models = this.builderFactory.builder(AnnotationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(lookup.getProject(), data);
|
||||||
|
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Annotation_Query, "lookup", lookup);
|
||||||
|
|
||||||
|
return new QueryResult<>(models, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public Annotation get(@PathVariable("id") UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||||
|
logger.debug(new MapLogEntry("retrieving" + Annotation.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||||
|
|
||||||
|
this.censorFactory.censor(AnnotationCensor.class).censor(fieldSet, null);
|
||||||
|
|
||||||
|
AnnotationQuery query = this.queryFactory.query(AnnotationQuery.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).ids(id);
|
||||||
|
Annotation model = this.builderFactory.builder(AnnotationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(fieldSet, query.firstAs(fieldSet));
|
||||||
|
if (model == null)
|
||||||
|
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Annotation.class.getSimpleName()}, LocaleContextHolder.getLocale()));
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Annotation_Lookup, Map.ofEntries(
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("id", id),
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
|
||||||
|
));
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("persist")
|
||||||
|
@Transactional
|
||||||
|
@ValidationFilterAnnotation(validator = AnnotationPersist.AnnotationPersistValidator.ValidatorName, argumentName = "model")
|
||||||
|
public Annotation persist(@RequestBody AnnotationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JAXBException, ParserConfigurationException, JsonProcessingException, TransformerException {
|
||||||
|
logger.debug(new MapLogEntry("persisting" + Annotation.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
|
||||||
|
|
||||||
|
this.censorFactory.censor(AnnotationCensor.class).censor(fieldSet, null);
|
||||||
|
|
||||||
|
Annotation persisted = this.annotationService.persist(model, fieldSet);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Annotation_Persist, Map.ofEntries(
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("model", model),
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
|
||||||
|
));
|
||||||
|
|
||||||
|
return persisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
@Transactional
|
||||||
|
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
|
||||||
|
logger.debug(new MapLogEntry("retrieving" + Annotation.class.getSimpleName()).And("id", id));
|
||||||
|
|
||||||
|
this.annotationService.deleteAndSave(id);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Annotation_Delete, "id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package gr.cite.annotation.web.controllers;
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.ClaimNames;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.MyPrincipal;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||||
|
import gr.cite.annotation.audit.AuditableAction;
|
||||||
|
import gr.cite.annotation.common.scope.tenant.TenantScope;
|
||||||
|
import gr.cite.annotation.web.model.Account;
|
||||||
|
import gr.cite.annotation.web.model.AccountBuilder;
|
||||||
|
import gr.cite.tools.auditing.AuditService;
|
||||||
|
import gr.cite.tools.fieldset.BaseFieldSet;
|
||||||
|
import gr.cite.tools.fieldset.FieldSet;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "api/annotation/principal", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public class PrincipalController {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
|
||||||
|
private final AuditService auditService;
|
||||||
|
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
private final AccountBuilder accountBuilder;
|
||||||
|
private final ClaimExtractor claimExtractor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PrincipalController(
|
||||||
|
CurrentPrincipalResolver currentPrincipalResolver,
|
||||||
|
AccountBuilder accountBuilder,
|
||||||
|
AuditService auditService,
|
||||||
|
ClaimExtractor claimExtractor) {
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
this.accountBuilder = accountBuilder;
|
||||||
|
this.auditService = auditService;
|
||||||
|
this.claimExtractor = claimExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("me")
|
||||||
|
public Account me(FieldSet fieldSet) {
|
||||||
|
logger.debug("me");
|
||||||
|
|
||||||
|
|
||||||
|
if (fieldSet == null || fieldSet.isEmpty()) {
|
||||||
|
fieldSet = new BaseFieldSet(
|
||||||
|
Account._isAuthenticated,
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._subject),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._userId),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._name),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._scope),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._client),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._issuedAt),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._notBefore),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt),
|
||||||
|
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more),
|
||||||
|
Account._permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
|
||||||
|
|
||||||
|
Account me = this.accountBuilder.build(fieldSet, principal);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Principal_Lookup);
|
||||||
|
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
|
||||||
|
|
||||||
|
return me;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("my-tenants")
|
||||||
|
public List<String> myTenants() {
|
||||||
|
logger.debug("my-tenants");
|
||||||
|
|
||||||
|
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
|
||||||
|
List<String> tenants = this.claimExtractor.asStrings(principal, ClaimNames.TenantCodesClaimName);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Tenants_Lookup);
|
||||||
|
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
|
||||||
|
|
||||||
|
return tenants == null ? null : tenants.stream().distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
package gr.cite.annotation.web.controllers;
|
||||||
|
|
||||||
|
import gr.cite.annotation.audit.AuditableAction;
|
||||||
|
import gr.cite.annotation.authorization.AuthorizationFlags;
|
||||||
|
import gr.cite.annotation.common.enums.TenantConfigurationType;
|
||||||
|
import gr.cite.annotation.data.TenantConfigurationEntity;
|
||||||
|
import gr.cite.annotation.model.TenantConfiguration;
|
||||||
|
import gr.cite.annotation.model.builder.TenantConfigurationBuilder;
|
||||||
|
import gr.cite.annotation.model.censorship.TenantConfigurationCensor;
|
||||||
|
import gr.cite.annotation.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist;
|
||||||
|
import gr.cite.annotation.query.TenantConfigurationQuery;
|
||||||
|
import gr.cite.annotation.query.lookup.TenantConfigurationLookup;
|
||||||
|
import gr.cite.annotation.service.tenantconfiguration.TenantConfigurationService;
|
||||||
|
import gr.cite.annotation.web.model.QueryResult;
|
||||||
|
import gr.cite.tools.auditing.AuditService;
|
||||||
|
import gr.cite.tools.data.builder.BuilderFactory;
|
||||||
|
import gr.cite.tools.data.censor.CensorFactory;
|
||||||
|
import gr.cite.tools.data.query.QueryFactory;
|
||||||
|
import gr.cite.tools.exception.MyApplicationException;
|
||||||
|
import gr.cite.tools.exception.MyForbiddenException;
|
||||||
|
import gr.cite.tools.exception.MyNotFoundException;
|
||||||
|
import gr.cite.tools.fieldset.FieldSet;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import gr.cite.tools.logging.MapLogEntry;
|
||||||
|
import gr.cite.tools.validation.ValidationFilterAnnotation;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "api/annotation/tenant-configuration")
|
||||||
|
public class TenantConfigurationController {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationController.class));
|
||||||
|
|
||||||
|
private final BuilderFactory builderFactory;
|
||||||
|
private final AuditService auditService;
|
||||||
|
private final TenantConfigurationService tenantConfigurationService;
|
||||||
|
private final CensorFactory censorFactory;
|
||||||
|
private final QueryFactory queryFactory;
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantConfigurationController(BuilderFactory builderFactory,
|
||||||
|
AuditService auditService,
|
||||||
|
TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory,
|
||||||
|
QueryFactory queryFactory,
|
||||||
|
MessageSource messageSource) {
|
||||||
|
this.builderFactory = builderFactory;
|
||||||
|
this.auditService = auditService;
|
||||||
|
this.tenantConfigurationService = tenantConfigurationService;
|
||||||
|
this.censorFactory = censorFactory;
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("query")
|
||||||
|
public QueryResult<TenantConfiguration> query(@RequestBody TenantConfigurationLookup lookup) throws MyApplicationException, MyForbiddenException {
|
||||||
|
logger.debug("querying {}", TenantConfiguration.class.getSimpleName());
|
||||||
|
|
||||||
|
this.censorFactory.censor(TenantConfigurationCensor.class).censor(lookup.getProject());
|
||||||
|
|
||||||
|
TenantConfigurationQuery query = lookup.enrich(this.queryFactory);
|
||||||
|
List<TenantConfigurationEntity> data = query.collectAs(lookup.getProject());
|
||||||
|
List<TenantConfiguration> models = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(lookup.getProject(), data);
|
||||||
|
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Tenant_Configuration_Query, "lookup", lookup);
|
||||||
|
|
||||||
|
return new QueryResult<>(models, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
@Transactional
|
||||||
|
public TenantConfiguration get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
|
||||||
|
logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id).And("fields", fieldSet));
|
||||||
|
|
||||||
|
this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet);
|
||||||
|
|
||||||
|
TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).ids(id);
|
||||||
|
TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermissionAssociated).build(fieldSet, query.firstAs(fieldSet));
|
||||||
|
if (model == null)
|
||||||
|
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, TenantConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale()));
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Tenant_Configuration_Lookup, Map.ofEntries(
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("id", id),
|
||||||
|
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
|
||||||
|
));
|
||||||
|
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("persist/email-client")
|
||||||
|
@Transactional
|
||||||
|
@ValidationFilterAnnotation(validator = TenantConfigurationEmailClientPersist.TenantConfigurationEmailClientPersistValidator.ValidatorName, argumentName = "model")
|
||||||
|
public TenantConfiguration persist(@RequestBody TenantConfigurationEmailClientPersist model, FieldSet fieldSet)
|
||||||
|
{
|
||||||
|
logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION).And("model", model).And("fields", fieldSet));
|
||||||
|
|
||||||
|
TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Tenant_Configuration_Persist, Map.of(
|
||||||
|
"type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION,
|
||||||
|
"model", model,
|
||||||
|
"fields", fieldSet
|
||||||
|
));
|
||||||
|
//this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action);
|
||||||
|
|
||||||
|
return persisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("{id}")
|
||||||
|
@Transactional
|
||||||
|
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
|
||||||
|
logger.debug(new MapLogEntry("deleting" + TenantConfiguration.class.getSimpleName()).And("id", id));
|
||||||
|
|
||||||
|
this.tenantConfigurationService.deleteAndSave(id);
|
||||||
|
|
||||||
|
this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id);
|
||||||
|
|
||||||
|
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
package gr.cite.annotation.web.model;
|
||||||
|
|
||||||
|
import gr.cite.tools.logging.annotation.LogSensitive;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class Account {
|
||||||
|
|
||||||
|
public static class PrincipalInfo {
|
||||||
|
|
||||||
|
public static final String _userId = "userId";
|
||||||
|
public UUID userId;
|
||||||
|
|
||||||
|
public UUID getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(UUID userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _subject = "subject";
|
||||||
|
public UUID subject;
|
||||||
|
|
||||||
|
public UUID getSubject() {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubject(UUID subject) {
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _name = "name";
|
||||||
|
@LogSensitive
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _scope = "scope";
|
||||||
|
public List<String> scope;
|
||||||
|
|
||||||
|
public List<String> getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(List<String> scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _client = "client";
|
||||||
|
public String client;
|
||||||
|
|
||||||
|
public String getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(String client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _notBefore = "notBefore";
|
||||||
|
public Instant notBefore;
|
||||||
|
|
||||||
|
public Instant getNotBefore() {
|
||||||
|
return notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotBefore(Instant notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _issuedAt = "issuedAt";
|
||||||
|
public Instant issuedAt;
|
||||||
|
|
||||||
|
public Instant getIssuedAt() {
|
||||||
|
return issuedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssuedAt(Instant issuedAt) {
|
||||||
|
this.issuedAt = issuedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _authenticatedAt = "authenticatedAt";
|
||||||
|
public Instant authenticatedAt;
|
||||||
|
|
||||||
|
public Instant getAuthenticatedAt() {
|
||||||
|
return authenticatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticatedAt(Instant authenticatedAt) {
|
||||||
|
this.authenticatedAt = authenticatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _expiresAt = "expiresAt";
|
||||||
|
public Instant expiresAt;
|
||||||
|
|
||||||
|
public Instant getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(Instant expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _more = "more";
|
||||||
|
@LogSensitive
|
||||||
|
public Map<String, List<String>> more;
|
||||||
|
|
||||||
|
public Map<String, List<String>> getMore() {
|
||||||
|
return more;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMore(Map<String, List<String>> more) {
|
||||||
|
this.more = more;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final String _isAuthenticated = "isAuthenticated";
|
||||||
|
private Boolean isAuthenticated;
|
||||||
|
|
||||||
|
public Boolean getIsAuthenticated() {
|
||||||
|
return isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsAuthenticated(Boolean authenticated) {
|
||||||
|
isAuthenticated = authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _principal = "principal";
|
||||||
|
private PrincipalInfo principal;
|
||||||
|
|
||||||
|
public PrincipalInfo getPrincipal() {
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrincipal(PrincipalInfo principal) {
|
||||||
|
this.principal = principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String _permissions = "permissions";
|
||||||
|
private List<String> permissions;
|
||||||
|
|
||||||
|
public List<String> getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermissions(List<String> permissions) {
|
||||||
|
this.permissions = permissions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package gr.cite.annotation.web.model;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration;
|
||||||
|
import gr.cite.commons.web.authz.configuration.Permission;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.MyPrincipal;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorKeys;
|
||||||
|
import gr.cite.annotation.common.scope.user.UserScope;
|
||||||
|
import gr.cite.tools.fieldset.BaseFieldSet;
|
||||||
|
import gr.cite.tools.fieldset.FieldSet;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||||
|
public class AccountBuilder {
|
||||||
|
|
||||||
|
private final ClaimExtractor claimExtractor;
|
||||||
|
private final Set<String> excludeMoreClaim;
|
||||||
|
private final AuthorizationConfiguration authorizationConfiguration;
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
private final UserScope userScope;
|
||||||
|
|
||||||
|
public AccountBuilder(ClaimExtractor claimExtractor, AuthorizationConfiguration authorizationConfiguration, CurrentPrincipalResolver currentPrincipalResolver, UserScope userScope) {
|
||||||
|
this.claimExtractor = claimExtractor;
|
||||||
|
this.authorizationConfiguration = authorizationConfiguration;
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
this.userScope = userScope;
|
||||||
|
this.excludeMoreClaim = Set.of(
|
||||||
|
ClaimExtractorKeys.Subject,
|
||||||
|
ClaimExtractorKeys.Name,
|
||||||
|
ClaimExtractorKeys.Scope,
|
||||||
|
ClaimExtractorKeys.Client,
|
||||||
|
ClaimExtractorKeys.IssuedAt,
|
||||||
|
ClaimExtractorKeys.NotBefore,
|
||||||
|
ClaimExtractorKeys.AuthenticatedAt,
|
||||||
|
ClaimExtractorKeys.ExpiresAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account build(FieldSet fields, MyPrincipal principal) {
|
||||||
|
Account model = new Account();
|
||||||
|
if (principal == null || !principal.isAuthenticated()) {
|
||||||
|
model.setIsAuthenticated(false);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
model.setIsAuthenticated(true);
|
||||||
|
|
||||||
|
FieldSet principalFields = fields.extractPrefixed(BaseFieldSet.asIndexerPrefix(Account._principal));
|
||||||
|
if (!principalFields.isEmpty()) model.setPrincipal(new Account.PrincipalInfo());
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._subject)) model.getPrincipal().setSubject(this.claimExtractor.subjectUUID(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._userId)) model.getPrincipal().setUserId(this.userScope.getUserIdSafe());
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._name)) model.getPrincipal().setName(this.claimExtractor.name(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._scope)) model.getPrincipal().setScope(this.claimExtractor.scope(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._client)) model.getPrincipal().setClient(this.claimExtractor.client(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._issuedAt)) model.getPrincipal().setIssuedAt(this.claimExtractor.issuedAt(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._notBefore)) model.getPrincipal().setNotBefore(this.claimExtractor.notBefore(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._authenticatedAt)) model.getPrincipal().setAuthenticatedAt(this.claimExtractor.authenticatedAt(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._expiresAt)) model.getPrincipal().setExpiresAt(this.claimExtractor.expiresAt(principal));
|
||||||
|
if (principalFields.hasField(Account.PrincipalInfo._more)) {
|
||||||
|
model.getPrincipal().setMore(new HashMap<>());
|
||||||
|
for (String key : this.claimExtractor.knownPublicKeys()) {
|
||||||
|
if (this.excludeMoreClaim.contains(key)) continue;
|
||||||
|
List<String> values = this.claimExtractor.asStrings(principal, key);
|
||||||
|
if (values == null || values.size() == 0) continue;
|
||||||
|
if (!model.getPrincipal().getMore().containsKey(key)) model.getPrincipal().getMore().put(key, new ArrayList<>());
|
||||||
|
model.getPrincipal().getMore().get(key).addAll(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.hasField(Account._permissions)) {
|
||||||
|
List<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
|
||||||
|
Set<String> permissions = authorizationConfiguration.permissionsOfRoles(roles);
|
||||||
|
for (Map.Entry<String, Permission> permissionEntry : authorizationConfiguration.getRawPolicies().entrySet()){
|
||||||
|
if (permissionEntry.getValue().getAllowAuthenticated()){
|
||||||
|
permissions.add(permissionEntry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.setPermissions(new ArrayList<>(permissions));
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package gr.cite.annotation.web.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class QueryResult<M> {
|
||||||
|
public QueryResult() { }
|
||||||
|
public QueryResult(List<M> items, long count)
|
||||||
|
{
|
||||||
|
this.items = items;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<M> items;
|
||||||
|
public long count;
|
||||||
|
|
||||||
|
public List<M> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<M> items) {
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCount(long count) {
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryResult<?> Empty()
|
||||||
|
{
|
||||||
|
return new QueryResult<>(new ArrayList<>(), 0L);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.tools.cache.CacheOptions;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cache.tenant-by-code")
|
||||||
|
public class TenantByCodeCacheOptions extends CacheOptions {
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.event.TenantTouchedEvent;
|
||||||
|
import gr.cite.tools.cache.CacheService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TenantByCodeCacheService extends CacheService<TenantByCodeCacheService.TenantByCodeCacheValue> {
|
||||||
|
|
||||||
|
public static class TenantByCodeCacheValue {
|
||||||
|
|
||||||
|
public TenantByCodeCacheValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public TenantByCodeCacheValue(String tenantCode, UUID tenantId) {
|
||||||
|
this.tenantCode = tenantCode;
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tenantCode;
|
||||||
|
|
||||||
|
public String getTenantCode() {
|
||||||
|
return tenantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantCode(String tenantCode) {
|
||||||
|
this.tenantCode = tenantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID tenantId;
|
||||||
|
|
||||||
|
public UUID getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(UUID tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConventionService conventionService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) {
|
||||||
|
super(options);
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
|
||||||
|
if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
|
||||||
|
this.evict(this.buildKey(event.getTenantCode()));
|
||||||
|
if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode()))
|
||||||
|
this.evict(this.buildKey(event.getPreviousTenantCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<TenantByCodeCacheValue> valueClass() {
|
||||||
|
return TenantByCodeCacheValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String keyOf(TenantByCodeCacheValue value) {
|
||||||
|
return this.buildKey(value.getTenantCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildKey(String code) {
|
||||||
|
HashMap<String, String> keyParts = new HashMap<>();
|
||||||
|
keyParts.put("$code$", code);
|
||||||
|
return this.generateKey(keyParts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.tools.cache.CacheOptions;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cache.tenant-by-id")
|
||||||
|
public class TenantByIdCacheOptions extends CacheOptions {
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.event.TenantTouchedEvent;
|
||||||
|
import gr.cite.tools.cache.CacheService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TenantByIdCacheService extends CacheService<TenantByIdCacheService.TenantByIdCacheValue> {
|
||||||
|
|
||||||
|
public static class TenantByIdCacheValue {
|
||||||
|
|
||||||
|
public TenantByIdCacheValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public TenantByIdCacheValue(String tenantCode, UUID tenantId) {
|
||||||
|
this.tenantCode = tenantCode;
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tenantCode;
|
||||||
|
|
||||||
|
public String getTenantCode() {
|
||||||
|
return tenantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantCode(String tenantCode) {
|
||||||
|
this.tenantCode = tenantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID tenantId;
|
||||||
|
|
||||||
|
public UUID getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(UUID tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConventionService conventionService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) {
|
||||||
|
super(options);
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
|
||||||
|
if (!this.conventionService.isNullOrEmpty(event.getTenantCode()))
|
||||||
|
this.evict(this.buildKey(event.getTenantId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<TenantByIdCacheValue> valueClass() {
|
||||||
|
return TenantByIdCacheValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String keyOf(TenantByIdCacheValue value) {
|
||||||
|
return this.buildKey(value.getTenantId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildKey(UUID id) {
|
||||||
|
HashMap<String, String> keyParts = new HashMap<>();
|
||||||
|
keyParts.put("$tenantId$", id.toString().toLowerCase(Locale.ROOT));
|
||||||
|
return this.generateKey(keyParts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.ClaimNames;
|
||||||
|
import gr.cite.annotation.authorization.Permission;
|
||||||
|
import gr.cite.annotation.common.enums.IsActive;
|
||||||
|
import gr.cite.annotation.common.scope.tenant.TenantScope;
|
||||||
|
import gr.cite.annotation.common.scope.user.UserScope;
|
||||||
|
import gr.cite.annotation.data.TenantUserEntity;
|
||||||
|
import gr.cite.annotation.data.UserEntity;
|
||||||
|
import gr.cite.annotation.data.tenant.TenantScopedBaseEntity;
|
||||||
|
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
|
||||||
|
import gr.cite.annotation.query.utils.BuildSubQueryInput;
|
||||||
|
import gr.cite.annotation.query.utils.QueryUtilsService;
|
||||||
|
import gr.cite.commons.web.authz.service.AuthorizationService;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||||
|
import gr.cite.tools.exception.MyForbiddenException;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
|
import jakarta.persistence.criteria.Root;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.web.context.request.ServletWebRequest;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TenantInterceptor implements WebRequestInterceptor {
|
||||||
|
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantInterceptor.class));
|
||||||
|
private final TenantScope tenantScope;
|
||||||
|
private final UserScope userScope;
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
private final ClaimExtractor claimExtractor;
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
private final TenantScopeProperties tenantScopeProperties;
|
||||||
|
private final UserAllowedTenantCacheService userAllowedTenantCacheService;
|
||||||
|
private final ErrorThesaurusProperties errors;
|
||||||
|
private final QueryUtilsService queryUtilsService;
|
||||||
|
@PersistenceContext
|
||||||
|
public EntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantInterceptor(
|
||||||
|
TenantScope tenantScope,
|
||||||
|
UserScope userScope,
|
||||||
|
CurrentPrincipalResolver currentPrincipalResolver,
|
||||||
|
ClaimExtractor claimExtractor,
|
||||||
|
ApplicationContext applicationContext,
|
||||||
|
TenantScopeProperties tenantScopeProperties,
|
||||||
|
UserAllowedTenantCacheService userAllowedTenantCacheService,
|
||||||
|
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService) {
|
||||||
|
this.tenantScope = tenantScope;
|
||||||
|
this.userScope = userScope;
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
this.claimExtractor = claimExtractor;
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
this.tenantScopeProperties = tenantScopeProperties;
|
||||||
|
this.userAllowedTenantCacheService = userAllowedTenantCacheService;
|
||||||
|
this.errors = errors;
|
||||||
|
this.queryUtilsService = queryUtilsService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException, InterruptedException {
|
||||||
|
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
|
||||||
|
if (!this.tenantScope.isMultitenant()) return;
|
||||||
|
|
||||||
|
boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant);
|
||||||
|
if (tenantScope.isSet() && this.entityManager != null) {
|
||||||
|
List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), ClaimNames.TenantCodesClaimName);
|
||||||
|
if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) {
|
||||||
|
logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
|
||||||
|
throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isUserAllowedTenant = false;
|
||||||
|
if (this.tenantScope.isDefaultTenant()){
|
||||||
|
isUserAllowedTenant = true;
|
||||||
|
} else {
|
||||||
|
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
|
||||||
|
if (cacheValue != null) {
|
||||||
|
isUserAllowedTenant = cacheValue.isAllowed();
|
||||||
|
} else {
|
||||||
|
isUserAllowedTenant = this.isUserAllowedTenant();
|
||||||
|
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUserAllowedTenant) {
|
||||||
|
if(!tenantScope.isDefaultTenant()) {
|
||||||
|
this.entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
|
||||||
|
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, tenantScope.getTenant().toString());
|
||||||
|
} else {
|
||||||
|
this.entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) {
|
||||||
|
tenantScope.setTenant(null, null);
|
||||||
|
} else {
|
||||||
|
logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
|
||||||
|
throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isAllowedNoTenant) {
|
||||||
|
if (!this.isWhiteListedEndpoint(request)) {
|
||||||
|
logger.warn("tenant scope not provided");
|
||||||
|
throw new MyForbiddenException(this.errors.getMissingTenant().getCode(), this.errors.getMissingTenant().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWhiteListedEndpoint(WebRequest request) {
|
||||||
|
String servletPath = ((ServletWebRequest) request).getRequest().getServletPath();
|
||||||
|
if (this.tenantScopeProperties.getWhiteListedEndpoints() != null) {
|
||||||
|
for (String whiteListedEndpoint : this.tenantScopeProperties.getWhiteListedEndpoints()) {
|
||||||
|
if (servletPath.toLowerCase(Locale.ROOT).startsWith(whiteListedEndpoint.toLowerCase(Locale.ROOT))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUserAllowedTenant() throws InvalidApplicationException, InterruptedException {
|
||||||
|
if (userScope.isSet()) {
|
||||||
|
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<UserEntity> query = criteriaBuilder.createQuery(UserEntity.class);
|
||||||
|
Root<UserEntity> root = query.from(UserEntity.class);
|
||||||
|
query.where(criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active),
|
||||||
|
criteriaBuilder.in(root.get(UserEntity._id)).value(queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(TenantUserEntity.class, UUID.class)
|
||||||
|
.query(query)
|
||||||
|
.criteriaBuilder(criteriaBuilder)
|
||||||
|
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(TenantUserEntity._userId))
|
||||||
|
.filterFunc((subQueryRoot, cb) ->
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return cb.and(
|
||||||
|
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._tenantId), this.tenantScope.getTenant()),
|
||||||
|
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._userId), this.userScope.getUserId()),
|
||||||
|
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._isActive), IsActive.Active)
|
||||||
|
);
|
||||||
|
} catch (InvalidApplicationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
));
|
||||||
|
query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
|
||||||
|
List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
|
||||||
|
return !results.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postHandle(@NonNull WebRequest request, ModelMap model) {
|
||||||
|
this.tenantScope.setTenant(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.ClaimNames;
|
||||||
|
import gr.cite.annotation.common.enums.IsActive;
|
||||||
|
import gr.cite.annotation.common.scope.tenant.TenantScope;
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.data.TenantEntity;
|
||||||
|
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.MyPrincipal;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
|
||||||
|
import gr.cite.tools.exception.MyForbiddenException;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
|
import jakarta.persistence.criteria.Root;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class));
|
||||||
|
private final TenantScope tenantScope;
|
||||||
|
private final ConventionService conventionService;
|
||||||
|
private final TenantScopeProperties tenantScopeProperties;
|
||||||
|
private final ErrorThesaurusProperties errorThesaurusProperties;
|
||||||
|
private final ClaimExtractor claimExtractor;
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
private final String clientTenantClaimName;
|
||||||
|
private final ClaimExtractorContext claimExtractorContext;
|
||||||
|
private final TenantByCodeCacheService tenantByCodeCacheService;
|
||||||
|
private final TenantByIdCacheService tenantByIdCacheService;
|
||||||
|
@PersistenceContext
|
||||||
|
public EntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantScopeClaimInterceptor(
|
||||||
|
TenantScope tenantScope,
|
||||||
|
ConventionService conventionService,
|
||||||
|
ClaimExtractor claimExtractor,
|
||||||
|
CurrentPrincipalResolver currentPrincipalResolver,
|
||||||
|
ErrorThesaurusProperties errorThesaurusProperties,
|
||||||
|
TenantScopeProperties tenantScopeProperties,
|
||||||
|
ClaimExtractorContext claimExtractorContext,
|
||||||
|
TenantByCodeCacheService tenantByCodeCacheService,
|
||||||
|
TenantByIdCacheService tenantByIdCacheService
|
||||||
|
) {
|
||||||
|
this.tenantScope = tenantScope;
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
this.claimExtractor = claimExtractor;
|
||||||
|
this.errorThesaurusProperties = errorThesaurusProperties;
|
||||||
|
this.tenantScopeProperties = tenantScopeProperties;
|
||||||
|
this.claimExtractorContext = claimExtractorContext;
|
||||||
|
this.tenantByCodeCacheService = tenantByCodeCacheService;
|
||||||
|
this.tenantByIdCacheService = tenantByIdCacheService;
|
||||||
|
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + ClaimNames.TenantClaimName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
|
||||||
|
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
|
||||||
|
if (!this.tenantScope.isMultitenant()) return;
|
||||||
|
|
||||||
|
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
|
||||||
|
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
|
||||||
|
boolean scoped = this.scopeByPrincipal(principal);
|
||||||
|
if (!scoped) scoped = this.scopeByClient(principal);
|
||||||
|
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant())
|
||||||
|
throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scopeByPrincipal(MyPrincipal principal) {
|
||||||
|
String tenantCode = this.claimExtractor.tenantString(principal);
|
||||||
|
if (this.conventionService.isNullOrEmpty(tenantCode)) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
|
||||||
|
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return false;
|
||||||
|
|
||||||
|
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
|
||||||
|
logger.debug("parsed tenant header and set tenant to default tenant");
|
||||||
|
this.tenantScope.setTenant(null, tenantCode);
|
||||||
|
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
|
||||||
|
if (tenantId == null) {
|
||||||
|
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
|
||||||
|
if (cacheValue != null) {
|
||||||
|
tenantId = cacheValue.getTenantId();
|
||||||
|
} else {
|
||||||
|
tenantId = this.getTenantIdFromDatabase(tenantCode);
|
||||||
|
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
|
||||||
|
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("tenant claim was set to {}", tenantId);
|
||||||
|
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId));
|
||||||
|
|
||||||
|
if (cacheValue != null) {
|
||||||
|
tenantCode = cacheValue.getTenantCode();
|
||||||
|
} else {
|
||||||
|
tenantCode = this.getTenantCodeFromDatabase(tenantId);
|
||||||
|
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
|
||||||
|
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantId != null) {
|
||||||
|
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
|
||||||
|
this.tenantScope.setTenant(tenantId, tenantCode);
|
||||||
|
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scopeByClient(MyPrincipal principal) throws InvalidApplicationException {
|
||||||
|
String client = this.claimExtractor.client(principal);
|
||||||
|
|
||||||
|
Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client);
|
||||||
|
logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, this.tenantScope.isSet(), (this.tenantScope.isSet() ? this.tenantScope.getTenant() : null));
|
||||||
|
|
||||||
|
return isWhiteListed && this.tenantScope.isSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID getTenantIdFromDatabase(String tenantCode) {
|
||||||
|
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
|
||||||
|
Root<TenantEntity> root = query.from(TenantEntity.class);
|
||||||
|
query = query.where(
|
||||||
|
criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
|
||||||
|
)
|
||||||
|
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
|
||||||
|
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
|
||||||
|
if (results.size() == 1) {
|
||||||
|
return results.getFirst().getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTenantCodeFromDatabase(UUID tenantId) {
|
||||||
|
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
|
||||||
|
Root<TenantEntity> root = query.from(TenantEntity.class);
|
||||||
|
query = query.where(
|
||||||
|
criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
|
||||||
|
)
|
||||||
|
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
|
||||||
|
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
|
||||||
|
if (results.size() == 1) {
|
||||||
|
return results.getFirst().getCode();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postHandle(@NonNull WebRequest request, ModelMap model) {
|
||||||
|
this.tenantScope.setTenant(null, null);
|
||||||
|
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(TenantScopeProperties.class)
|
||||||
|
public class TenantScopeConfiguration {
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.ClaimNames;
|
||||||
|
import gr.cite.annotation.common.enums.IsActive;
|
||||||
|
import gr.cite.annotation.common.scope.tenant.TenantScope;
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.data.TenantEntity;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
|
import jakarta.persistence.criteria.Root;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeHeaderInterceptor.class));
|
||||||
|
private final TenantScope tenantScope;
|
||||||
|
private final ConventionService conventionService;
|
||||||
|
private final TenantByCodeCacheService tenantByCodeCacheService;
|
||||||
|
private final TenantByIdCacheService tenantByIdCacheService;
|
||||||
|
private final ClaimExtractorContext claimExtractorContext;
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
@PersistenceContext
|
||||||
|
public EntityManager entityManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantScopeHeaderInterceptor(
|
||||||
|
TenantScope tenantScope,
|
||||||
|
ConventionService conventionService,
|
||||||
|
TenantByCodeCacheService tenantByCodeCacheService,
|
||||||
|
TenantByIdCacheService tenantByIdCacheService,
|
||||||
|
ClaimExtractorContext claimExtractorContext,
|
||||||
|
CurrentPrincipalResolver currentPrincipalResolver
|
||||||
|
) {
|
||||||
|
this.tenantScope = tenantScope;
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
this.tenantByCodeCacheService = tenantByCodeCacheService;
|
||||||
|
this.tenantByIdCacheService = tenantByIdCacheService;
|
||||||
|
this.claimExtractorContext = claimExtractorContext;
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preHandle(@NotNull WebRequest request) {
|
||||||
|
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
|
||||||
|
if (!this.tenantScope.isMultitenant()) return;
|
||||||
|
|
||||||
|
String tenantCode = request.getHeader(ClaimNames.TenantClaimName);
|
||||||
|
logger.debug("retrieved request tenant header is: {}", tenantCode);
|
||||||
|
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return;
|
||||||
|
|
||||||
|
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
|
||||||
|
logger.debug("parsed tenant header and set tenant to default tenant");
|
||||||
|
this.tenantScope.setTenant(null, tenantCode);
|
||||||
|
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
|
||||||
|
if (tenantId == null) {
|
||||||
|
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
|
||||||
|
if (cacheValue != null) {
|
||||||
|
tenantId = cacheValue.getTenantId();
|
||||||
|
} else {
|
||||||
|
tenantId = this.getTenantIdFromDatabase(tenantCode);
|
||||||
|
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
|
||||||
|
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId));
|
||||||
|
if (cacheValue != null) {
|
||||||
|
tenantCode = cacheValue.getTenantCode();
|
||||||
|
} else {
|
||||||
|
tenantCode = this.getTenantCodeFromDatabase(tenantId);
|
||||||
|
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
|
||||||
|
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantId != null) {
|
||||||
|
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
|
||||||
|
this.tenantScope.setTenant(tenantId, tenantCode);
|
||||||
|
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID getTenantIdFromDatabase(String tenantCode) {
|
||||||
|
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
|
||||||
|
Root<TenantEntity> root = query.from(TenantEntity.class);
|
||||||
|
query = query.where(
|
||||||
|
criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
|
||||||
|
)
|
||||||
|
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
|
||||||
|
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
|
||||||
|
if (results.size() == 1) {
|
||||||
|
return results.getFirst().getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTenantCodeFromDatabase(UUID tenantId) {
|
||||||
|
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.class);
|
||||||
|
Root<TenantEntity> root = query.from(TenantEntity.class);
|
||||||
|
query = query.where(
|
||||||
|
criteriaBuilder.and(
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
|
||||||
|
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
|
||||||
|
)
|
||||||
|
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
|
||||||
|
List<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
|
||||||
|
if (results.size() == 1) {
|
||||||
|
return results.getFirst().getCode();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postHandle(@NonNull WebRequest request, ModelMap model) {
|
||||||
|
|
||||||
|
this.tenantScope.setTenant(null, null);
|
||||||
|
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "tenant.interceptor")
|
||||||
|
public class TenantScopeProperties {
|
||||||
|
|
||||||
|
private String clientClaimsPrefix;
|
||||||
|
public String getClientClaimsPrefix() {
|
||||||
|
return clientClaimsPrefix;
|
||||||
|
}
|
||||||
|
public void setClientClaimsPrefix(String clientClaimsPrefix) {
|
||||||
|
this.clientClaimsPrefix = clientClaimsPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashSet<String> whiteListedClients;
|
||||||
|
public HashSet<String> getWhiteListedClients() {
|
||||||
|
return whiteListedClients;
|
||||||
|
}
|
||||||
|
public void setWhiteListedClients(HashSet<String> whiteListedClients) {
|
||||||
|
this.whiteListedClients = whiteListedClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> whiteListedEndpoints;
|
||||||
|
public List<String> getWhiteListedEndpoints() {
|
||||||
|
return whiteListedEndpoints;
|
||||||
|
}
|
||||||
|
public void setWhiteListedEndpoints(List<String> whiteListedEndpoints) {
|
||||||
|
this.whiteListedEndpoints = whiteListedEndpoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean enforceTrustedTenant;
|
||||||
|
public Boolean getEnforceTrustedTenant() {
|
||||||
|
return enforceTrustedTenant;
|
||||||
|
}
|
||||||
|
public void setEnforceTrustedTenant(Boolean enforceTrustedTenant) {
|
||||||
|
this.enforceTrustedTenant = enforceTrustedTenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.tools.cache.CacheOptions;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cache.user-allowed-tenant")
|
||||||
|
public class UserAllowedTenantCacheOptions extends CacheOptions {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package gr.cite.annotation.web.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.annotation.event.UserAddedToTenantEvent;
|
||||||
|
import gr.cite.annotation.event.UserRemovedFromTenantEvent;
|
||||||
|
import gr.cite.tools.cache.CacheService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserAllowedTenantCacheService extends CacheService<UserAllowedTenantCacheService.UserAllowedTenantCacheValue> {
|
||||||
|
|
||||||
|
public static class UserAllowedTenantCacheValue {
|
||||||
|
|
||||||
|
public UserAllowedTenantCacheValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
this.isAllowed = isAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID userId;
|
||||||
|
|
||||||
|
public UUID getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(UUID userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID tenantId;
|
||||||
|
|
||||||
|
public UUID getTenantId() {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenantId(UUID tenantId) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllowed;
|
||||||
|
|
||||||
|
public boolean isAllowed() {
|
||||||
|
return isAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowed(boolean allowed) {
|
||||||
|
isAllowed = allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) {
|
||||||
|
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) {
|
||||||
|
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<UserAllowedTenantCacheValue> valueClass() {
|
||||||
|
return UserAllowedTenantCacheValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String keyOf(UserAllowedTenantCacheValue value) {
|
||||||
|
return this.buildKey(value.getUserId(), value.getTenantId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildKey(UUID userId, UUID tenantId) {
|
||||||
|
HashMap<String, String> keyParts = new HashMap<>();
|
||||||
|
keyParts.put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
|
||||||
|
keyParts.put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT));
|
||||||
|
return this.generateKey(keyParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package gr.cite.annotation.web.scope.user;
|
||||||
|
|
||||||
|
|
||||||
|
import gr.cite.annotation.common.scope.user.UserScope;
|
||||||
|
import gr.cite.annotation.data.UserCredentialEntity;
|
||||||
|
import gr.cite.annotation.model.UserCredential;
|
||||||
|
import gr.cite.annotation.query.UserCredentialQuery;
|
||||||
|
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
|
||||||
|
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
|
||||||
|
import gr.cite.tools.data.query.QueryFactory;
|
||||||
|
import gr.cite.tools.exception.MyForbiddenException;
|
||||||
|
import gr.cite.tools.fieldset.BaseFieldSet;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.context.request.WebRequestInterceptor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserInterceptor implements WebRequestInterceptor {
|
||||||
|
private final UserScope userScope;
|
||||||
|
private final ClaimExtractor claimExtractor;
|
||||||
|
private final CurrentPrincipalResolver currentPrincipalResolver;
|
||||||
|
private final UserInterceptorCacheService userInterceptorCacheService;
|
||||||
|
private final QueryFactory queryFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UserInterceptor(
|
||||||
|
UserScope userScope,
|
||||||
|
ClaimExtractor claimExtractor,
|
||||||
|
CurrentPrincipalResolver currentPrincipalResolver,
|
||||||
|
UserInterceptorCacheService userInterceptorCacheService,
|
||||||
|
QueryFactory queryFactory) {
|
||||||
|
this.userScope = userScope;
|
||||||
|
this.currentPrincipalResolver = currentPrincipalResolver;
|
||||||
|
this.claimExtractor = claimExtractor;
|
||||||
|
this.userInterceptorCacheService = userInterceptorCacheService;
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preHandle(@NotNull WebRequest request) {
|
||||||
|
UUID userId = null;
|
||||||
|
if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
|
||||||
|
String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
|
||||||
|
if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
|
||||||
|
|
||||||
|
UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
|
||||||
|
if (cacheValue != null) {
|
||||||
|
userId = cacheValue.getUserId();
|
||||||
|
} else {
|
||||||
|
userId = this.findExistingUserFromDb(subjectId);
|
||||||
|
if (userId != null) {
|
||||||
|
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
|
||||||
|
this.userInterceptorCacheService.put(cacheValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.userScope.setUserId(userId);
|
||||||
|
}
|
||||||
|
private UUID findExistingUserFromDb(String subjectId) {
|
||||||
|
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
|
||||||
|
if (userCredential != null) {
|
||||||
|
return userCredential.getUserId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void postHandle(@NonNull WebRequest request, ModelMap model) {
|
||||||
|
this.userScope.setUserId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package gr.cite.annotation.web.scope.user;
|
||||||
|
|
||||||
|
import gr.cite.tools.cache.CacheOptions;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cache.user-by-subject-id")
|
||||||
|
public class UserInterceptorCacheOptions extends CacheOptions {
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package gr.cite.annotation.web.scope.user;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.tools.cache.CacheService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
|
||||||
|
|
||||||
|
public static class UserInterceptorCacheValue {
|
||||||
|
|
||||||
|
public UserInterceptorCacheValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserInterceptorCacheValue(String subjectId, UUID userId) {
|
||||||
|
this.subjectId = subjectId;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getSubjectId() {
|
||||||
|
return subjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSubjectId(String subjectId) {
|
||||||
|
this.subjectId = subjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String subjectId;
|
||||||
|
private UUID userId;
|
||||||
|
|
||||||
|
public UUID getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(UUID userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<UserInterceptorCacheValue> valueClass() {
|
||||||
|
return UserInterceptorCacheValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String keyOf(UserInterceptorCacheValue value) {
|
||||||
|
return this.buildKey(value.getSubjectId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String buildKey(String subject) {
|
||||||
|
HashMap<String, String> keyParts = new HashMap<>();
|
||||||
|
keyParts.put("$subject$", subject);
|
||||||
|
return this.generateKey(keyParts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
spring:
|
||||||
|
jackson:
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
config:
|
||||||
|
import: optional:classpath:config/app.env[.properties], optional:file:../config/app.env[.properties],
|
||||||
|
optional:classpath:config/db.yml[.yml], optional:classpath:config/db-${spring.profiles.active}.yml[.yml], optional:file:../config/db-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/permissions.yml[.yml], optional:classpath:config/permissions-${spring.profiles.active}.yml[.yml], optional:file:../config/permissions-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/errors.yml[.yml], optional:classpath:config/errors-${spring.profiles.active}.yml[.yml], optional:file:../config/errors-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/logging.yml[.yml], optional:classpath:config/logging-${spring.profiles.active}.yml[.yml], optional:file:../config/logging-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/idpclaims.yml[.yml], optional:classpath:config/idpclaims-${spring.profiles.active}.yml[.yml], optional:file:../config/idpclaims-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/cache.yml[.yml], optional:classpath:config/cache-${spring.profiles.active}.yml[.yml], optional:file:../config/cache-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/tenant.yml[.yml], optional:classpath:config/tenant-${spring.profiles.active}.yml[.yml], optional:file:../config/tenant-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/locale.yml[.yml], optional:classpath:config/locale-${spring.profiles.active}.yml[.yml], optional:file:../config/locale-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/cors.yml[.yml], optional:classpath:config/cors-${spring.profiles.active}.yml[.yml], optional:file:../config/cors-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/queue.yml[.yml], optional:classpath:config/queue-${spring.profiles.active}.yml[.yml], optional:file:../config/queue-${spring.profiles.active}.yml[.yml],
|
||||||
|
optional:classpath:config/cipher.yml[.yml], optional:classpath:config/cipher-${spring.profiles.active}.yml[.yml], optional:file:../config/cipher-${spring.profiles.active}.yml[.yml]
|
|
@ -0,0 +1,59 @@
|
||||||
|
cache:
|
||||||
|
manager:
|
||||||
|
fallbackToNoOpCache: true
|
||||||
|
caffeineCaches:
|
||||||
|
- names: [ apikey ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 500
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 600
|
||||||
|
- names: [ tenantByCode ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 500
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 600
|
||||||
|
- names: [ tenantById ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 500
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 600
|
||||||
|
- names: [ userBySubjectId ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 500
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 320
|
||||||
|
- names: [ userAccessTenant ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 500
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 320
|
||||||
|
- names: [ "affiliation" ]
|
||||||
|
allowNullValues: true
|
||||||
|
initialCapacity: 100
|
||||||
|
maximumSize: 5000
|
||||||
|
enableRecordStats: false
|
||||||
|
expireAfterWriteSeconds: 20
|
||||||
|
mapCaches:
|
||||||
|
apiKey:
|
||||||
|
name: apikey
|
||||||
|
keyPattern: ant_resolve_$keyhash$:v0
|
||||||
|
userBySubjectId:
|
||||||
|
name: userBySubjectId
|
||||||
|
keyPattern: ant_user_by_subject_$subject$:v0
|
||||||
|
tenantByCode:
|
||||||
|
name: tenantByCode
|
||||||
|
keyPattern: ant_tenant_by_code_$code$:v0
|
||||||
|
tenantById:
|
||||||
|
name: tenantById
|
||||||
|
keyPattern: ant_tenant_by_id_$tenantId$:v0
|
||||||
|
userAllowedTenant:
|
||||||
|
name: userAccessTenant
|
||||||
|
keyPattern: ant_user_access_tenant_$user_id$_$tenant_id$:v0
|
||||||
|
affiliation:
|
||||||
|
name: affiliation
|
||||||
|
keyPattern: ant_affiliation_$entity$_$user$_$type$:v0
|
|
@ -0,0 +1,35 @@
|
||||||
|
cipher-profiles:
|
||||||
|
profile-map:
|
||||||
|
configuration-profile-name: "configuration"
|
||||||
|
queue-profile-name: "queue"
|
||||||
|
notification-profile-name: "queue"
|
||||||
|
|
||||||
|
cipher:
|
||||||
|
# salted-hash:
|
||||||
|
# default-o: null
|
||||||
|
# options: null
|
||||||
|
symetric-encryption:
|
||||||
|
default-o: null
|
||||||
|
options:
|
||||||
|
configuration:
|
||||||
|
aes:
|
||||||
|
key: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_KEY:}
|
||||||
|
iv: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_IV:}
|
||||||
|
queue:
|
||||||
|
aes:
|
||||||
|
key: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_KEY:}
|
||||||
|
iv: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_IV:}
|
||||||
|
masking:
|
||||||
|
default: null
|
||||||
|
options:
|
||||||
|
configuration:
|
||||||
|
character: "*"
|
||||||
|
clear-begining: 2
|
||||||
|
clear-ending: 4
|
||||||
|
at-least-percentage: 70
|
||||||
|
digital-signature:
|
||||||
|
default: null
|
||||||
|
options:
|
||||||
|
configuration:
|
||||||
|
certificate-path: null
|
||||||
|
certificate-password: null
|
|
@ -0,0 +1,3 @@
|
||||||
|
web:
|
||||||
|
cors:
|
||||||
|
allowed-origins: [ http://localhost, http://localhost:4200 ]
|
|
@ -0,0 +1,7 @@
|
||||||
|
web:
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed-methods: [ HEAD, GET, POST, PUT, DELETE, PATCH ]
|
||||||
|
allowed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition, x-tenant ]
|
||||||
|
exposed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition ]
|
||||||
|
allow-credentials: false
|
|
@ -0,0 +1,10 @@
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
maxIdle: 10
|
||||||
|
minIdle: 5
|
||||||
|
maxActive: 10
|
||||||
|
jpa:
|
||||||
|
show-sql: true
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
format_sql: false
|
|
@ -0,0 +1,28 @@
|
||||||
|
spring:
|
||||||
|
jpa:
|
||||||
|
properties:
|
||||||
|
org:
|
||||||
|
hibernate:
|
||||||
|
flushMode: MANUAL
|
||||||
|
hibernate:
|
||||||
|
globally_quoted_identifiers: true
|
||||||
|
ddl-auto: validate
|
||||||
|
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||||
|
hibernate:
|
||||||
|
naming:
|
||||||
|
physical-strategy: gr.cite.annotation.data.namingstrategy.PrefixPhysicalNamingStrategy
|
||||||
|
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
|
||||||
|
datasource:
|
||||||
|
url: ${DB_CONNECTION_STRING}
|
||||||
|
username: ${DB_USER}
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
hikari:
|
||||||
|
connection-timeout: 30000
|
||||||
|
minimum-idle: 3
|
||||||
|
maximum-pool-size: 10
|
||||||
|
idle-timeout: 600000
|
||||||
|
max-lifetime: 1800000
|
||||||
|
|
||||||
|
naming-strategy:
|
||||||
|
prefix: ant_
|
|
@ -0,0 +1,49 @@
|
||||||
|
error-thesaurus:
|
||||||
|
hash-conflict:
|
||||||
|
code: 100
|
||||||
|
message: there is a hash conflict for the item modified. please reload to get the latest changes
|
||||||
|
forbidden:
|
||||||
|
code: 101
|
||||||
|
message: insufficient rights
|
||||||
|
system-error:
|
||||||
|
code: 102
|
||||||
|
message: an unexpected system error occurred
|
||||||
|
missing-tenant:
|
||||||
|
code: 103
|
||||||
|
message: tenant scope not provided
|
||||||
|
invalid-api-key:
|
||||||
|
code: 104
|
||||||
|
message: provided APIKey not valid
|
||||||
|
stale-api-key:
|
||||||
|
code: 105
|
||||||
|
message: there was a problem authorizing you with your API key. Please try again. Contact the system administrator if the problem persists
|
||||||
|
model-validation:
|
||||||
|
code: 106
|
||||||
|
message: validation error
|
||||||
|
sensitive-info:
|
||||||
|
code: 107
|
||||||
|
message: you are attempting to access sensitive information. please don't do that
|
||||||
|
non-person-principal:
|
||||||
|
code: 108
|
||||||
|
message: the operation is available only to person users
|
||||||
|
blocking-consent:
|
||||||
|
code: 113
|
||||||
|
message: user consents are not sufficient to complete the operation
|
||||||
|
single-tenant-configuration-per-type-supported:
|
||||||
|
code: 116
|
||||||
|
message: a single tenant configuration entry per config type is supported
|
||||||
|
incompatible-tenant-configuration-types:
|
||||||
|
code: 117
|
||||||
|
message: the provided tenant configuration type is incompatible
|
||||||
|
missing-totp-token:
|
||||||
|
code: 118
|
||||||
|
message: totp token not provided
|
||||||
|
overlapping-tenant-configuration-notifier-list:
|
||||||
|
code: 119
|
||||||
|
message: Overlapping Tenant Configuration Notifier List
|
||||||
|
tenant-not-allowed:
|
||||||
|
code: 113
|
||||||
|
message: tenant not allowed
|
||||||
|
tenant-tampering:
|
||||||
|
code: 123
|
||||||
|
message: Tenant tampering
|
|
@ -0,0 +1,58 @@
|
||||||
|
idpclient:
|
||||||
|
claims:
|
||||||
|
mapping:
|
||||||
|
Subject:
|
||||||
|
- type: sub
|
||||||
|
Name:
|
||||||
|
- type: name
|
||||||
|
Client:
|
||||||
|
- type: client_id
|
||||||
|
AuthenticationMethod:
|
||||||
|
- type: amr
|
||||||
|
NotBefore:
|
||||||
|
- type: nbf
|
||||||
|
AuthenticatedAt:
|
||||||
|
- type: auth_time
|
||||||
|
ExpiresAt:
|
||||||
|
- type: exp
|
||||||
|
Email:
|
||||||
|
- type: email
|
||||||
|
Roles:
|
||||||
|
- type: resource_access
|
||||||
|
path: dmp_web.roles
|
||||||
|
- type: tenant_roles
|
||||||
|
filterBy: "(.*):::TenantCode::"
|
||||||
|
extractByExpression: "(.*):(.*)"
|
||||||
|
extractExpressionValue: "[[g1]]"
|
||||||
|
GlobalRoles:
|
||||||
|
- type: resource_access
|
||||||
|
path: dmp_web.roles
|
||||||
|
TenantRoles:
|
||||||
|
- type: tenant_roles
|
||||||
|
filterBy: "(.*):::TenantCode::"
|
||||||
|
extractByExpression: "(.*):(.*)"
|
||||||
|
extractExpressionValue: "[[g1]]"
|
||||||
|
Scope:
|
||||||
|
- type: scope
|
||||||
|
AccessToken:
|
||||||
|
- type: x-access-token
|
||||||
|
visibility: SENSITIVE
|
||||||
|
Tenant:
|
||||||
|
- type: x-tenant
|
||||||
|
IssuedAt:
|
||||||
|
- type: iat
|
||||||
|
Issuer:
|
||||||
|
- type: iss
|
||||||
|
Audience:
|
||||||
|
- type: aud
|
||||||
|
TokenType:
|
||||||
|
- type: typ
|
||||||
|
AuthorizedParty:
|
||||||
|
- type: azp
|
||||||
|
Authorities:
|
||||||
|
- type: authorities
|
||||||
|
TenantCodes:
|
||||||
|
- type: tenant_roles
|
||||||
|
filterBy: "(.*):(.*)"
|
||||||
|
extractByExpression: "(.*):(.*)"
|
||||||
|
extractExpressionValue: "[[g2]]"
|
|
@ -0,0 +1,4 @@
|
||||||
|
locale:
|
||||||
|
timezone: UTC
|
||||||
|
language: en
|
||||||
|
culture: en-US
|
|
@ -0,0 +1,2 @@
|
||||||
|
logging:
|
||||||
|
config: classpath:logging/logback-dev.xml
|
|
@ -0,0 +1,35 @@
|
||||||
|
logging:
|
||||||
|
context:
|
||||||
|
request:
|
||||||
|
requestIdKey: req.id
|
||||||
|
requestRemoteHostKey: req.remoteHost
|
||||||
|
requestUriKey: req.requestURI
|
||||||
|
requestQueryStringKey: req.queryString
|
||||||
|
requestUrlKey : req.requestURL
|
||||||
|
requestMethodKey: req.method
|
||||||
|
requestUserAgentKey: req.userAgent
|
||||||
|
requestForwardedForKey: req.xForwardedFor
|
||||||
|
requestSchemeKey: req.scheme
|
||||||
|
requestRemoteAddressKey: req.remoteAddr
|
||||||
|
requestRemotePortKey: req.remotePort
|
||||||
|
requestRemoteUserKey: req.remoteUser
|
||||||
|
principal:
|
||||||
|
subjectKey: usr.subject
|
||||||
|
nameKey: usr.name
|
||||||
|
clientKey: usr.client
|
||||||
|
audit:
|
||||||
|
enable: true
|
||||||
|
requestRemoteHostKey: req.remoteHost
|
||||||
|
requestUriKey: req.requestURI
|
||||||
|
requestQueryStringKey: req.queryString
|
||||||
|
requestUrlKey : req.requestURL
|
||||||
|
requestMethodKey: req.method
|
||||||
|
requestUserAgentKey: req.userAgent
|
||||||
|
requestForwardedForKey: req.xForwardedFor
|
||||||
|
requestSchemeKey: req.scheme
|
||||||
|
requestRemoteAddressKey: req.remoteAddr
|
||||||
|
requestRemotePortKey: req.remotePort
|
||||||
|
requestRemoteUserKey: req.remoteUser
|
||||||
|
principalSubjectKey: usr.subject
|
||||||
|
principalNameKey: usr.name
|
||||||
|
principalClientKey: usr.client
|
|
@ -0,0 +1,99 @@
|
||||||
|
permissions:
|
||||||
|
policies:
|
||||||
|
DeferredAffiliation:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
- TenantUser
|
||||||
|
- TenantManager
|
||||||
|
- TenantDescriptionTemplateEditor
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
# Tenants
|
||||||
|
BrowseTenant:
|
||||||
|
roles:
|
||||||
|
- Admin
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
EditTenant:
|
||||||
|
roles:
|
||||||
|
- Admin
|
||||||
|
clients: [ "opendmp-api-dev" ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
DeleteTenant:
|
||||||
|
roles:
|
||||||
|
- Admin
|
||||||
|
claims: [ ]
|
||||||
|
clients: [ "opendmp-api-dev" ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
AllowNoTenant:
|
||||||
|
roles:
|
||||||
|
- Admin
|
||||||
|
claims: [ ]
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
# Users
|
||||||
|
BrowseUser:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: true
|
||||||
|
allowAuthenticated: false
|
||||||
|
EditUser:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
clients: [ "opendmp-api-dev" ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
DeleteUser:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
claims: [ ]
|
||||||
|
clients: [ "opendmp-api-dev" ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
#Annotation
|
||||||
|
BrowseAnnotation:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
entityAffiliated: true
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: true
|
||||||
|
allowAuthenticated: false
|
||||||
|
NewAnnotation:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
entityAffiliated: true
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: true
|
||||||
|
allowAuthenticated: false
|
||||||
|
EditAnnotation:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: true
|
||||||
|
allowAuthenticated: false
|
||||||
|
DeleteAnnotation:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
entityAffiliated: false
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
#Tenant Configuration
|
||||||
|
BrowseTenantConfiguration:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
||||||
|
EditTenantConfiguration:
|
||||||
|
roles:
|
||||||
|
- TenantAdmin
|
||||||
|
clients: [ ]
|
||||||
|
allowAnonymous: false
|
||||||
|
allowAuthenticated: false
|
|
@ -0,0 +1,21 @@
|
||||||
|
queue:
|
||||||
|
rabbitmq:
|
||||||
|
enable: true
|
||||||
|
durable: true
|
||||||
|
queue: cite_dmp_devel_annotation_inbox_queue
|
||||||
|
exchange: cite_dmp_devel_queue
|
||||||
|
listenerEnabled: true
|
||||||
|
publisherEnabled: true
|
||||||
|
task:
|
||||||
|
publisher:
|
||||||
|
enable: true
|
||||||
|
options:
|
||||||
|
exchange: cite_dmp_devel_queue
|
||||||
|
rabbitmq:
|
||||||
|
enable: true
|
||||||
|
listener:
|
||||||
|
enable: true
|
||||||
|
options:
|
||||||
|
exchange: cite_dmp_devel_queue
|
||||||
|
rabbitmq:
|
||||||
|
enable: true
|
|
@ -0,0 +1,54 @@
|
||||||
|
spring:
|
||||||
|
rabbitmq:
|
||||||
|
host: ${RABBIT_HOST}
|
||||||
|
port: ${RABBIT_PORT}
|
||||||
|
username: ${RABBIT_USER}
|
||||||
|
password: ${RABBIT_PASS}
|
||||||
|
ssl:
|
||||||
|
enabled: false
|
||||||
|
queue:
|
||||||
|
rabbitmq:
|
||||||
|
enable: false
|
||||||
|
appId: ${QUEUE_APP_ID}
|
||||||
|
durable: null
|
||||||
|
queue: null
|
||||||
|
exchange: null
|
||||||
|
listenerEnabled: true
|
||||||
|
publisherEnabled: true
|
||||||
|
#TODO
|
||||||
|
connection-recovery:
|
||||||
|
enable: true
|
||||||
|
network-recovery-interval: 5000
|
||||||
|
unreachable-recovery-interval: 5000
|
||||||
|
task:
|
||||||
|
publisher:
|
||||||
|
enable: false
|
||||||
|
options:
|
||||||
|
exchange: null
|
||||||
|
rabbitmq:
|
||||||
|
enable: false
|
||||||
|
interval-seconds: 30
|
||||||
|
options:
|
||||||
|
retry-threashold: 100
|
||||||
|
retry-delay-step-seconds: 300
|
||||||
|
max-retry-delay-seconds: 10800
|
||||||
|
too-old-to-send-seconds: 604800
|
||||||
|
confirm-timeout-seconds: 30
|
||||||
|
listener:
|
||||||
|
enable: false
|
||||||
|
options:
|
||||||
|
exchange: null
|
||||||
|
tenant-removal-topic: tenant.remove
|
||||||
|
tenant-touch-topic: tenant.touch
|
||||||
|
user-removal-topic: user.remove
|
||||||
|
user-touch-topic: user.touch
|
||||||
|
annotation-entities-touch-topic: annotation.entities.touch
|
||||||
|
annotation-entities-removal-topic: annotation.entities.remove
|
||||||
|
rabbitmq:
|
||||||
|
enable: false
|
||||||
|
interval-seconds: 30
|
||||||
|
options:
|
||||||
|
retry-threashold: 100
|
||||||
|
retry-delay-step-seconds: 300
|
||||||
|
max-retry-delay-seconds: 10800
|
||||||
|
too-old-to-send-seconds: 604800
|
|
@ -0,0 +1,6 @@
|
||||||
|
web:
|
||||||
|
security:
|
||||||
|
idp:
|
||||||
|
resource:
|
||||||
|
jwt:
|
||||||
|
audiences: [ "dmp_annotation" ]
|
|
@ -0,0 +1,14 @@
|
||||||
|
web:
|
||||||
|
security:
|
||||||
|
enabled: true
|
||||||
|
authorized-endpoints: [ api ]
|
||||||
|
allowed-endpoints: [ public ]
|
||||||
|
idp:
|
||||||
|
api-key:
|
||||||
|
enabled: false
|
||||||
|
resource:
|
||||||
|
token-type: JWT #| opaque
|
||||||
|
jwt:
|
||||||
|
claims: [ role, x-role ]
|
||||||
|
issuer-uri: ${IDP_ISSUER_URI}
|
||||||
|
validIssuer: ${IDP_ISSUER_URI}
|
|
@ -0,0 +1,2 @@
|
||||||
|
server:
|
||||||
|
forward-headers-strategy: FRAMEWORK
|
|
@ -0,0 +1,13 @@
|
||||||
|
server:
|
||||||
|
port: ${WEB_PORT}
|
||||||
|
forward-headers-strategy: NONE
|
||||||
|
tomcat:
|
||||||
|
threads:
|
||||||
|
max: 20
|
||||||
|
max-connections: 10000
|
||||||
|
|
||||||
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB
|
||||||
|
max-request-size: 10MB
|
|
@ -0,0 +1,7 @@
|
||||||
|
tenant:
|
||||||
|
multitenancy:
|
||||||
|
is-multitenant: true
|
||||||
|
default-tenant-code: default
|
||||||
|
interceptor:
|
||||||
|
client-claims-prefix: client_
|
||||||
|
enforce-trusted-tenant: false
|
|
@ -0,0 +1,7 @@
|
||||||
|
tenant:
|
||||||
|
multitenancy:
|
||||||
|
is-multitenant: false
|
||||||
|
interceptor:
|
||||||
|
white-listed-clients: [ ]
|
||||||
|
enforce-trusted-tenant: false
|
||||||
|
white-listed-endpoints: [ '/api/annotation/principal/me' ]
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration debug="true">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<Pattern>%date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n</Pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="TROUBLESHOOTING" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/logging.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>logs/logging.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<maxFileSize>100MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>15</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<Pattern>%date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n</Pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="AUDITING" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>logs/auditing.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>logs/auditing.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
<maxFileSize>100MB</maxFileSize>
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
<maxHistory>15</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<Pattern>%date{ISO8601} - %X{req.id} - %message%n</Pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="org.springframework.web" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="TROUBLESHOOTING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.hibernate" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="TROUBLESHOOTING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="gr.cite" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="TROUBLESHOOTING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="TRACE" additivity="false">
|
||||||
|
<appender-ref ref="TROUBLESHOOTING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="audit" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="AUDITING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="TROUBLESHOOTING"/>
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
|
@ -0,0 +1,10 @@
|
||||||
|
validation.empty=Value cannot be empty
|
||||||
|
validation.hashempty=Hash must be set
|
||||||
|
validation.lowerthanmin=Value must be larger than {value}
|
||||||
|
validation.largerthanmax=Value must be less than {value}
|
||||||
|
validation.invalidid=Not valid id
|
||||||
|
General_ItemNotFound=Item {0} of type {1} not found
|
||||||
|
Validation_Required={0} is required
|
||||||
|
Validation_OverPosting=Too much info
|
||||||
|
Validation_MaxLength={0} too long
|
||||||
|
Validation_UnexpectedValue=Unexpected value in field {0}
|
|
@ -0,0 +1,6 @@
|
||||||
|
validation.empty=el-Value cannot be empty
|
||||||
|
validation.hashempty=el-Hash must be set
|
||||||
|
validation.lowerthanmin=el-Value must be larger than {value}
|
||||||
|
validation.largerthanmax=el-Value must be less than {value}
|
||||||
|
validation.invalidid=el-Not valid id
|
||||||
|
General_ItemNotFound=el-Item {0} of type {1} not found
|
|
@ -0,0 +1,33 @@
|
||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>annotation-service-parent</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>annotation</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<revision>1.0.0-SNAPSHOT</revision>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>5.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>5.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>data-tools</artifactId>
|
||||||
|
<version>2.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>field-set</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>oidc-authn</artifactId>
|
||||||
|
<version>2.2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>logging</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>oidc-authz</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>exceptions</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>validation</artifactId>
|
||||||
|
<version>3.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>gr.cite</groupId>
|
||||||
|
<artifactId>cipher</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,59 @@
|
||||||
|
package gr.cite.annotation.audit;
|
||||||
|
|
||||||
|
import gr.cite.tools.logging.EventId;
|
||||||
|
|
||||||
|
public class AuditableAction {
|
||||||
|
// public static final EventId Tenant_Available_Notifiers_Query = new EventId(2006, "Tenant_Available_Notifiers_Query");
|
||||||
|
public static final EventId Principal_Lookup = new EventId(6000, "Principal_Lookup");
|
||||||
|
public static final EventId Tenants_Lookup = new EventId(6001, "Tenants_Lookup");
|
||||||
|
|
||||||
|
// public static final EventId User_Available_Notifiers_Query = new EventId(10004, "User_Available_Notifiers_Query");
|
||||||
|
|
||||||
|
public static final EventId User_Query = new EventId(11000, "User_Query");
|
||||||
|
public static final EventId User_Lookup = new EventId(11001, "User_Lookup");
|
||||||
|
public static final EventId User_Persist = new EventId(11002, "User_Persist");
|
||||||
|
public static final EventId User_Delete = new EventId(11003, "User_Delete");
|
||||||
|
|
||||||
|
public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query");
|
||||||
|
public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup");
|
||||||
|
public static final EventId Tenant_Persist = new EventId(12002, "Tenant_Persist");
|
||||||
|
public static final EventId Tenant_Delete = new EventId(12003, "Tenant_Delete");
|
||||||
|
|
||||||
|
// public static final EventId Notification_Query = new EventId(19000, "Notification_Query");
|
||||||
|
// public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup");
|
||||||
|
// public static final EventId Notification_Persist = new EventId(19002, "Notification_Persist");
|
||||||
|
// public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete");
|
||||||
|
|
||||||
|
// public static final EventId InApp_Notification_Query = new EventId(20000, "InApp_Notification_Query");
|
||||||
|
// public static final EventId InApp_Notification_Lookup = new EventId(20001, "InApp_Notification_Lookup");
|
||||||
|
// public static final EventId InApp_Notification_Persist = new EventId(20002, "InApp_Notification_Persist");
|
||||||
|
// public static final EventId InApp_Notification_Delete = new EventId(20003, "InApp_Notification_Delete");
|
||||||
|
// public static final EventId InApp_Notification_Read = new EventId(20003, "InApp_Notification_Read");
|
||||||
|
// public static final EventId InApp_Notification_Read_All = new EventId(20003, "InApp_Notification_Read_All");
|
||||||
|
|
||||||
|
public static final EventId Tenant_Configuration_Query = new EventId(21000, "Tenant_Configuration_Query");
|
||||||
|
public static final EventId Tenant_Configuration_Lookup = new EventId(21001, "Tenant_Configuration_Lookup");
|
||||||
|
public static final EventId Tenant_Configuration_Persist = new EventId(21002, "Tenant_Configuration_Persist");
|
||||||
|
public static final EventId Tenant_Configuration_Delete = new EventId(21003, "Tenant_Configuration_Delete");
|
||||||
|
|
||||||
|
// public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query");
|
||||||
|
// public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup");
|
||||||
|
// public static final EventId User_Notification_Preference_Persist = new EventId(22002, "User_Notification_Preference_Persist");
|
||||||
|
// public static final EventId User_Notification_Preference_Delete = new EventId(22003, "User_Notification_Preference_Delete");
|
||||||
|
//
|
||||||
|
// public static final EventId Notification_Template_Query = new EventId(23000, "Notification_Template_Query");
|
||||||
|
// public static final EventId Notification_Template_Lookup = new EventId(23001, "Notification_Template_Lookup");
|
||||||
|
// public static final EventId Notification_Template_Persist = new EventId(23002, "Notification_Template_Persist");
|
||||||
|
// public static final EventId Notification_Template_Delete = new EventId(23003, "Notification_Template_Delete");
|
||||||
|
|
||||||
|
public static final EventId Annotation_Query = new EventId(24000, "Annotation_Query");
|
||||||
|
public static final EventId Annotation_Lookup = new EventId(24001, "Annotation_Lookup");
|
||||||
|
public static final EventId Annotation_Persist = new EventId(24002, "Annotation_Persist");
|
||||||
|
public static final EventId Annotation_Delete = new EventId(24003, "Annotation_Delete");
|
||||||
|
|
||||||
|
public static final EventId Entity_User_Query = new EventId(25000, "Entity_User_Query");
|
||||||
|
public static final EventId Entity_User_Lookup = new EventId(25001, "Entity_User_Lookup");
|
||||||
|
public static final EventId Entity_User_Persist = new EventId(25002, "Entity_User_Persist");
|
||||||
|
public static final EventId Entity_User_Delete = new EventId(25003, "Entity_User_Delete");
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class AffiliatedAuthorizationRequirement implements AuthorizationRequirement {
|
||||||
|
private final Set<String> requiredPermissions;
|
||||||
|
private final boolean matchAll;
|
||||||
|
|
||||||
|
public AffiliatedAuthorizationRequirement(Set<String> requiredPermissions) {
|
||||||
|
this(false, requiredPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliatedAuthorizationRequirement(String... requiredPermissions) {
|
||||||
|
this(false, requiredPermissions);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliatedAuthorizationRequirement(boolean matchAll, Set<String> requiredPermissions) {
|
||||||
|
this.matchAll = matchAll;
|
||||||
|
this.requiredPermissions = requiredPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliatedAuthorizationRequirement(boolean matchAll, String... requiredPermissions) {
|
||||||
|
this.requiredPermissions = new HashSet<>();
|
||||||
|
this.matchAll = matchAll;
|
||||||
|
this.requiredPermissions.addAll(Arrays.stream(requiredPermissions).distinct().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getRequiredPermissions() {
|
||||||
|
return requiredPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getMatchAll() {
|
||||||
|
return matchAll;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationResource;
|
||||||
|
|
||||||
|
public class AffiliatedResource extends AuthorizationResource {
|
||||||
|
private Boolean isAffiliated;
|
||||||
|
|
||||||
|
public AffiliatedResource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliatedResource(Boolean isAffiliated) {
|
||||||
|
this.isAffiliated = isAffiliated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getAffiliated() {
|
||||||
|
return isAffiliated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAffiliated(Boolean affiliated) {
|
||||||
|
isAffiliated = affiliated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public enum AuthorizationFlags {
|
||||||
|
None, Permission, Associated, Owner;
|
||||||
|
public static final EnumSet<AuthorizationFlags> OwnerOrPermissionAssociated = EnumSet.of(Owner, Permission, Associated);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
public class ClaimNames {
|
||||||
|
public static final String ExternalProviderName = "ExternalProviderName";
|
||||||
|
public static final String TenantCodesClaimName = "TenantCodes";
|
||||||
|
public static final String TenantClaimName = "x-tenant";
|
||||||
|
public static final String GlobalRolesClaimName = "GlobalRoles";
|
||||||
|
public static final String TenantRolesClaimName = "TenantRoles";
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
|
||||||
|
|
||||||
|
public class OwnedAuthorizationRequirement implements AuthorizationRequirement {
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import gr.cite.commons.web.authz.policy.AuthorizationResource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class OwnedResource extends AuthorizationResource {
|
||||||
|
private List<UUID> userIds;
|
||||||
|
|
||||||
|
public OwnedResource(UUID userId) {
|
||||||
|
this(List.of(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OwnedResource(List<UUID> userIds) {
|
||||||
|
this.userIds = userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UUID> getUserIds() {
|
||||||
|
return userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserIds(List<UUID> userIds) {
|
||||||
|
this.userIds = userIds;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
public final class Permission {
|
||||||
|
public static String DeferredAffiliation = "DeferredAffiliation";
|
||||||
|
//User
|
||||||
|
public static String BrowseUser = "BrowseUser";
|
||||||
|
public static String EditUser = "EditUser";
|
||||||
|
public static String DeleteUser = "DeleteUser";
|
||||||
|
|
||||||
|
|
||||||
|
//Tenant
|
||||||
|
public static String BrowseTenant = "BrowseTenant";
|
||||||
|
public static String EditTenant = "EditTenant";
|
||||||
|
public static String DeleteTenant = "DeleteTenant";
|
||||||
|
public static String AllowNoTenant = "AllowNoTenant";
|
||||||
|
|
||||||
|
//Annotation
|
||||||
|
public static final String BrowseAnnotation = "BrowseAnnotation";
|
||||||
|
public static String NewAnnotation = "NewAnnotation";
|
||||||
|
public static String EditAnnotation = "EditAnnotation";
|
||||||
|
public static String DeleteAnnotation = "DeleteAnnotation";
|
||||||
|
|
||||||
|
public static final String BrowseTenantConfiguration = "BrowseTenantConfiguration";
|
||||||
|
public static final String EditTenantConfiguration = "EditTenantConfiguration";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package gr.cite.annotation.authorization;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||||
|
public class PermissionNameProvider {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PermissionNameProvider.class);
|
||||||
|
private final List<String> permissions;
|
||||||
|
|
||||||
|
public PermissionNameProvider(ConventionService conventionService) {
|
||||||
|
this.permissions = new ArrayList<>();
|
||||||
|
Class<Permission> clazz = Permission.class;
|
||||||
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
if (Modifier.isStatic(f.getModifiers())) {
|
||||||
|
try {
|
||||||
|
Object value = f.get(null);
|
||||||
|
if (value != null && !conventionService.isNullOrEmpty((String)value)) this.permissions.add((String)value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Can not load permission " + f.getName() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getPermissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package gr.cite.annotation.authorization.authorizationcontentresolver;
|
||||||
|
|
||||||
|
import gr.cite.tools.cache.CacheOptions;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cache.affiliation")
|
||||||
|
public class AffiliationCacheOptions extends CacheOptions {
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package gr.cite.annotation.authorization.authorizationcontentresolver;
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedResource;
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.tools.cache.CacheService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AffiliationCacheService extends CacheService<AffiliationCacheService.AffiliationCacheValue> {
|
||||||
|
|
||||||
|
public static class AffiliationCacheValue {
|
||||||
|
|
||||||
|
public AffiliationCacheValue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliationCacheValue(UUID userId, UUID entityId, String entityType, AffiliatedResource affiliatedResource) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.entityId = entityId;
|
||||||
|
this.entityType = entityType;
|
||||||
|
this.affiliatedResource = affiliatedResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID userId;
|
||||||
|
private UUID entityId;
|
||||||
|
private String entityType;
|
||||||
|
private AffiliatedResource affiliatedResource;
|
||||||
|
|
||||||
|
public UUID getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(UUID userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getEntityId() {
|
||||||
|
return entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityId(UUID entityId) {
|
||||||
|
this.entityId = entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntityType() {
|
||||||
|
return entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntityType(String entityType) {
|
||||||
|
this.entityType = entityType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AffiliatedResource getAffiliatedResource() {
|
||||||
|
return affiliatedResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAffiliatedResource(AffiliatedResource affiliatedResource) {
|
||||||
|
this.affiliatedResource = affiliatedResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ConventionService conventionService;
|
||||||
|
@Autowired
|
||||||
|
public AffiliationCacheService(AffiliationCacheOptions options, ConventionService conventionService) {
|
||||||
|
super(options);
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<AffiliationCacheValue> valueClass() {
|
||||||
|
return AffiliationCacheValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String keyOf(AffiliationCacheValue value) {
|
||||||
|
return this.buildKey(value.getUserId(), value.getEntityId(), value.getEntityType());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String buildKey(UUID userId, UUID entityId, String entityType) {
|
||||||
|
if (userId == null) throw new IllegalArgumentException("userId id is required");
|
||||||
|
if (entityId == null) throw new IllegalArgumentException("entityId id is required");
|
||||||
|
if (this.conventionService.isNullOrEmpty(entityType)) throw new IllegalArgumentException("entityType id is required");
|
||||||
|
|
||||||
|
HashMap<String, String> keyParts = new HashMap<>();
|
||||||
|
keyParts.put("$user$", userId.toString().replace("-", "").toLowerCase(Locale.ROOT));
|
||||||
|
keyParts.put("$entity$", entityId.toString().replace("-", "").toLowerCase(Locale.ROOT));
|
||||||
|
keyParts.put("$type$", entityType);
|
||||||
|
return this.generateKey(keyParts);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package gr.cite.annotation.authorization.authorizationcontentresolver;
|
||||||
|
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedResource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface AuthorizationContentResolver {
|
||||||
|
AffiliatedResource entityAffiliation(UUID id);
|
||||||
|
|
||||||
|
Map<UUID, AffiliatedResource> entitiesAffiliation(List<UUID> ids);
|
||||||
|
|
||||||
|
List<String> getPermissionNames();
|
||||||
|
|
||||||
|
AffiliatedResource annotationAffiliation(UUID id);
|
||||||
|
|
||||||
|
Map<UUID, AffiliatedResource> annotationsAffiliation(List<UUID> ids);
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package gr.cite.annotation.authorization.authorizationcontentresolver;
|
||||||
|
|
||||||
|
import gr.cite.annotation.authorization.AffiliatedResource;
|
||||||
|
import gr.cite.annotation.authorization.PermissionNameProvider;
|
||||||
|
import gr.cite.annotation.common.enums.IsActive;
|
||||||
|
import gr.cite.annotation.common.scope.user.UserScope;
|
||||||
|
import gr.cite.annotation.data.AnnotationEntity;
|
||||||
|
import gr.cite.annotation.data.EntityUserEntity;
|
||||||
|
import gr.cite.annotation.model.Annotation;
|
||||||
|
import gr.cite.annotation.model.EntityUser;
|
||||||
|
import gr.cite.annotation.query.AnnotationQuery;
|
||||||
|
import gr.cite.annotation.query.EntityUserQuery;
|
||||||
|
import gr.cite.tools.data.query.QueryFactory;
|
||||||
|
import gr.cite.tools.fieldset.BaseFieldSet;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.context.annotation.RequestScope;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequestScope
|
||||||
|
public class AuthorizationContentResolverImpl implements AuthorizationContentResolver {
|
||||||
|
private final QueryFactory queryFactory;
|
||||||
|
private final UserScope userScope;
|
||||||
|
private final AffiliationCacheService affiliationCacheService;
|
||||||
|
private final PermissionNameProvider permissionNameProvider;
|
||||||
|
public AuthorizationContentResolverImpl(QueryFactory queryFactory, UserScope userScope, AffiliationCacheService affiliationCacheService, PermissionNameProvider permissionNameProvider) {
|
||||||
|
this.queryFactory = queryFactory;
|
||||||
|
this.userScope = userScope;
|
||||||
|
this.affiliationCacheService = affiliationCacheService;
|
||||||
|
this.permissionNameProvider = permissionNameProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AffiliatedResource entityAffiliation(UUID id) {
|
||||||
|
return this.entitiesAffiliation(List.of(id)).getOrDefault(id, new AffiliatedResource());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Map<UUID, AffiliatedResource> entitiesAffiliation(List<UUID> ids){
|
||||||
|
UUID userId = this.userScope.getUserIdSafe();
|
||||||
|
Map<UUID, AffiliatedResource> affiliatedResources = new HashMap<>();
|
||||||
|
for (UUID id : ids){
|
||||||
|
affiliatedResources.put(id, new AffiliatedResource());
|
||||||
|
}
|
||||||
|
if (userId == null || !userScope.isSet()) return affiliatedResources;
|
||||||
|
|
||||||
|
List<UUID> idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, AnnotationEntity._entityId);
|
||||||
|
if (idsToResolve.isEmpty()) return affiliatedResources;
|
||||||
|
|
||||||
|
List<EntityUserEntity> entityUsers = this.queryFactory.query(EntityUserQuery.class).entityIds(ids).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId));
|
||||||
|
|
||||||
|
for (UUID entityId : entityUsers.stream().map(EntityUserEntity::getEntityId).distinct().toList()){
|
||||||
|
affiliatedResources.get(entityId).setAffiliated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, AnnotationEntity._entityId);
|
||||||
|
return affiliatedResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPermissionNames() {
|
||||||
|
return permissionNameProvider.getPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AffiliatedResource annotationAffiliation(UUID id) {
|
||||||
|
return this.annotationsAffiliation(List.of(id)).getOrDefault(id, new AffiliatedResource());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Map<UUID, AffiliatedResource> annotationsAffiliation(List<UUID> ids){
|
||||||
|
UUID userId = this.userScope.getUserIdSafe();
|
||||||
|
Map<UUID, AffiliatedResource> affiliatedResources = new HashMap<>();
|
||||||
|
for (UUID id : ids){
|
||||||
|
affiliatedResources.put(id, new AffiliatedResource());
|
||||||
|
}
|
||||||
|
if (userId == null || !userScope.isSet()) return affiliatedResources;
|
||||||
|
|
||||||
|
List<UUID> idsToResolve = this.getAffiliatedFromCache(ids, userId, affiliatedResources, AnnotationEntity.class.getSimpleName());
|
||||||
|
if (idsToResolve.isEmpty()) return affiliatedResources;
|
||||||
|
|
||||||
|
List<AnnotationEntity> annotationEntities = this.queryFactory.query(AnnotationQuery.class).ids(ids).collectAs(new BaseFieldSet().ensure(Annotation._id).ensure(Annotation._entityId).ensure(Annotation._id));
|
||||||
|
List<EntityUserEntity> entityUsers = this.queryFactory.query(EntityUserQuery.class).entityIds(annotationEntities.stream().map(AnnotationEntity::getEntityId).distinct().toList()).userIds(userId).isActive(IsActive.Active).collectAs(new BaseFieldSet().ensure(EntityUser._id).ensure(EntityUser._entityId));
|
||||||
|
Map<UUID, List<EntityUserEntity>> dmpUsersMap = entityUsers.stream().collect(Collectors.groupingBy(EntityUserEntity::getEntityId));
|
||||||
|
|
||||||
|
for (AnnotationEntity annotation : annotationEntities){
|
||||||
|
List<EntityUserEntity> dmpDescriptionUsers = dmpUsersMap.getOrDefault(annotation.getEntityId(), new ArrayList<>());
|
||||||
|
if (!dmpDescriptionUsers.isEmpty()) {
|
||||||
|
affiliatedResources.get(annotation.getId()).setAffiliated(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ensureAffiliatedInCache(idsToResolve, userId, affiliatedResources, AnnotationEntity.class.getSimpleName());
|
||||||
|
return affiliatedResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private List<UUID> getAffiliatedFromCache(List<UUID> ids, UUID userId, Map<UUID, AffiliatedResource> affiliatedResources, String entityType){
|
||||||
|
List<UUID> idsToResolve = new ArrayList<>();
|
||||||
|
for (UUID id : ids){
|
||||||
|
AffiliationCacheService.AffiliationCacheValue cacheValue = this.affiliationCacheService.lookup(this.affiliationCacheService.buildKey(userId, id, entityType));
|
||||||
|
if (cacheValue != null) affiliatedResources.put(id, cacheValue.getAffiliatedResource());
|
||||||
|
else idsToResolve.add(id);
|
||||||
|
}
|
||||||
|
return idsToResolve;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureAffiliatedInCache(List<UUID> idsToResolve, UUID userId, Map<UUID, AffiliatedResource> affiliatedResources, String entityType){
|
||||||
|
for (UUID id : idsToResolve){
|
||||||
|
AffiliatedResource affiliatedResource = affiliatedResources.getOrDefault(id, null);
|
||||||
|
if (affiliatedResource != null) {
|
||||||
|
AffiliationCacheService.AffiliationCacheValue cacheValue = new AffiliationCacheService.AffiliationCacheValue(userId, id, entityType, affiliatedResource);
|
||||||
|
this.affiliationCacheService.put(cacheValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package gr.cite.annotation.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JsonHandlingService {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(JsonHandlingService.class));
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
public String toJson(Object item) throws JsonProcessingException {
|
||||||
|
if (item == null) return null;
|
||||||
|
return objectMapper.writeValueAsString(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJsonSafe(Object item) {
|
||||||
|
if (item == null) return null;
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(item);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fromJson(Class<T> type, String json) throws JsonProcessingException {
|
||||||
|
if (json == null) return null;
|
||||||
|
return objectMapper.readValue(json, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fromJsonSafe(Class<T> type, String json) {
|
||||||
|
if (json == null) return null;
|
||||||
|
try {
|
||||||
|
return objectMapper.readValue(json, type);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package gr.cite.annotation.common;
|
||||||
|
|
||||||
|
public class StringUtils {
|
||||||
|
|
||||||
|
public static Boolean isNullOrEmpty(String string) {
|
||||||
|
return string == null || string.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package gr.cite.annotation.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import gr.cite.annotation.common.types.xml.XmlSerializable;
|
||||||
|
import jakarta.xml.bind.JAXBContext;
|
||||||
|
import jakarta.xml.bind.JAXBException;
|
||||||
|
import jakarta.xml.bind.Marshaller;
|
||||||
|
import jakarta.xml.bind.Unmarshaller;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.OutputKeys;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
|
import javax.xml.transform.dom.DOMSource;
|
||||||
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||||
|
public class XmlHandlingService {
|
||||||
|
|
||||||
|
public String generateXml(Document doc) throws TransformerException {
|
||||||
|
TransformerFactory tFact = TransformerFactory.newInstance();
|
||||||
|
Transformer trans = tFact.newTransformer();
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
StreamResult result = new StreamResult(writer);
|
||||||
|
DOMSource source = new DOMSource(doc);
|
||||||
|
trans.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||||
|
trans.transform(source, result);
|
||||||
|
return writer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toXml(Object item) throws JsonProcessingException, JAXBException, ParserConfigurationException, InvalidApplicationException, TransformerException {
|
||||||
|
if (XmlSerializable.class.isAssignableFrom(item.getClass())){
|
||||||
|
Document document = this.getDocument();
|
||||||
|
if (document == null) throw new InvalidApplicationException("Can not create document");
|
||||||
|
document.appendChild(((XmlSerializable)item).toXml(document));
|
||||||
|
return this.generateXml(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
JAXBContext context = JAXBContext.newInstance(item.getClass());
|
||||||
|
Marshaller marshaller = context.createMarshaller();
|
||||||
|
StringWriter out = new StringWriter();
|
||||||
|
marshaller.marshal(item, out);
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toXmlSafe(Object item) {
|
||||||
|
if (item == null) return null;
|
||||||
|
try {
|
||||||
|
return this.toXml(item);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fromXml(Class<T> type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException {
|
||||||
|
if (XmlSerializable.class.isAssignableFrom(type)){
|
||||||
|
XmlSerializable<T> object = (XmlSerializable<T>)type.newInstance();
|
||||||
|
return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement());
|
||||||
|
} else {
|
||||||
|
JAXBContext jaxbContext = JAXBContext.newInstance(type);
|
||||||
|
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
|
||||||
|
|
||||||
|
return (T) jaxbUnmarshaller.unmarshal(new StringReader(xmlString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T fromXmlSafe(Class<T> type, String xmlString) {
|
||||||
|
if (xmlString == null) return null;
|
||||||
|
try {
|
||||||
|
return this.fromXml(type, xmlString);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public <T extends XmlSerializable<T>> T xmlSerializableFromXml(Class<T> type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException {
|
||||||
|
// T object = type.newInstance();
|
||||||
|
// return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public <T extends XmlSerializable<T>> T xmlSerializableFromXmlSafe(Class<T> type, String xmlString) {
|
||||||
|
// if (xmlString == null) return null;
|
||||||
|
// try {
|
||||||
|
// return this.xmlSerializableFromXml(type, xmlString);
|
||||||
|
// } catch (Exception ex) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public Document getDocument(String xml) throws ParserConfigurationException, IOException, SAXException {
|
||||||
|
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||||
|
InputSource inputStream = new InputSource(new StringReader(xml));
|
||||||
|
return docBuilder.parse(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document getDocument() throws ParserConfigurationException {
|
||||||
|
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||||
|
return docBuilder.newDocument();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package gr.cite.annotation.common.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import gr.cite.annotation.data.conventers.DatabaseEnum;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum AnnotationProtectionType implements DatabaseEnum<Short> {
|
||||||
|
|
||||||
|
Private((short) 0),
|
||||||
|
EntityAccessors((short) 1);
|
||||||
|
|
||||||
|
private final Short value;
|
||||||
|
|
||||||
|
AnnotationProtectionType(Short value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public Short getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<Short, AnnotationProtectionType> map = EnumUtils.getEnumValueMap(AnnotationProtectionType.class);
|
||||||
|
|
||||||
|
public static AnnotationProtectionType of(Short i) {
|
||||||
|
return map.get(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package gr.cite.annotation.common.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import gr.cite.annotation.data.conventers.DatabaseEnum;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum ContactInfoType implements DatabaseEnum<Short> {
|
||||||
|
Email((short) 0),
|
||||||
|
MobilePhone((short) 1),
|
||||||
|
LandLinePhone((short) 2);
|
||||||
|
private final Short value;
|
||||||
|
|
||||||
|
ContactInfoType(Short value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JsonValue
|
||||||
|
public Short getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<Short, ContactInfoType> map = EnumUtils.getEnumValueMap(ContactInfoType.class);
|
||||||
|
|
||||||
|
public static ContactInfoType of(Short i) {
|
||||||
|
return map.get(i);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package gr.cite.annotation.common.enums;
|
||||||
|
|
||||||
|
import gr.cite.annotation.data.conventers.DatabaseEnum;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EnumUtils {
|
||||||
|
public static <EnumType extends Enum<EnumType> & DatabaseEnum<EnumValue>, EnumValue> Map<EnumValue, EnumType> getEnumValueMap(Class<EnumType> enumType){
|
||||||
|
HashMap<EnumValue, EnumType> map = new HashMap<>();
|
||||||
|
for (EnumType v : enumType.getEnumConstants()) {
|
||||||
|
map.put(v.getValue(), v);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package gr.cite.annotation.common.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import gr.cite.annotation.data.conventers.DatabaseEnum;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum IsActive implements DatabaseEnum<Short> {
|
||||||
|
Inactive((short) 0),
|
||||||
|
Active((short) 1);
|
||||||
|
|
||||||
|
private final Short value;
|
||||||
|
|
||||||
|
IsActive(Short value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public Short getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<Short, IsActive> map = EnumUtils.getEnumValueMap(IsActive.class);
|
||||||
|
|
||||||
|
public static IsActive of(Short i) {
|
||||||
|
return map.get(i);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package gr.cite.annotation.common.enums;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
import gr.cite.annotation.data.conventers.DatabaseEnum;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum TenantConfigurationType implements DatabaseEnum<Short> {
|
||||||
|
EMAIL_CLIENT_CONFIGURATION((short)1),
|
||||||
|
DEFAULT_USER_LOCALE((short)3),
|
||||||
|
NOTIFIER_LIST((short)4);
|
||||||
|
|
||||||
|
private final Short value;
|
||||||
|
|
||||||
|
TenantConfigurationType(Short value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public Short getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Map<Short, TenantConfigurationType> map = EnumUtils.getEnumValueMap(TenantConfigurationType.class);
|
||||||
|
|
||||||
|
public static TenantConfigurationType of(Short i) {
|
||||||
|
return map.get(i);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package gr.cite.annotation.common.lock;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class LockByKeyManager {
|
||||||
|
|
||||||
|
private static class LockWrapper {
|
||||||
|
private final ReentrantLock lock = new ReentrantLock();
|
||||||
|
private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1);
|
||||||
|
|
||||||
|
private LockWrapper addThreadInQueue() {
|
||||||
|
numberOfThreadsInQueue.incrementAndGet();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int removeThreadFromQueue() {
|
||||||
|
return numberOfThreadsInQueue.decrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConcurrentHashMap<String, LockWrapper> locks = new ConcurrentHashMap<String, LockWrapper>();
|
||||||
|
|
||||||
|
public void lock(String key) {
|
||||||
|
LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue());
|
||||||
|
lockWrapper.lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryLock(String key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
LockWrapper lockWrapper = null;
|
||||||
|
try {
|
||||||
|
lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue());
|
||||||
|
return lockWrapper.lock.tryLock(timeout, unit);
|
||||||
|
} catch (Exception ex){
|
||||||
|
if (lockWrapper != null && lockWrapper.removeThreadFromQueue() == 0) {
|
||||||
|
// NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal
|
||||||
|
locks.remove(key, lockWrapper);
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlock(String key) {
|
||||||
|
LockWrapper lockWrapper = locks.get(key);
|
||||||
|
lockWrapper.lock.unlock();
|
||||||
|
if (lockWrapper.removeThreadFromQueue() == 0) {
|
||||||
|
// NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal
|
||||||
|
locks.remove(key, lockWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package gr.cite.annotation.common.scope.fake;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FakeRequestAttributes implements RequestAttributes {
|
||||||
|
private Map<String, Object> requestAttributeMap = new HashMap<>();
|
||||||
|
private final Map<String, Runnable> requestDestructionCallbacks = new LinkedHashMap<>(8);
|
||||||
|
private volatile boolean requestActive = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getAttribute(String name, int scope) {
|
||||||
|
if (scope == RequestAttributes.SCOPE_REQUEST) {
|
||||||
|
if (!isRequestActive()) {
|
||||||
|
throw new IllegalStateException("Cannot ask for request attribute - request is not active anymore!");
|
||||||
|
}
|
||||||
|
return this.requestAttributeMap.get(name);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAttribute(String name, Object value, int scope) {
|
||||||
|
if (scope == RequestAttributes.SCOPE_REQUEST) {
|
||||||
|
if (!isRequestActive()) {
|
||||||
|
throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
|
||||||
|
}
|
||||||
|
this.requestAttributeMap.put(name, value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAttribute(String name, int scope) {
|
||||||
|
if (scope == RequestAttributes.SCOPE_REQUEST) {
|
||||||
|
if (isRequestActive()) {
|
||||||
|
removeRequestDestructionCallback(name);
|
||||||
|
this.requestAttributeMap.remove(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getAttributeNames(int scope) {
|
||||||
|
if (scope == RequestAttributes.SCOPE_REQUEST) {
|
||||||
|
if (!isRequestActive()) {
|
||||||
|
throw new IllegalStateException("Cannot ask for request attributes - request is not active anymore!");
|
||||||
|
}
|
||||||
|
return this.requestAttributeMap.keySet().toArray(new String[0]);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
|
||||||
|
}
|
||||||
|
//return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerDestructionCallback(String name, Runnable callback, int scope) {
|
||||||
|
if (scope == SCOPE_REQUEST) {
|
||||||
|
registerRequestDestructionCallback(name, callback);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void registerRequestDestructionCallback(String name, Runnable callback) {
|
||||||
|
Assert.notNull(name, "Name must not be null");
|
||||||
|
Assert.notNull(callback, "Callback must not be null");
|
||||||
|
synchronized (this.requestDestructionCallbacks) {
|
||||||
|
this.requestDestructionCallbacks.put(name, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object resolveReference(String key) {
|
||||||
|
// Not supported
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSessionId() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getSessionMutex() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestCompleted() {
|
||||||
|
executeRequestDestructionCallbacks();
|
||||||
|
for (String name : getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
|
||||||
|
this.removeAttribute(name, RequestAttributes.SCOPE_REQUEST);
|
||||||
|
}
|
||||||
|
this.requestActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean isRequestActive() {
|
||||||
|
return this.requestActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final void removeRequestDestructionCallback(String name) {
|
||||||
|
Assert.notNull(name, "Name must not be null");
|
||||||
|
synchronized (this.requestDestructionCallbacks) {
|
||||||
|
this.requestDestructionCallbacks.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeRequestDestructionCallbacks() {
|
||||||
|
synchronized (this.requestDestructionCallbacks) {
|
||||||
|
for (Runnable runnable : this.requestDestructionCallbacks.values()) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
this.requestDestructionCallbacks.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package gr.cite.annotation.common.scope.fake;
|
||||||
|
|
||||||
|
import org.springframework.web.context.request.RequestAttributes;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
public class FakeRequestScope implements Closeable {
|
||||||
|
private RequestAttributes initialRequestAttributes = null;
|
||||||
|
private FakeRequestAttributes currentRequestAttributes = null;
|
||||||
|
boolean isInUse = false;
|
||||||
|
|
||||||
|
public FakeRequestScope() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.close();
|
||||||
|
this.isInUse = true;
|
||||||
|
|
||||||
|
this.initialRequestAttributes = RequestContextHolder.getRequestAttributes();
|
||||||
|
this.currentRequestAttributes = new FakeRequestAttributes();
|
||||||
|
RequestContextHolder.setRequestAttributes(this.currentRequestAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (!this.isInUse) return;
|
||||||
|
this.isInUse = false;
|
||||||
|
|
||||||
|
if (initialRequestAttributes != null) RequestContextHolder.setRequestAttributes(initialRequestAttributes);
|
||||||
|
else RequestContextHolder.resetRequestAttributes();
|
||||||
|
|
||||||
|
if (currentRequestAttributes != null) currentRequestAttributes.requestCompleted();
|
||||||
|
|
||||||
|
this.initialRequestAttributes = null;
|
||||||
|
this.currentRequestAttributes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package gr.cite.annotation.common.scope.tenant;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(MultitenancyProperties.class)
|
||||||
|
public class MultitenancyConfiguration {
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package gr.cite.annotation.common.scope.tenant;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "tenant.multitenancy")
|
||||||
|
public class MultitenancyProperties {
|
||||||
|
private boolean isMultitenant;
|
||||||
|
private String defaultTenantCode;
|
||||||
|
|
||||||
|
public boolean isMultitenant() {
|
||||||
|
return isMultitenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsMultitenant(boolean multitenant) {
|
||||||
|
isMultitenant = multitenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMultitenant(boolean multitenant) {
|
||||||
|
isMultitenant = multitenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultTenantCode() {
|
||||||
|
return defaultTenantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultTenantCode(String defaultTenantCode) {
|
||||||
|
this.defaultTenantCode = defaultTenantCode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package gr.cite.annotation.common.scope.tenant;
|
||||||
|
|
||||||
|
import gr.cite.annotation.data.tenant.TenantScopedBaseEntity;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.annotation.RequestScope;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequestScope
|
||||||
|
public class TenantScope {
|
||||||
|
public static final String TenantReplaceParameter = "::TenantCode::";
|
||||||
|
private final MultitenancyProperties multitenancy;
|
||||||
|
private final AtomicReference<UUID> tenant = new AtomicReference<>();
|
||||||
|
private final AtomicReference<String> tenantCode = new AtomicReference<>();
|
||||||
|
private final AtomicReference<UUID> initialTenant = new AtomicReference<>();
|
||||||
|
private final AtomicReference<String> initialTenantCode = new AtomicReference<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TenantScope(MultitenancyProperties multitenancy) {
|
||||||
|
this.multitenancy = multitenancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isMultitenant() {
|
||||||
|
return multitenancy.isMultitenant();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultTenantCode() {
|
||||||
|
return multitenancy.getDefaultTenantCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isSet() {
|
||||||
|
if (!this.isMultitenant())
|
||||||
|
return Boolean.TRUE;
|
||||||
|
return this.tenant.get() != null || this.isDefaultTenant();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean isDefaultTenant() {
|
||||||
|
if (!this.isMultitenant())
|
||||||
|
return Boolean.TRUE;
|
||||||
|
return this.multitenancy.getDefaultTenantCode().equalsIgnoreCase(this.tenantCode.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getTenant() throws InvalidApplicationException {
|
||||||
|
if (!this.isMultitenant())
|
||||||
|
return null;
|
||||||
|
if (this.tenant.get() == null && !this.isDefaultTenant())
|
||||||
|
throw new InvalidApplicationException("tenant not set");
|
||||||
|
return this.isDefaultTenant() ? null : this.tenant.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTenantCode() throws InvalidApplicationException {
|
||||||
|
if (!this.isMultitenant())
|
||||||
|
return null;
|
||||||
|
if (this.tenantCode.get() == null)
|
||||||
|
throw new InvalidApplicationException("tenant not set");
|
||||||
|
return this.tenantCode.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) {
|
||||||
|
this.tenant.set(tenant);
|
||||||
|
this.tenantCode.set(tenantCode);
|
||||||
|
|
||||||
|
if (this.tenant.get() != null && !this.isDefaultTenant()) {
|
||||||
|
if(!this.isDefaultTenant()) {
|
||||||
|
entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
|
||||||
|
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
|
||||||
|
} else {
|
||||||
|
entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTempTenant(EntityManager entityManager) {
|
||||||
|
this.tenant.set(this.initialTenant.get());
|
||||||
|
this.tenantCode.set(this.initialTenantCode.get());
|
||||||
|
if (this.initialTenant.get() != null && !this.isDefaultTenant()) {
|
||||||
|
if(!this.isDefaultTenant()) {
|
||||||
|
entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.TENANT_FILTER)
|
||||||
|
.setParameter(TenantScopedBaseEntity.TENANT_FILTER_TENANT_PARAM, this.tenant.get().toString());
|
||||||
|
} else {
|
||||||
|
entityManager
|
||||||
|
.unwrap(Session.class)
|
||||||
|
.enableFilter(TenantScopedBaseEntity.DEFAULT_TENANT_FILTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTenant(UUID tenant, String tenantCode) {
|
||||||
|
if (this.isMultitenant()) {
|
||||||
|
this.tenant.set(tenant);
|
||||||
|
this.initialTenant.set(tenant);
|
||||||
|
this.tenantCode.set(tenantCode);
|
||||||
|
this.initialTenantCode.set(tenantCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package gr.cite.annotation.common.scope.tenant;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface TenantScoped {
|
||||||
|
void setTenantId(UUID tenantId);
|
||||||
|
UUID getTenantId();
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package gr.cite.annotation.common.scope.user;
|
||||||
|
|
||||||
|
import gr.cite.tools.logging.LoggerService;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.context.annotation.RequestScope;
|
||||||
|
|
||||||
|
import javax.management.InvalidApplicationException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequestScope
|
||||||
|
public class UserScope {
|
||||||
|
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserScope.class));
|
||||||
|
private UUID userId = null;
|
||||||
|
|
||||||
|
public Boolean isSet(){
|
||||||
|
return this.userId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUserId() throws InvalidApplicationException {
|
||||||
|
if (this.userId == null) throw new InvalidApplicationException("user not set");
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUserIdSafe() {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(UUID userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package gr.cite.annotation.common.types.tenant;
|
||||||
|
|
||||||
|
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElement;
|
||||||
|
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
||||||
|
@XmlRootElement(name = "config")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class TenantConfigEntity {
|
||||||
|
|
||||||
|
@XmlElement(name = "deposit-configuration")
|
||||||
|
private TenantDepositConfigEntity deposit;
|
||||||
|
|
||||||
|
@XmlElement(name = "file-transformers-configuration")
|
||||||
|
private TenantFileTransformersConfigEntity fileTransformers;
|
||||||
|
|
||||||
|
public TenantDepositConfigEntity getDeposit() {
|
||||||
|
return deposit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeposit(TenantDepositConfigEntity deposit) {
|
||||||
|
this.deposit = deposit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TenantFileTransformersConfigEntity getFileTransformers() {
|
||||||
|
return fileTransformers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileTransformers(TenantFileTransformersConfigEntity fileTransformers) {
|
||||||
|
this.fileTransformers = fileTransformers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package gr.cite.annotation.common.types.tenant;
|
||||||
|
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElement;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class TenantDepositConfigEntity {
|
||||||
|
|
||||||
|
@XmlElementWrapper(name = "sources")
|
||||||
|
@XmlElement(name = "source")
|
||||||
|
private List<TenantSourceEntity> sources;
|
||||||
|
|
||||||
|
|
||||||
|
public List<TenantSourceEntity> getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSources(List<TenantSourceEntity> sources) {
|
||||||
|
this.sources = sources;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package gr.cite.annotation.common.types.tenant;
|
||||||
|
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElement;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class TenantFileTransformersConfigEntity {
|
||||||
|
|
||||||
|
@XmlElementWrapper(name = "sources")
|
||||||
|
@XmlElement(name = "source")
|
||||||
|
private List<TenantSourceEntity> sources;
|
||||||
|
|
||||||
|
public List<TenantSourceEntity> getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSources(List<TenantSourceEntity> sources) {
|
||||||
|
this.sources = sources;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package gr.cite.annotation.common.types.tenant;
|
||||||
|
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElement;
|
||||||
|
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class TenantSourceEntity {
|
||||||
|
|
||||||
|
@XmlElement(name = "url")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@XmlElementWrapper(name = "codes")
|
||||||
|
@XmlElement(name = "code")
|
||||||
|
private List<String> codes;
|
||||||
|
|
||||||
|
@XmlElement(name = "issuer-url")
|
||||||
|
private String issuerUrl;
|
||||||
|
|
||||||
|
@XmlElement(name = "client-id")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@XmlElement(name = "client-secret")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
@XmlElement(name = "scope")
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCodes() {
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodes(List<String> codes) {
|
||||||
|
this.codes = codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuerUrl() {
|
||||||
|
return issuerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIssuerUrl(String issuerUrl) {
|
||||||
|
this.issuerUrl = issuerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientSecret() {
|
||||||
|
return clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(String scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package gr.cite.annotation.common.types.tenantconfiguration;
|
||||||
|
|
||||||
|
public class DefaultUserLocaleConfigurationDataContainer {
|
||||||
|
|
||||||
|
public static class Field {
|
||||||
|
public static final String LANGUAGE = "language";
|
||||||
|
public static final String TIME_ZONE = "timeZone";
|
||||||
|
public static final String CULTURE = "culture";
|
||||||
|
}
|
||||||
|
private String language;
|
||||||
|
private String timeZone;
|
||||||
|
private String culture;
|
||||||
|
|
||||||
|
public String getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLanguage(String language) {
|
||||||
|
this.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimeZone() {
|
||||||
|
return timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone(String timeZone) {
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCulture() {
|
||||||
|
return culture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCulture(String culture) {
|
||||||
|
this.culture = culture;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package gr.cite.annotation.common.types.tenantconfiguration;
|
||||||
|
|
||||||
|
public class EmailClientConfigurationDataContainer {
|
||||||
|
|
||||||
|
public static class Field {
|
||||||
|
public static final String REQUIRE_CREDENTIALS = "requireCredentials";
|
||||||
|
public static final String ENABLE_SSL = "enableSSL";
|
||||||
|
public static final String CERTIFICATE_PATH = "certificatePath";
|
||||||
|
public static final String HOST_SERVER = "hostServer";
|
||||||
|
public static final String HOST_PORT_NO = "hostPortNo";
|
||||||
|
public static final String EMAIL_ADDRESS = "emailAddress";
|
||||||
|
public static final String EMAIL_USER_NAME = "emailUserName";
|
||||||
|
public static final String EMAIL_PASSWORD = "emailPassword";
|
||||||
|
}
|
||||||
|
private Boolean requireCredentials;
|
||||||
|
private Boolean enableSSL;
|
||||||
|
private String certificatePath;
|
||||||
|
private String hostServer;
|
||||||
|
private Integer hostPortNo;
|
||||||
|
private String emailAddress;
|
||||||
|
private String emailUserName;
|
||||||
|
private String emailPassword;
|
||||||
|
|
||||||
|
public Boolean getRequireCredentials() {
|
||||||
|
return requireCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequireCredentials(Boolean requireCredentials) {
|
||||||
|
this.requireCredentials = requireCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEnableSSL() {
|
||||||
|
return enableSSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableSSL(Boolean enableSSL) {
|
||||||
|
this.enableSSL = enableSSL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCertificatePath() {
|
||||||
|
return certificatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCertificatePath(String certificatePath) {
|
||||||
|
this.certificatePath = certificatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHostServer() {
|
||||||
|
return hostServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostServer(String hostServer) {
|
||||||
|
this.hostServer = hostServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getHostPortNo() {
|
||||||
|
return hostPortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostPortNo(Integer hostPortNo) {
|
||||||
|
this.hostPortNo = hostPortNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailAddress() {
|
||||||
|
return emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailAddress(String emailAddress) {
|
||||||
|
this.emailAddress = emailAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailUserName() {
|
||||||
|
return emailUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailUserName(String emailUserName) {
|
||||||
|
this.emailUserName = emailUserName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmailPassword() {
|
||||||
|
return emailPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmailPassword(String emailPassword) {
|
||||||
|
this.emailPassword = emailPassword;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package gr.cite.annotation.common.types.user;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class AdditionalInfoEntity {
|
||||||
|
private String avatarUrl;
|
||||||
|
private String timezone;
|
||||||
|
private String culture;
|
||||||
|
private String language;
|
||||||
|
private String roleOrganization;
|
||||||
|
private UUID organizationId;
|
||||||
|
|
||||||
|
public String getAvatarUrl() {
|
||||||
|
return avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvatarUrl(String avatarUrl) {
|
||||||
|
this.avatarUrl = avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimezone() {
|
||||||
|
return timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimezone(String timezone) {
|
||||||
|
this.timezone = timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCulture() {
|
||||||
|
return culture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCulture(String culture) {
|
||||||
|
this.culture = culture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLanguage(String language) {
|
||||||
|
this.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getOrganizationId() {
|
||||||
|
return organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrganizationId(UUID organizationId) {
|
||||||
|
this.organizationId = organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleOrganization() {
|
||||||
|
return roleOrganization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleOrganization(String roleOrganization) {
|
||||||
|
this.roleOrganization = roleOrganization;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package gr.cite.annotation.common.types.xml;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.transform.OutputKeys;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
|
import javax.xml.transform.dom.DOMSource;
|
||||||
|
import javax.xml.transform.stream.StreamResult;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
|
||||||
|
public class XmlBuilder {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(XmlBuilder.class);
|
||||||
|
|
||||||
|
public static Document getDocument() {
|
||||||
|
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder docBuilder;
|
||||||
|
try {
|
||||||
|
docBuilder = docFactory.newDocumentBuilder();
|
||||||
|
Document doc = docBuilder.newDocument();
|
||||||
|
return doc;
|
||||||
|
} catch (ParserConfigurationException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateXml(Document doc) {
|
||||||
|
TransformerFactory tFact = TransformerFactory.newInstance();
|
||||||
|
Transformer trans;
|
||||||
|
try {
|
||||||
|
trans = tFact.newTransformer();
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
StreamResult result = new StreamResult(writer);
|
||||||
|
DOMSource source = new DOMSource(doc);
|
||||||
|
trans.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||||
|
trans.transform(source, result);
|
||||||
|
return writer.toString();
|
||||||
|
} catch (TransformerException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Document fromXml(String xml) {
|
||||||
|
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder docBuilder;
|
||||||
|
try {
|
||||||
|
docBuilder = docFactory.newDocumentBuilder();
|
||||||
|
InputSource inputStream = new InputSource(new StringReader(xml));
|
||||||
|
Document doc = docBuilder.parse(inputStream);
|
||||||
|
return doc;
|
||||||
|
} catch (ParserConfigurationException | SAXException | IOException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Element getNodeFromListByTagName(NodeList list, String tagName) {
|
||||||
|
for (int temp = 0; temp < list.getLength(); temp++) {
|
||||||
|
Node element = list.item(temp);
|
||||||
|
if (element.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
if (element.getNodeName().equals(tagName)) return (Element) element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package gr.cite.annotation.common.types.xml;
|
||||||
|
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
public interface XmlSerializable<T> {
|
||||||
|
Element toXml(Document doc);
|
||||||
|
|
||||||
|
T fromXml(Element item);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package gr.cite.annotation.common.validation;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
|
||||||
|
import gr.cite.tools.exception.MyValidationException;
|
||||||
|
import gr.cite.tools.validation.AbstractValidator;
|
||||||
|
import gr.cite.tools.validation.ValidationResult;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class BaseValidator<T> extends AbstractValidator<T> {
|
||||||
|
protected final ConventionService conventionService;
|
||||||
|
protected final ErrorThesaurusProperties errors;
|
||||||
|
|
||||||
|
protected BaseValidator(ConventionService conventionService, ErrorThesaurusProperties errors) {
|
||||||
|
this.conventionService = conventionService;
|
||||||
|
this.errors = errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateForce(Object target) {
|
||||||
|
this.validate(target);
|
||||||
|
ValidationResult result = result();
|
||||||
|
if (!result.isValid()) {
|
||||||
|
List<Map.Entry<String, List<String>>> errorsMap = this.flattenValidationResult();
|
||||||
|
throw new MyValidationException(this.errors.getModelValidation().getCode(), errorsMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean isValidGuid(UUID guid) {
|
||||||
|
return this.conventionService.isValidGuid(guid);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean isValidHash(String hash) {
|
||||||
|
return this.conventionService.isValidHash(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean isEmpty(String value) {
|
||||||
|
return this.conventionService.isNullOrEmpty(value);
|
||||||
|
}
|
||||||
|
protected Boolean isListNullOrEmpty(List<?> value) {
|
||||||
|
return this.conventionService.isListNullOrEmpty(value);
|
||||||
|
}
|
||||||
|
protected Boolean isNull(Object value) {
|
||||||
|
return value == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean isNull(Collection<?> value) {
|
||||||
|
return value == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean lessEqualLength(String value, int size) {
|
||||||
|
return value.length() <= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Boolean lessEqual(Integer value, int target) {
|
||||||
|
return value <= target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package gr.cite.annotation.common.validation;
|
||||||
|
|
||||||
|
import gr.cite.annotation.convention.ConventionService;
|
||||||
|
import gr.cite.annotation.errorcode.ErrorThesaurusProperties;
|
||||||
|
import gr.cite.tools.validation.specification.Specification;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Component(UuidValidator.ValidatorName)
|
||||||
|
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||||
|
public class UuidValidator extends BaseValidator<UUID> {
|
||||||
|
|
||||||
|
public static final String ValidatorName = "UuidValidator";
|
||||||
|
|
||||||
|
private final MessageSource messageSource;
|
||||||
|
|
||||||
|
protected UuidValidator(ConventionService conventionService, ErrorThesaurusProperties errors, MessageSource messageSource) {
|
||||||
|
super(conventionService, errors);
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<UUID> modelClass() {
|
||||||
|
return UUID.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Specification> specifications(UUID item) {
|
||||||
|
return Arrays.asList(
|
||||||
|
this.spec()
|
||||||
|
.must(() -> this.isValidGuid(item))
|
||||||
|
.failOn("uuid").failWith(messageSource.getMessage("Validation_Required", new Object[]{"uuid"}, LocaleContextHolder.getLocale()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package gr.cite.annotation.convention;
|
||||||
|
|
||||||
|
import gr.cite.tools.exception.MyApplicationException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public interface ConventionService {
|
||||||
|
Boolean isValidId(Integer id);
|
||||||
|
|
||||||
|
Boolean isValidGuid(UUID guid);
|
||||||
|
|
||||||
|
Boolean isValidUUID(String str);
|
||||||
|
UUID parseUUIDSafe(String str);
|
||||||
|
|
||||||
|
Boolean isValidHash(String hash);
|
||||||
|
|
||||||
|
String hashValue(Object value) throws MyApplicationException;
|
||||||
|
|
||||||
|
String limit(String text, int maxLength);
|
||||||
|
|
||||||
|
String truncate(String text, int maxLength);
|
||||||
|
|
||||||
|
UUID getEmptyUUID();
|
||||||
|
|
||||||
|
boolean isNullOrEmpty(String value);
|
||||||
|
|
||||||
|
boolean isListNullOrEmpty(List value);
|
||||||
|
|
||||||
|
String stringEmpty();
|
||||||
|
|
||||||
|
String asPrefix(String name);
|
||||||
|
|
||||||
|
String asIndexerPrefix(String part);
|
||||||
|
|
||||||
|
String asIndexer(String... names);
|
||||||
|
|
||||||
|
<K, V> Map<K, List<V>> toDictionaryOfList(List<V> items, Function<V, K> keySelector);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue