Compare commits
1323
Commits
1.2.0.CR1
...
1.6.1.Final
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb0519c3a0 | ||
|
|
145ae5a740 | ||
|
|
534a1a336c | ||
|
|
fc5d1bea1d | ||
|
|
91dd570ecc | ||
|
|
e3b3381f07 | ||
|
|
2e6d884153 | ||
|
|
64582ad8fb | ||
|
|
966fba8cd7 | ||
|
|
f29332be96 | ||
|
|
06a9c7e35d | ||
|
|
08a5c790b2 | ||
|
|
538dc2da2c | ||
|
|
6b1287a553 | ||
|
|
256ca2b665 | ||
|
|
e4412523d4 | ||
|
|
073d82b367 | ||
|
|
484c5ebb15 | ||
|
|
4c33704c56 | ||
|
|
511763d4bc | ||
|
|
605c80a68d | ||
|
|
2325143803 | ||
|
|
9cc5ca3ce4 | ||
|
|
d13c120b6c | ||
|
|
9942c7b519 | ||
|
|
4587fd23b6 | ||
|
|
c21b56d596 | ||
|
|
4ee5c32b1e | ||
|
|
6386d95904 | ||
|
|
f99395aebe | ||
|
|
82a7173dba | ||
|
|
2bf6f4b0fd | ||
|
|
d8e90028af | ||
|
|
d2afb4892a | ||
|
|
eb9f69eee0 | ||
|
|
39f985e04a | ||
|
|
41a51d924b | ||
|
|
ce996f6a41 | ||
|
|
244848afc4 | ||
|
|
2910db5595 | ||
|
|
a6556a49c2 | ||
|
|
e4961056db | ||
|
|
c8eee38471 | ||
|
|
1e4b3fc3b6 | ||
|
|
14426f26f2 | ||
|
|
19ad846306 | ||
|
|
235ffb2ff6 | ||
|
|
181fdeb0d0 | ||
|
|
8674578d0d | ||
|
|
9cf4186eb2 | ||
|
|
92ed01a61b | ||
|
|
99028e9cab | ||
|
|
20f18eec15 | ||
|
|
67435791ed | ||
|
|
b4520baee5 | ||
|
|
802a39b1ce | ||
|
|
229b964867 | ||
|
|
b413bcb41d | ||
|
|
0c39e30241 | ||
|
|
11a8d3842e | ||
|
|
2c460c3adc | ||
|
|
aaddaa6c25 | ||
|
|
96099e0798 | ||
|
|
e582de2837 | ||
|
|
e1fc863421 | ||
|
|
7de4051c9b | ||
|
|
5b7b5b1b60 | ||
|
|
f8effaee58 | ||
|
|
bf7d43fffb | ||
|
|
870702fd81 | ||
|
|
2a3e5fa7a4 | ||
|
|
90063cafec | ||
|
|
2b6fcc05e1 | ||
|
|
f585ed5582 | ||
|
|
192387047f | ||
|
|
78664e2571 | ||
|
|
bc71739fb2 | ||
|
|
5563118d79 | ||
|
|
aadd63e2b0 | ||
|
|
59d548228a | ||
|
|
68c3f2f65b | ||
|
|
b54bb25121 | ||
|
|
7f65a2be16 | ||
|
|
b907f8f92c | ||
|
|
3381ed50d1 | ||
|
|
c2c2b835b2 | ||
|
|
f92fe6bea9 | ||
|
|
6aa029784a | ||
|
|
325072c74d | ||
|
|
1e48143db0 | ||
|
|
8c72ef2980 | ||
|
|
e7febbd5a5 | ||
|
|
d9211d6bc9 | ||
|
|
8121a9391f | ||
|
|
65b05fd4e4 | ||
|
|
940c9f8bf1 | ||
|
|
c85e0248dd | ||
|
|
2ec143a3ba | ||
|
|
a279ff3d7f | ||
|
|
128c26043b | ||
|
|
fe9b5a6588 | ||
|
|
0f93e72b02 | ||
|
|
bdba5ff8b1 | ||
|
|
7071161a7c | ||
|
|
672103e363 | ||
|
|
f579d7ffe9 | ||
|
|
d1f0b353e8 | ||
|
|
ef56dca050 | ||
|
|
07c3772b08 | ||
|
|
85a886da18 | ||
|
|
4ce93171ba | ||
|
|
c987d57b9e | ||
|
|
a416327124 | ||
|
|
285ef50341 | ||
|
|
0d22074826 | ||
|
|
b76fb68ff7 | ||
|
|
f03128f5ed | ||
|
|
8fb05bacd1 | ||
|
|
f8842b3df1 | ||
|
|
b376d1fb6d | ||
|
|
d4e0901599 | ||
|
|
217c359530 | ||
|
|
4fbf6841be | ||
|
|
2deb20a2c9 | ||
|
|
20b92c410f | ||
|
|
1d829a8365 | ||
|
|
10b4bd24a4 | ||
|
|
788501f13d | ||
|
|
345bea50b6 | ||
|
|
ab7bfa6d90 | ||
|
|
4f7979d9fc | ||
|
|
286458b94a | ||
|
|
61a8c422df | ||
|
|
baaf445727 | ||
|
|
2faf0eccdb | ||
|
|
639b5b617b | ||
|
|
115663a32d | ||
|
|
b277afa6d9 | ||
|
|
960cd8c822 | ||
|
|
4acc69c604 | ||
|
|
0230e008c5 | ||
|
|
382b8d60d0 | ||
|
|
0ad29c9737 | ||
|
|
9ced56d8d7 | ||
|
|
eb197c46f7 | ||
|
|
63947cd37a | ||
|
|
938ce9612c | ||
|
|
d6119c04d6 | ||
|
|
14646781ad | ||
|
|
1b41832946 | ||
|
|
57d0bac96a | ||
|
|
2ca3ce2dba | ||
|
|
e5d404fdb3 | ||
|
|
8829fe8658 | ||
|
|
9ccb0b1f65 | ||
|
|
7bb70f2d00 | ||
|
|
3ab7edbbcd | ||
|
|
d4060cce92 | ||
|
|
1342904fae | ||
|
|
fc0a3c3e73 | ||
|
|
756a9f095c | ||
|
|
021e48d6d6 | ||
|
|
91f78c61b7 | ||
|
|
d39aee0a72 | ||
|
|
7c600e2f4b | ||
|
|
14079b950a | ||
|
|
173c112f8e | ||
|
|
67d8ca0207 | ||
|
|
e690f4e645 | ||
|
|
4c554b4af6 | ||
|
|
3aee5eeecf | ||
|
|
366a1629e5 | ||
|
|
7fec1677ee | ||
|
|
8e391ffa4e | ||
|
|
d65ce5d1d4 | ||
|
|
6741dffe38 | ||
|
|
894af03323 | ||
|
|
80dea9c0bd | ||
|
|
0b83325470 | ||
|
|
368ea64b51 | ||
|
|
450e031da9 | ||
|
|
1796a45a68 | ||
|
|
c9437595b7 | ||
|
|
a1b315adbd | ||
|
|
1781588597 | ||
|
|
f29aff4bed | ||
|
|
ac91839e69 | ||
|
|
9c0c8e37b6 | ||
|
|
d40237b08d | ||
|
|
59527e12f0 | ||
|
|
6d0483667f | ||
|
|
dcf8747d7e | ||
|
|
7a3b4823b0 | ||
|
|
64ecc3b153 | ||
|
|
cc41a1fd78 | ||
|
|
255b1cb3bf | ||
|
|
a1bb202bd4 | ||
|
|
332e3f6099 | ||
|
|
6b41d79058 | ||
|
|
3b4b5c11e6 | ||
|
|
0c9e764e11 | ||
|
|
00cbc22d8b | ||
|
|
0a3de09b5a | ||
|
|
4e0ad5ac0b | ||
|
|
72256fd6ac | ||
|
|
d06237a3fd | ||
|
|
28fad68486 | ||
|
|
53b86cbace | ||
|
|
a53aebcddf | ||
|
|
af307c695a | ||
|
|
7816f053a6 | ||
|
|
ed54ba9064 | ||
|
|
8acdaf462b | ||
|
|
c5efd34a46 | ||
|
|
75343986b0 | ||
|
|
424d357c9c | ||
|
|
56116038d6 | ||
|
|
c76da8f3b8 | ||
|
|
589eb3206e | ||
|
|
5419ec32e4 | ||
|
|
c9e0a7a97d | ||
|
|
5b5f99fdfe | ||
|
|
d3bd0a67a7 | ||
|
|
2a36185419 | ||
|
|
76e3c6394c | ||
|
|
9e15b20591 | ||
|
|
05bd51ac1c | ||
|
|
d648ba73de | ||
|
|
c6b34f32a1 | ||
|
|
974c0a50e7 | ||
|
|
07d3b26722 | ||
|
|
510b03476f | ||
|
|
4f6d3c8dca | ||
|
|
95fe2fe74b | ||
|
|
e937216cf7 | ||
|
|
fd8d93663e | ||
|
|
a048cfc102 | ||
|
|
272272634d | ||
|
|
01947f46b9 | ||
|
|
5ec73a81e9 | ||
|
|
4cba764ac3 | ||
|
|
84b9963609 | ||
|
|
99c216635f | ||
|
|
25a6fcc382 | ||
|
|
acefd3cacd | ||
|
|
15a5c87cc5 | ||
|
|
3044138e10 | ||
|
|
6fbb8ccf71 | ||
|
|
018866aa81 | ||
|
|
18b2b572ac | ||
|
|
d9e67aa5ee | ||
|
|
e675ce44d5 | ||
|
|
df46a8ea0d | ||
|
|
e277d5b55e | ||
|
|
4bd1d899db | ||
|
|
3e3d92c2e4 | ||
|
|
55deedd3b8 | ||
|
|
8b6251eb23 | ||
|
|
35919da0a4 | ||
|
|
ec018d2739 | ||
|
|
b1a23a13d6 | ||
|
|
3ebbe96786 | ||
|
|
cfaa4c60c9 | ||
|
|
e104450fc8 | ||
|
|
c22b0f4da8 | ||
|
|
302d0b58cc | ||
|
|
72999f0acb | ||
|
|
a942322d99 | ||
|
|
8902502c4d | ||
|
|
8d19741f53 | ||
|
|
e60549d764 | ||
|
|
0a42a28eeb | ||
|
|
88355d7eb4 | ||
|
|
67db5917ac | ||
|
|
8f1daf5d77 | ||
|
|
6d5e3b2a17 | ||
|
|
75c0d5089f | ||
|
|
5b1fcbfc51 | ||
|
|
0c80255f3a | ||
|
|
121f8e9174 | ||
|
|
c1cb50b39f | ||
|
|
791a740f32 | ||
|
|
546cdd7d8f | ||
|
|
8ef6602f82 | ||
|
|
2938759db4 | ||
|
|
f7bcca8553 | ||
|
|
4eaf893492 | ||
|
|
0ba6ab198a | ||
|
|
49a530f2b4 | ||
|
|
c14d3d7963 | ||
|
|
fe26d6ecec | ||
|
|
35a00b449d | ||
|
|
1e9c09d23a | ||
|
|
ec4f8a7f15 | ||
|
|
d109bf7b56 | ||
|
|
4625ab917d | ||
|
|
7d239f8990 | ||
|
|
22f7efd977 | ||
|
|
2a349673b9 | ||
|
|
361019fc6a | ||
|
|
abb099745f | ||
|
|
046edbbd54 | ||
|
|
8cc4c8c5e2 | ||
|
|
d2d3a3f216 | ||
|
|
cbf5c920ea | ||
|
|
7ac1d1f14c | ||
|
|
e811b87679 | ||
|
|
d08fca4e0a | ||
|
|
30aa41a2ee | ||
|
|
9d8d690100 | ||
|
|
43541d3028 | ||
|
|
880e831e71 | ||
|
|
fe2278b320 | ||
|
|
f7a8bbbcc8 | ||
|
|
fde4bc99aa | ||
|
|
cc9d6d0cf7 | ||
|
|
b17bbc9b86 | ||
|
|
d4c5349e1a | ||
|
|
2865a3c047 | ||
|
|
522fc7412c | ||
|
|
7ec3f86efb | ||
|
|
c11539cccb | ||
|
|
09488b8df3 | ||
|
|
e16cc493d2 | ||
|
|
379fc8961e | ||
|
|
43114f176b | ||
|
|
f01d959f7c | ||
|
|
28f4409768 | ||
|
|
364aecda2a | ||
|
|
9bfbac6be5 | ||
|
|
db20fd9eeb | ||
|
|
53a198fde8 | ||
|
|
861a13501a | ||
|
|
0f24bd2ea4 | ||
|
|
9dc54815eb | ||
|
|
cb8ca619ae | ||
|
|
ac91deac96 | ||
|
|
71e2be5d3d | ||
|
|
4878073867 | ||
|
|
dc327ddddb | ||
|
|
e6467403ef | ||
|
|
cf0fab2d66 | ||
|
|
2eaa03539c | ||
|
|
b7e49dc88d | ||
|
|
ca05455aa0 | ||
|
|
7ae3eb8321 | ||
|
|
ae54babbbb | ||
|
|
a8378fe84b | ||
|
|
85df0b6a67 | ||
|
|
95967b9c79 | ||
|
|
8396b999a2 | ||
|
|
3bbe82057c | ||
|
|
0c49fce78f | ||
|
|
f9af8ba1a0 | ||
|
|
80f91b5fa5 | ||
|
|
6478e8f5c7 | ||
|
|
55dc42e147 | ||
|
|
f55ab0d689 | ||
|
|
fe79c9b01e | ||
|
|
6291e90f9c | ||
|
|
a32fefb597 | ||
|
|
3e04d045ee | ||
|
|
fb4645a3dc | ||
|
|
e0eac89e5a | ||
|
|
90d34bfd06 | ||
|
|
e58b5762f3 | ||
|
|
3fd4d23bed | ||
|
|
1c38bb7158 | ||
|
|
149ef706dd | ||
|
|
75bd8d4627 | ||
|
|
f1c40dd345 | ||
|
|
6225ccfc06 | ||
|
|
34de631b2a | ||
|
|
86f0092622 | ||
|
|
e455cbf319 | ||
|
|
cee2d69718 | ||
|
|
ff17123123 | ||
|
|
e118a06a09 | ||
|
|
4898d74c6d | ||
|
|
76f3842dad | ||
|
|
d818ebdd9e | ||
|
|
4d90898dd9 | ||
|
|
298b9e01d8 | ||
|
|
d0b08cacd8 | ||
|
|
050c65a520 | ||
|
|
be0c359160 | ||
|
|
3f792030d3 | ||
|
|
5c2fc1120b | ||
|
|
3d2acbe780 | ||
|
|
bce0e0ea90 | ||
|
|
931102ff26 | ||
|
|
5a3938d12f | ||
|
|
dca1751a63 | ||
|
|
f6ec9af61e | ||
|
|
64f2f84ffa | ||
|
|
5df5e53f11 | ||
|
|
81f4c50574 | ||
|
|
2550d16127 | ||
|
|
333ad0efac | ||
|
|
f3675681c3 | ||
|
|
28de8dc5b7 | ||
|
|
35e63a9398 | ||
|
|
f23f1cc813 | ||
|
|
b0095154d1 | ||
|
|
79ea9ed314 | ||
|
|
413db0dce1 | ||
|
|
e6745532ce | ||
|
|
59da0f90f8 | ||
|
|
76f0f52ee3 | ||
|
|
73c230f1ca | ||
|
|
ddee72c756 | ||
|
|
6318964374 | ||
|
|
853f1af40d | ||
|
|
d429b2151a | ||
|
|
b223e1a7af | ||
|
|
c806b8911e | ||
|
|
9f30a51925 | ||
|
|
a8b7129269 | ||
|
|
991b0e8ee2 | ||
|
|
e9d1475475 | ||
|
|
bc189554aa | ||
|
|
d0f26ae4cf | ||
|
|
ab6d247ef1 | ||
|
|
fcc82a47e4 | ||
|
|
25e53db96f | ||
|
|
89660d06e5 | ||
|
|
493fd0ad6a | ||
|
|
11422524f3 | ||
|
|
957214ed78 | ||
|
|
f013cca3b8 | ||
|
|
a1cfc0a0ff | ||
|
|
e8b3b3acf3 | ||
|
|
0cfb03be03 | ||
|
|
7d4b93e01e | ||
|
|
2f3793805b | ||
|
|
a310c13d32 | ||
|
|
38a721425d | ||
|
|
dfc34d99c5 | ||
|
|
6c58fd03a4 | ||
|
|
5b9d1286cc | ||
|
|
d9e330802c | ||
|
|
085de8a069 | ||
|
|
5af4c63a3e | ||
|
|
5177365c28 | ||
|
|
4df5ff3004 | ||
|
|
00f6841dc7 | ||
|
|
be8394158f | ||
|
|
fbf62b2a63 | ||
|
|
a047fb947b | ||
|
|
eec3a3d5c3 | ||
|
|
9387de1d1a | ||
|
|
b9c90f128a | ||
|
|
5699efc0f5 | ||
|
|
6ead008bf4 | ||
|
|
34e52839f7 | ||
|
|
bd54e1c60c | ||
|
|
03006522e2 | ||
|
|
7492ae2990 | ||
|
|
6edf890699 | ||
|
|
43fd24f02b | ||
|
|
22ebb81650 | ||
|
|
fd074278f7 | ||
|
|
ad497a66cd | ||
|
|
41275b0e91 | ||
|
|
b82502af1d | ||
|
|
43f2199d46 | ||
|
|
d44541fc9e | ||
|
|
24147a3d84 | ||
|
|
244d879d00 | ||
|
|
f5f67a629e | ||
|
|
aad1d57502 | ||
|
|
cf75c2378f | ||
|
|
3a64c4d582 | ||
|
|
d36376ffcd | ||
|
|
434197c10d | ||
|
|
d4af694c6f | ||
|
|
f245b67036 | ||
|
|
945673c7da | ||
|
|
c25967bd61 | ||
|
|
165fba6b9c | ||
|
|
76209dd899 | ||
|
|
8c8cc53989 | ||
|
|
6f463196ef | ||
|
|
b0e2624343 | ||
|
|
99fda3a7cb | ||
|
|
457039d7ba | ||
|
|
eb4ffbca29 | ||
|
|
c9afe8fcc4 | ||
|
|
d8d6348f67 | ||
|
|
87f7ec5909 | ||
|
|
18583eb6ed | ||
|
|
b9d0219f04 | ||
|
|
d9cb1fc4a9 | ||
|
|
1654be0a85 | ||
|
|
d5944163d6 | ||
|
|
5ca3a48094 | ||
|
|
1fb3d7cd3b | ||
|
|
f827b2dc0d | ||
|
|
0295824923 | ||
|
|
8fee0c946b | ||
|
|
bd13b8a5fe | ||
|
|
743ea95a78 | ||
|
|
2998a0fa35 | ||
|
|
7b0a3989e2 | ||
|
|
06fcb7595a | ||
|
|
bd3c4d4215 | ||
|
|
b969d9edfa | ||
|
|
3ef0fb63a9 | ||
|
|
bf706a9191 | ||
|
|
26be615a80 | ||
|
|
81928eb61a | ||
|
|
126f517520 | ||
|
|
7028496601 | ||
|
|
3a4897c7d2 | ||
|
|
b3f142d715 | ||
|
|
a4e16ca9c7 | ||
|
|
57176dee75 | ||
|
|
f87ba8a3a9 | ||
|
|
8c0b33f902 | ||
|
|
51e927adf7 | ||
|
|
bf4b681506 | ||
|
|
3c88d2cc18 | ||
|
|
c7b5975ac1 | ||
|
|
09901997c1 | ||
|
|
c0f3d851db | ||
|
|
98b6344f03 | ||
|
|
e4b6e4c167 | ||
|
|
d6983e829a | ||
|
|
374a2ad957 | ||
|
|
52fd2d7e75 | ||
|
|
25bb0ade1b | ||
|
|
6d7be80930 | ||
|
|
1f13f6372a | ||
|
|
39fcd84d53 | ||
|
|
d11a83d6e2 | ||
|
|
1978e23cfd | ||
|
|
4cc1a774df | ||
|
|
8a23463328 | ||
|
|
fad2913665 | ||
|
|
c4c27973f3 | ||
|
|
2ae733bd07 | ||
|
|
7665b9f81e | ||
|
|
248322c748 | ||
|
|
523bdd5ec9 | ||
|
|
a8ad00156b | ||
|
|
8f820713fa | ||
|
|
fc7d4b072c | ||
|
|
1801526f65 | ||
|
|
58e37b6a3f | ||
|
|
0bd9cad6ad | ||
|
|
bcc2c893ef | ||
|
|
ac71bbd4b5 | ||
|
|
ae94b30066 | ||
|
|
b3c307affe | ||
|
|
03d3f4229e | ||
|
|
c627b83cc5 | ||
|
|
5469db311d | ||
|
|
75793a36cf | ||
|
|
d68603e4ea | ||
|
|
33e402e7be | ||
|
|
375dce14e6 | ||
|
|
08ae3aee50 | ||
|
|
1ad7d4f1d8 | ||
|
|
f9e8f1f7b7 | ||
|
|
fa0b45625c | ||
|
|
c5459b2614 | ||
|
|
8232773d97 | ||
|
|
53b752ccfe | ||
|
|
07efba364e | ||
|
|
2e52d8ea7a | ||
|
|
a46a7a42a1 | ||
|
|
060a193b48 | ||
|
|
06ee06ae50 | ||
|
|
17a5a9f033 | ||
|
|
e27a754f5f | ||
|
|
61d5ba6a92 | ||
|
|
eff0054b4a | ||
|
|
99cd6c7383 | ||
|
|
5087392162 | ||
|
|
7474a31d95 | ||
|
|
a0197bc9da | ||
|
|
3b2e679aed | ||
|
|
75d69a1ec1 | ||
|
|
930fc66ebf | ||
|
|
04d3d26cb1 | ||
|
|
de940ccff9 | ||
|
|
aedc54cc89 | ||
|
|
de3a776b9a | ||
|
|
5d6cc61d4d | ||
|
|
f32b38cdbc | ||
|
|
f3bfb06dec | ||
|
|
630597746e | ||
|
|
f98036296c | ||
|
|
bf1f867f23 | ||
|
|
8215a85930 | ||
|
|
10a5686955 | ||
|
|
2e3c7a3acf | ||
|
|
06713da67b | ||
|
|
46fb1b6582 | ||
|
|
1b3f98db1e | ||
|
|
a190cdd8f2 | ||
|
|
8e25369001 | ||
|
|
8d90ad816a | ||
|
|
da9e42cb21 | ||
|
|
cbab3ff0d0 | ||
|
|
210b570c89 | ||
|
|
a2203df6a3 | ||
|
|
6a2b369014 | ||
|
|
fda6a6657f | ||
|
|
04565dad00 | ||
|
|
ac0ae06463 | ||
|
|
a8775f757f | ||
|
|
e4f4ed923d | ||
|
|
c7915fa95d | ||
|
|
652b118634 | ||
|
|
f122ff1471 | ||
|
|
f76b90fab9 | ||
|
|
ccd8beae92 | ||
|
|
33f01009d2 | ||
|
|
a0e3a71109 | ||
|
|
f35ecba801 | ||
|
|
0d7f26bd14 | ||
|
|
2f7bdd2640 | ||
|
|
3125e4ddd2 | ||
|
|
c624cb27da | ||
|
|
06adf9a094 | ||
|
|
94ed55ee35 | ||
|
|
95c8603b69 | ||
|
|
9ad2a8272e | ||
|
|
6dc17da00a | ||
|
|
97aa1c6b2c | ||
|
|
084fed8377 | ||
|
|
68d86e4fa1 | ||
|
|
5203373047 | ||
|
|
86de481ba1 | ||
|
|
5d075a97dd | ||
|
|
e89499f5db | ||
|
|
a1f0208767 | ||
|
|
d1964c20ab | ||
|
|
1e035bf2e5 | ||
|
|
f3b87f9cf9 | ||
|
|
d2ea45e1eb | ||
|
|
58ad6b1a40 | ||
|
|
772ef3cd10 | ||
|
|
204dfd52c8 | ||
|
|
244d3452d0 | ||
|
|
e65f8a7150 | ||
|
|
90d6991b9d | ||
|
|
87ca3f203a | ||
|
|
3f3a20e178 | ||
|
|
ce1a19fdbe | ||
|
|
26ac92a6b1 | ||
|
|
1c7dd0a533 | ||
|
|
ceb29d76b2 | ||
|
|
6efb354cce | ||
|
|
8ae242a6ed | ||
|
|
5f0f92fd15 | ||
|
|
e521fa3554 | ||
|
|
b8f73cf21f | ||
|
|
d068cddbaf | ||
|
|
99422835ab | ||
|
|
68263360c7 | ||
|
|
34f5ddab0f | ||
|
|
0cc4630fc6 | ||
|
|
ee57c79784 | ||
|
|
7e41b8df27 | ||
|
|
79c1d7bafa | ||
|
|
652b2fee86 | ||
|
|
dfb871c26a | ||
|
|
99f74acb94 | ||
|
|
83f7682606 | ||
|
|
c4a07f3098 | ||
|
|
c9451f4f35 | ||
|
|
dff4922783 | ||
|
|
c05b666f3c | ||
|
|
9905024f08 | ||
|
|
b0cb457287 | ||
|
|
d4946c1c08 | ||
|
|
1d745b5cbd | ||
|
|
c99785f266 | ||
|
|
9ecdec3f45 | ||
|
|
866f45bd95 | ||
|
|
320a79dbff | ||
|
|
1e0b6f1aad | ||
|
|
251ad6727a | ||
|
|
1b1f56d183 | ||
|
|
7dc05a45ac | ||
|
|
7257bab289 | ||
|
|
48a76c2d0d | ||
|
|
d78c83b236 | ||
|
|
d9b0415047 | ||
|
|
af2e3de96d | ||
|
|
1c142cb170 | ||
|
|
d5dae7d444 | ||
|
|
85816be967 | ||
|
|
e66d658955 | ||
|
|
1916fded95 | ||
|
|
8cc8f4c49c | ||
|
|
1a3bf16729 | ||
|
|
5304b533fc | ||
|
|
0156d47d97 | ||
|
|
b04ac6cdba | ||
|
|
f43e4200da | ||
|
|
2ba7aef2b1 | ||
|
|
7a1932cab4 | ||
|
|
59f189229c | ||
|
|
29af7a1d29 | ||
|
|
3d9ddc53e6 | ||
|
|
1a34feda65 | ||
|
|
d8c1081578 | ||
|
|
1085a1da62 | ||
|
|
576db8e0e1 | ||
|
|
fe9dc4a28d | ||
|
|
d2aaae2340 | ||
|
|
84e40cbc2c | ||
|
|
42672a9ae2 | ||
|
|
2dd2173553 | ||
|
|
fcd5400cda | ||
|
|
23b69bb7a7 | ||
|
|
c31b0945d8 | ||
|
|
c71a4ac4e8 | ||
|
|
38c7ca64cb | ||
|
|
bc187fe7b3 | ||
|
|
548c0db0ae | ||
|
|
3ecd1307b0 | ||
|
|
b51661b790 | ||
|
|
02d6e41ce3 | ||
|
|
f884aed25e | ||
|
|
f2dee1a470 | ||
|
|
d5d806760f | ||
|
|
efe2a8f789 | ||
|
|
69bbed999f | ||
|
|
5b7d4184f0 | ||
|
|
422b7ed5ef | ||
|
|
66a2b916f2 | ||
|
|
27ac253098 | ||
|
|
1839b24b90 | ||
|
|
a60ebb6210 | ||
|
|
4dba4f3c02 | ||
|
|
6727af2f35 | ||
|
|
5bcf334bc5 | ||
|
|
ef49bd685e | ||
|
|
092562fb94 | ||
|
|
44e2983803 | ||
|
|
2e848da1a8 | ||
|
|
22ae4ecb23 | ||
|
|
1642ac2394 | ||
|
|
be8aa93cca | ||
|
|
fc7ba85639 | ||
|
|
11d1dd6419 | ||
|
|
e8bd7270e9 | ||
|
|
99e2afa81b | ||
|
|
676792d07d | ||
|
|
0bec2bcd1e | ||
|
|
25328048aa | ||
|
|
e51d2137cf | ||
|
|
5695677873 | ||
|
|
9d86f582ac | ||
|
|
97b0558f71 | ||
|
|
e825be1c79 | ||
|
|
88252c3dfc | ||
|
|
02997b593f | ||
|
|
9de81a0b14 | ||
|
|
ab13182091 | ||
|
|
e75e4580ae | ||
|
|
b7f031d199 | ||
|
|
808d27be2d | ||
|
|
dcf8751ff7 | ||
|
|
ec6e3d80ee | ||
|
|
5a9badf683 | ||
|
|
dd11dcef93 | ||
|
|
e9851957f0 | ||
|
|
57cfbb3770 | ||
|
|
da2590819d | ||
|
|
d74d93a522 | ||
|
|
b3eadcc990 | ||
|
|
f4d4405c20 | ||
|
|
1ebec0e2e5 | ||
|
|
4433347ff3 | ||
|
|
08c539ba51 | ||
|
|
4e538dd138 | ||
|
|
66373ccab2 | ||
|
|
9c82c0700d | ||
|
|
a480e50c16 | ||
|
|
4391bf4f70 | ||
|
|
18f46cb527 | ||
|
|
32b89adc04 | ||
|
|
5a213fcad9 | ||
|
|
d6f1d1bb3c | ||
|
|
c8c0d0e804 | ||
|
|
aeddf2a860 | ||
|
|
b876a5f7bd | ||
|
|
af8949c291 | ||
|
|
e3da38df7c | ||
|
|
a16079e38d | ||
|
|
e13b0fac63 | ||
|
|
26190350c4 | ||
|
|
7598cc62d3 | ||
|
|
b3211fa8c2 | ||
|
|
8dcc909b6e | ||
|
|
fafaf2c490 | ||
|
|
11e2f19503 | ||
|
|
d67bd0eff2 | ||
|
|
43bbea9a7c | ||
|
|
dc366c53a7 | ||
|
|
fe1ede26be | ||
|
|
aca799b28c | ||
|
|
1017a0af1d | ||
|
|
ff25484a34 | ||
|
|
af922920ab | ||
|
|
919a3f1b91 | ||
|
|
1ec8e63e9e | ||
|
|
96c76a059a | ||
|
|
608701aea7 | ||
|
|
142d44a01f | ||
|
|
83c0f537ce | ||
|
|
aeab25b7fe | ||
|
|
ef255c6a5c | ||
|
|
474ebed0ee | ||
|
|
6e55604dc3 | ||
|
|
2dfdfefb1a | ||
|
|
48f78cf80a | ||
|
|
69cc28dd4b | ||
|
|
adb8bcf791 | ||
|
|
bd22d30674 | ||
|
|
afbf1cc33f | ||
|
|
485df59c5b | ||
|
|
43308b326d | ||
|
|
6c9708c700 | ||
|
|
4a962aa75a | ||
|
|
e786158071 | ||
|
|
ea5a767948 | ||
|
|
bde65a6c57 | ||
|
|
94c0a436b5 | ||
|
|
bddf1adeba | ||
|
|
9406a577c2 | ||
|
|
40344726e1 | ||
|
|
08ffea23b7 | ||
|
|
f1ddaa8051 | ||
|
|
7ae62c1060 | ||
|
|
7c51046d8a | ||
|
|
df6d75e2c5 | ||
|
|
3c88d9666d | ||
|
|
ee74025f38 | ||
|
|
d49c7d47a2 | ||
|
|
8f18e9bbbc | ||
|
|
be96a3785b | ||
|
|
f3aeb5ec9f | ||
|
|
71ea61e7a6 | ||
|
|
c6c73e5e59 | ||
|
|
ae242f8480 | ||
|
|
351a17f68d | ||
|
|
1c4f7cb339 | ||
|
|
2713af189f | ||
|
|
be62d1a915 | ||
|
|
68d71a6767 | ||
|
|
46c88ad1bf | ||
|
|
917151123f | ||
|
|
b9e9d6a787 | ||
|
|
db20077c37 | ||
|
|
ec3d027f90 | ||
|
|
50678085c9 | ||
|
|
5af4785ba1 | ||
|
|
f59d42d5d3 | ||
|
|
fc7ae1ee96 | ||
|
|
2699bb9a44 | ||
|
|
212f5b4082 | ||
|
|
9591f48077 | ||
|
|
0a8494a0f2 | ||
|
|
39f2a4ab71 | ||
|
|
39aa09ca36 | ||
|
|
45c5a2ae78 | ||
|
|
a1c612f833 | ||
|
|
bc39bb32d4 | ||
|
|
884308ecb5 | ||
|
|
2d7f232c3d | ||
|
|
afa65d9ead | ||
|
|
605c88a029 | ||
|
|
09994d1730 | ||
|
|
bb9c99ebf6 | ||
|
|
d4819886a6 | ||
|
|
23445123a2 | ||
|
|
773bb43b41 | ||
|
|
493dddde02 | ||
|
|
2e5704c6b4 | ||
|
|
be6c843bba | ||
|
|
064d677fdc | ||
|
|
084ab782da | ||
|
|
1a10a1ef75 | ||
|
|
acc77e4e6c | ||
|
|
2c0ff9be60 | ||
|
|
b9f5946cf0 | ||
|
|
c403968fa8 | ||
|
|
14baef425e | ||
|
|
8da532da93 | ||
|
|
facf701897 | ||
|
|
f9fe9ea5d6 | ||
|
|
f5a80c3007 | ||
|
|
7cff37e707 | ||
|
|
4e8c6b3fc4 | ||
|
|
82282c1c75 | ||
|
|
d7379f29da | ||
|
|
000159226d | ||
|
|
4ebdb8d24b | ||
|
|
fc2df08e15 | ||
|
|
aaa83dc19a | ||
|
|
adff0d5da0 | ||
|
|
285d6931bc | ||
|
|
114b687c5c | ||
|
|
a26ade3988 | ||
|
|
94e2983310 | ||
|
|
fb2f5f6756 | ||
|
|
d7b818cba2 | ||
|
|
8d885d14ed | ||
|
|
39e0023a5f | ||
|
|
84faac0cd6 | ||
|
|
1bce639d35 | ||
|
|
f2061610b9 | ||
|
|
8716f9b41a | ||
|
|
a24c817bb7 | ||
|
|
47997e0056 | ||
|
|
70fd7bea99 | ||
|
|
52565375b3 | ||
|
|
2c327d9eee | ||
|
|
caeb710444 | ||
|
|
0b0b101184 | ||
|
|
f5b4a0788e | ||
|
|
1aa338fe3e | ||
|
|
5615d8d870 | ||
|
|
9711ccd914 | ||
|
|
fbc2b845ad | ||
|
|
39ecca0989 | ||
|
|
f19b6afd85 | ||
|
|
dca347398d | ||
|
|
de6341bd87 | ||
|
|
5cb9dbb87a | ||
|
|
dddc5181e7 | ||
|
|
3b78fa2d5d | ||
|
|
2a64f5e3ce | ||
|
|
8f5b53e3ce | ||
|
|
605f5decb3 | ||
|
|
d00e128920 | ||
|
|
226c1c53c0 | ||
|
|
001b874903 | ||
|
|
cc523ea356 | ||
|
|
5dc2b7b1d5 | ||
|
|
cd84e78b27 | ||
|
|
dedaa24972 | ||
|
|
1b0c4e5d1f | ||
|
|
b7e3fc5f73 | ||
|
|
9b9ec09788 | ||
|
|
bd7ceb0b73 | ||
|
|
9638c0dd83 | ||
|
|
60f40544bb | ||
|
|
066fb7178d | ||
|
|
cd762e5e8c | ||
|
|
f990d68cee | ||
|
|
88f14b39a7 | ||
|
|
eda64584d7 | ||
|
|
2f2a2dced6 | ||
|
|
38a2ea2fdf | ||
|
|
43bad9a608 | ||
|
|
be5f3086ce | ||
|
|
663f12d5e6 | ||
|
|
6e758d721d | ||
|
|
4c98b04ab7 | ||
|
|
f846645989 | ||
|
|
6bc79b590c | ||
|
|
2ad7e870d8 | ||
|
|
87636669ae | ||
|
|
8d627f1329 | ||
|
|
c51cc4703b | ||
|
|
a1124c9e1f | ||
|
|
20e245cb05 | ||
|
|
5c8e165080 | ||
|
|
afe088a9e8 | ||
|
|
162f31baa2 | ||
|
|
28a50df746 | ||
|
|
5a0d600322 | ||
|
|
38c1945ce4 | ||
|
|
939527bd98 | ||
|
|
cc2de52a1a | ||
|
|
4ccc37abbb | ||
|
|
47d1681e90 | ||
|
|
c89a412661 | ||
|
|
5bd53804f9 | ||
|
|
0d957311b4 | ||
|
|
c2495acca9 | ||
|
|
c297b29a13 | ||
|
|
112ebf77d5 | ||
|
|
5993d40fab | ||
|
|
2d82d15e5b | ||
|
|
656b157103 | ||
|
|
392fa21f1e | ||
|
|
fa99b5415d | ||
|
|
514417ac2d | ||
|
|
938cfe2555 | ||
|
|
d6e64a2c5e | ||
|
|
c52d16da6d | ||
|
|
3f62cd9271 | ||
|
|
df7cb4a9d0 | ||
|
|
9a37696d29 | ||
|
|
20fe7ec7b8 | ||
|
|
37cef49565 | ||
|
|
b5ee395aac | ||
|
|
7b89b80f71 | ||
|
|
08f2b0dea9 | ||
|
|
92e17f4b40 | ||
|
|
b8aedd5a3a | ||
|
|
d3e178d459 | ||
|
|
996d8e8cd3 | ||
|
|
0e1a059d8d | ||
|
|
30359b51fa | ||
|
|
d8880e5ff3 | ||
|
|
6819b8e3a5 | ||
|
|
03b521926a | ||
|
|
abfec23404 | ||
|
|
3dd282e11b | ||
|
|
92efe8441e | ||
|
|
95349e6e2e | ||
|
|
15f8e83d4a | ||
|
|
0095968a73 | ||
|
|
ad01d64803 | ||
|
|
30405804bc | ||
|
|
6093d00fdd | ||
|
|
1ba5ea411b | ||
|
|
9dd7a947e9 | ||
|
|
c51e6a916c | ||
|
|
e9b9da6d20 | ||
|
|
557edbec6d | ||
|
|
93650c442a | ||
|
|
d3e9b29d78 | ||
|
|
7cf1b002d2 | ||
|
|
e96ba202e9 | ||
|
|
9a5ca4d367 | ||
|
|
e6928c6456 | ||
|
|
245edcb5b9 | ||
|
|
e4cb97aa49 | ||
|
|
bc93066fe3 | ||
|
|
bf686bd791 | ||
|
|
01e232158b | ||
|
|
c6e0195a3c | ||
|
|
966d939c12 | ||
|
|
c61c05be51 | ||
|
|
7b099e3d6b | ||
|
|
6d257a0d4c | ||
|
|
b80a4a0aa9 | ||
|
|
aabb3428a8 | ||
|
|
80ff7b92db | ||
|
|
7badd3d5e5 | ||
|
|
e977a363ef | ||
|
|
91283878ac | ||
|
|
74ace4006c | ||
|
|
7be957f4f9 | ||
|
|
3643e76a06 | ||
|
|
07bc756785 | ||
|
|
0af68d28f1 | ||
|
|
75e1f50faf | ||
|
|
dcc40b0a63 | ||
|
|
dc0d9a8dce | ||
|
|
021b01f0bd | ||
|
|
b578122f19 | ||
|
|
e014dcce76 | ||
|
|
a1bdf2837e | ||
|
|
0092d9f74d | ||
|
|
e48aafd588 | ||
|
|
d958d85290 | ||
|
|
5b40031251 | ||
|
|
399a4a0511 | ||
|
|
bebe51c885 | ||
|
|
6812514683 | ||
|
|
62de481329 | ||
|
|
c3eb6df220 | ||
|
|
d875b9885d | ||
|
|
9ab023cc6c | ||
|
|
3510e17dd3 | ||
|
|
f50570e350 | ||
|
|
a52cdae1f7 | ||
|
|
826c60ae36 | ||
|
|
d2c9d1569c | ||
|
|
15dca61c59 | ||
|
|
4224496611 | ||
|
|
7467fbaa12 | ||
|
|
580e4fb0b2 | ||
|
|
90b15a070c | ||
|
|
b54061fc3f | ||
|
|
ce7de3b576 | ||
|
|
31d0365e85 | ||
|
|
084da8a03d | ||
|
|
e395ecb749 | ||
|
|
a34cb92fc1 | ||
|
|
c12fe28b2d | ||
|
|
0ac000adca | ||
|
|
1dd0b563b6 | ||
|
|
26acf6fa83 | ||
|
|
dcc19a39d7 | ||
|
|
496062ef0c | ||
|
|
b27f18c380 | ||
|
|
442d65bb5f | ||
|
|
35e4a5e56d | ||
|
|
fb60ca115f | ||
|
|
3d3871b0e6 | ||
|
|
060a59f982 | ||
|
|
c6c0abbedf | ||
|
|
df182c3301 | ||
|
|
56c858d917 | ||
|
|
00068c460a | ||
|
|
5d0b6400df | ||
|
|
413295e15e | ||
|
|
1c3d165e5d | ||
|
|
5fe0406ec9 | ||
|
|
17affe576f | ||
|
|
b86d091ad1 | ||
|
|
ea544c639e | ||
|
|
492e6cd856 | ||
|
|
5680be743c | ||
|
|
1fd22ee672 | ||
|
|
c3faea0428 | ||
|
|
483ac69c47 | ||
|
|
805ad9f250 | ||
|
|
43799fd315 | ||
|
|
91ec4cb5c7 | ||
|
|
634ce85f17 | ||
|
|
b6c52e8859 | ||
|
|
d042f6e2c9 | ||
|
|
cfe8c2956f | ||
|
|
235a8eab02 | ||
|
|
6baefa7490 | ||
|
|
319b29aa7d | ||
|
|
02428e4f0f | ||
|
|
3fe93eebb6 | ||
|
|
f5c7c14ce0 | ||
|
|
f57ab78916 | ||
|
|
087dab6f3d | ||
|
|
3df504b6b4 | ||
|
|
ea98b26766 | ||
|
|
6d6e598ce5 | ||
|
|
67ad7463f4 | ||
|
|
df6b481112 | ||
|
|
5301b556f0 | ||
|
|
2ff7345b08 | ||
|
|
bafcd3f036 | ||
|
|
5f787028df | ||
|
|
e1e65af8ea | ||
|
|
5bb71d396a | ||
|
|
058f44743c | ||
|
|
2077a2a3af | ||
|
|
5a3a0ae437 | ||
|
|
b4204fc806 | ||
|
|
e8c90c1327 | ||
|
|
136f2c8bb7 | ||
|
|
2b5e0e238d | ||
|
|
a6eface8bc | ||
|
|
7659b1aa18 | ||
|
|
a953b52eac | ||
|
|
6f164f0ee9 | ||
|
|
f61a9d7a70 | ||
|
|
4f270bffd0 | ||
|
|
fa90602aa0 | ||
|
|
eaf5034ec7 | ||
|
|
e83de896c9 | ||
|
|
bcd607a1ee | ||
|
|
e1adceeb85 | ||
|
|
106bdc3c52 | ||
|
|
5229807020 | ||
|
|
1e89467b31 | ||
|
|
f65b502a96 | ||
|
|
efe8342965 | ||
|
|
90cb3acd2f | ||
|
|
98dd87efbc | ||
|
|
ce6576ce84 | ||
|
|
5d4869f915 | ||
|
|
92567a070c | ||
|
|
30955c4e2c | ||
|
|
f9d51db182 | ||
|
|
852e799fea | ||
|
|
bbc3e99bb5 | ||
|
|
e8e22ee44c | ||
|
|
7a211beede | ||
|
|
24279b138b | ||
|
|
a2718a889d | ||
|
|
756de9f7f6 | ||
|
|
143d176dcd | ||
|
|
a1f7cfaf3a | ||
|
|
a549b3ac70 | ||
|
|
43c9179204 | ||
|
|
dfe232cf80 | ||
|
|
ba88cdce70 | ||
|
|
269f9fe860 | ||
|
|
4a670ab236 | ||
|
|
ab934f3d35 | ||
|
|
24eca809f1 | ||
|
|
57780b6520 | ||
|
|
f5e301fe0d | ||
|
|
68976f5b0c | ||
|
|
8e7e208265 | ||
|
|
0951c11aa6 | ||
|
|
3d4ca01242 | ||
|
|
319f86e91a | ||
|
|
490b3e3603 | ||
|
|
7476ee51e4 | ||
|
|
a9f1fda68a | ||
|
|
975337f225 | ||
|
|
1490f106f2 | ||
|
|
61c35265a6 | ||
|
|
0f4497e53e | ||
|
|
ca60c283fc | ||
|
|
37298bdbd6 | ||
|
|
d70fd86a82 | ||
|
|
ad606cbce6 | ||
|
|
4e0a32a37a | ||
|
|
7290040008 | ||
|
|
91bf50eb6c | ||
|
|
748ed30b47 | ||
|
|
b88d2f792d | ||
|
|
c8b19fa1ae | ||
|
|
c0795ce2c7 | ||
|
|
12c3aa83dd | ||
|
|
c193ba0c81 | ||
|
|
d8226c457f | ||
|
|
a507771f7c | ||
|
|
2f8d577e95 | ||
|
|
362babe5e2 | ||
|
|
d9297cee16 | ||
|
|
3851a2fe72 | ||
|
|
338300df32 | ||
|
|
0c7f63b2f3 | ||
|
|
9a87b7f196 | ||
|
|
abcd830af1 | ||
|
|
f807c999b6 | ||
|
|
ccd5deb85f | ||
|
|
dbf9525860 | ||
|
|
502efe42c9 | ||
|
|
f0787d98d1 | ||
|
|
d2ed8302a7 | ||
|
|
15310d1e97 | ||
|
|
38af401a16 | ||
|
|
ab1d39dfb3 | ||
|
|
d35786fe49 | ||
|
|
b9db84edf3 | ||
|
|
c3176cac6a | ||
|
|
6c98d64874 | ||
|
|
269dc26029 | ||
|
|
48d26fc0e9 | ||
|
|
797bf49ea6 | ||
|
|
fd96bacf70 | ||
|
|
7f9739cd4f | ||
|
|
698660c92a | ||
|
|
5aa90243a0 | ||
|
|
4d32ac8765 | ||
|
|
224f85b981 | ||
|
|
29c72c0744 | ||
|
|
36c162126b | ||
|
|
252dfe006b | ||
|
|
d1743f0608 | ||
|
|
9168cf811c | ||
|
|
9e50ae6a01 | ||
|
|
371c798116 | ||
|
|
d00e0021b6 | ||
|
|
bce3e61b7d | ||
|
|
ccbd4bace1 | ||
|
|
78999537f0 | ||
|
|
e3b0cc7093 | ||
|
|
50014f4398 | ||
|
|
833c276424 | ||
|
|
770525a385 | ||
|
|
e4204a56f5 | ||
|
|
d37a9eada3 | ||
|
|
a7bfae2f56 | ||
|
|
aaebcd074e | ||
|
|
58fc4520c9 | ||
|
|
53716697ca | ||
|
|
96668daefa | ||
|
|
4b033d3d93 | ||
|
|
8ca9a6a64a | ||
|
|
2ce3925ba9 | ||
|
|
3d9adbe991 | ||
|
|
f16828e129 | ||
|
|
91eb80beb0 | ||
|
|
30682968ff | ||
|
|
620e3b67e9 | ||
|
|
afe1ddd719 | ||
|
|
0aefacc88e | ||
|
|
366c707afa | ||
|
|
707e29adef | ||
|
|
66f2735224 | ||
|
|
86e19c274f | ||
|
|
46bb59e348 | ||
|
|
37cbba57e6 | ||
|
|
b05da425b9 | ||
|
|
c28f5cef7a | ||
|
|
f65e804836 | ||
|
|
01e9b3bcff | ||
|
|
4e58bed39e | ||
|
|
7d2942e1be | ||
|
|
943404689e | ||
|
|
89875aea02 | ||
|
|
70ce96caa0 | ||
|
|
625fb02dfe | ||
|
|
18966334ab | ||
|
|
a8f3756bbc | ||
|
|
f0dab97b80 | ||
|
|
3a7bba041f | ||
|
|
a50c0e61e4 | ||
|
|
87cb28eaab | ||
|
|
03fa58a271 | ||
|
|
666e610334 | ||
|
|
3c76a85674 | ||
|
|
c571ad9902 | ||
|
|
355c440c3d | ||
|
|
9a17658f84 | ||
|
|
69347aa7f6 | ||
|
|
bf259f4c98 | ||
|
|
082ebe8043 | ||
|
|
a13a02c995 | ||
|
|
3a9ac80786 | ||
|
|
d3cf177ebc | ||
|
|
7b49dbb74e | ||
|
|
4cc249bcd4 | ||
|
|
035529d7a2 | ||
|
|
fa91c0ed03 | ||
|
|
adbde56c1a | ||
|
|
70f53c6c06 |
No files matched your search
@@ -0,0 +1,20 @@
|
|||||||
|
* text=auto
|
||||||
|
|
||||||
|
*.html text eol=lf
|
||||||
|
*.java text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.jsp text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.properties text eol=lf
|
||||||
|
*.svg text auto
|
||||||
|
*.xml text eol=lf
|
||||||
|
*.xsl text eol=lf
|
||||||
|
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.otf binary
|
||||||
|
*.woff binary
|
||||||
+13
-2
@@ -1,8 +1,19 @@
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
|
jdk:
|
||||||
|
- oraclejdk8
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.m2
|
- $HOME/.m2
|
||||||
sudo: false
|
|
||||||
|
|
||||||
after_script: 'misc/.travis/after.sh'
|
before_cache:
|
||||||
|
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||||
|
|
||||||
|
install: mvn install -Pdistribution -DskipTests=true -B -V
|
||||||
|
|
||||||
|
script:
|
||||||
|
- mvn test -B
|
||||||
|
- mvn -file testsuite/integration-arquillian test -B
|
||||||
|
|
||||||
|
sudo: false
|
||||||
+50
-50
@@ -1,50 +1,50 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-broker-core</artifactId>
|
<artifactId>keycloak-broker-core</artifactId>
|
||||||
<name>Keycloak Broker Core</name>
|
<name>Keycloak Broker Core</name>
|
||||||
<description/>
|
<description/>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-events-api</artifactId>
|
<artifactId>keycloak-events-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>log4j</groupId>
|
<groupId>log4j</groupId>
|
||||||
<artifactId>log4j</artifactId>
|
<artifactId>log4j</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>slf4j-simple</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -58,6 +58,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response performLogin(AuthenticationRequest request) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||||
return null;
|
return null;
|
||||||
@@ -73,6 +78,11 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
|||||||
+2
@@ -18,8 +18,10 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|||||||
+38
-31
@@ -1,31 +1,38 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
/**
|
import org.keycloak.models.RealmModel;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
/**
|
||||||
*/
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
|
* @version $Revision: 1 $
|
||||||
@Override
|
*/
|
||||||
public void close() {
|
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
|
||||||
|
@Override
|
||||||
}
|
public void close() {
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public IdentityProviderMapper create(KeycloakSession session) {
|
|
||||||
return null;
|
@Override
|
||||||
}
|
public IdentityProviderMapper create(KeycloakSession session) {
|
||||||
|
return null;
|
||||||
@Override
|
}
|
||||||
public void init(org.keycloak.Config.Scope config) {
|
|
||||||
|
@Override
|
||||||
}
|
public void init(org.keycloak.Config.Scope config) {
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
|
@Override
|
||||||
}
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -32,6 +33,7 @@ public class BrokeredIdentityContext {
|
|||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String username;
|
private String username;
|
||||||
|
private String modelUsername;
|
||||||
private String email;
|
private String email;
|
||||||
private String firstName;
|
private String firstName;
|
||||||
private String lastName;
|
private String lastName;
|
||||||
@@ -42,6 +44,7 @@ public class BrokeredIdentityContext {
|
|||||||
private IdentityProviderModel idpConfig;
|
private IdentityProviderModel idpConfig;
|
||||||
private IdentityProvider idp;
|
private IdentityProvider idp;
|
||||||
private Map<String, Object> contextData = new HashMap<>();
|
private Map<String, Object> contextData = new HashMap<>();
|
||||||
|
private ClientSessionModel clientSession;
|
||||||
|
|
||||||
public BrokeredIdentityContext(String id) {
|
public BrokeredIdentityContext(String id) {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
@@ -59,6 +62,11 @@ public class BrokeredIdentityContext {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Username in remote idp
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
@@ -67,6 +75,19 @@ public class BrokeredIdentityContext {
|
|||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* username to store in UserModel
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getModelUsername() {
|
||||||
|
return modelUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModelUsername(String modelUsername) {
|
||||||
|
this.modelUsername = modelUsername;
|
||||||
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
@@ -147,6 +168,14 @@ public class BrokeredIdentityContext {
|
|||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ClientSessionModel getClientSession() {
|
||||||
|
return clientSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientSession(ClientSessionModel clientSession) {
|
||||||
|
this.clientSession = clientSession;
|
||||||
|
}
|
||||||
|
|
||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
int i = name.lastIndexOf(' ');
|
int i = name.lastIndexOf(' ');
|
||||||
|
|||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
|
public static final String ATTRIBUTE = "attribute";
|
||||||
|
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||||
|
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(ATTRIBUTE);
|
||||||
|
property.setLabel("User Attribute");
|
||||||
|
property.setHelpText("Name of user attribute you want to hardcode");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(ATTRIBUTE_VALUE);
|
||||||
|
property.setLabel("User Attribute Value");
|
||||||
|
property.setHelpText("Value you want to hardcode");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Attribute Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Hardcoded Attribute";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "hardcoded-attribute-idp-mapper";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
|
user.setSingleAttribute(attribute, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
|
user.setSingleAttribute(attribute, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "When user is imported from provider, hardcode a value to a specific user attribute.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,110 +1,110 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||||
public static final String ROLE = "role";
|
public static final String ROLE = "role";
|
||||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(ROLE);
|
property.setName(ROLE);
|
||||||
property.setLabel("Role");
|
property.setLabel("Role");
|
||||||
property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
property.setHelpText("Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static String[] parseRole(String role) {
|
public static String[] parseRole(String role) {
|
||||||
int scopeIndex = role.indexOf('.');
|
int scopeIndex = role.lastIndexOf('.');
|
||||||
if (scopeIndex > -1) {
|
if (scopeIndex > -1) {
|
||||||
String appName = role.substring(0, scopeIndex);
|
String appName = role.substring(0, scopeIndex);
|
||||||
role = role.substring(scopeIndex + 1);
|
role = role.substring(scopeIndex + 1);
|
||||||
String[] rtn = {appName, role};
|
String[] rtn = {appName, role};
|
||||||
return rtn;
|
return rtn;
|
||||||
} else {
|
} else {
|
||||||
String[] rtn = {null, role};
|
String[] rtn = {null, role};
|
||||||
return rtn;
|
return rtn;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
|
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
|
||||||
String[] parsedRole = parseRole(roleName);
|
String[] parsedRole = parseRole(roleName);
|
||||||
RoleModel role = null;
|
RoleModel role = null;
|
||||||
if (parsedRole[0] == null) {
|
if (parsedRole[0] == null) {
|
||||||
role = realm.getRole(parsedRole[1]);
|
role = realm.getRole(parsedRole[1]);
|
||||||
} else {
|
} else {
|
||||||
ClientModel client = realm.getClientByClientId(parsedRole[0]);
|
ClientModel client = realm.getClientByClientId(parsedRole[0]);
|
||||||
role = client.getRole(parsedRole[1]);
|
role = client.getRole(parsedRole[1]);
|
||||||
}
|
}
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return configProperties;
|
return configProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Role Importer";
|
return "Role Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "Hardcoded Role";
|
return "Hardcoded Role";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||||
|
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
|
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getCompatibleProviders() {
|
public String[] getCompatibleProviders() {
|
||||||
return COMPATIBLE_PROVIDERS;
|
return COMPATIBLE_PROVIDERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(ROLE);
|
String roleName = mapperModel.getConfig().get(ROLE);
|
||||||
RoleModel role = getRoleFromString(realm, roleName);
|
RoleModel role = getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "When user is imported from provider, hardcode a role mapping for it.";
|
return "When user is imported from provider, hardcode a role mapping for it.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
|
public static final String ATTRIBUTE = "attribute";
|
||||||
|
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||||
|
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(ATTRIBUTE);
|
||||||
|
property.setLabel("User Session Attribute");
|
||||||
|
property.setHelpText("Name of user session attribute you want to hardcode");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(ATTRIBUTE_VALUE);
|
||||||
|
property.setLabel("User Session Attribute Value");
|
||||||
|
property.setHelpText("Value you want to hardcode");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Attribute Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Hardcoded User Session Attribute";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "hardcoded-user-session-attribute-idp-mapper";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
|
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
|
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "When user is imported from provider, hardcode a value to a specific user session attribute.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package org.keycloak.broker.provider;
|
|||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
@@ -35,7 +36,7 @@ import javax.ws.rs.core.UriInfo;
|
|||||||
*/
|
*/
|
||||||
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
||||||
|
|
||||||
public interface AuthenticationCallback {
|
interface AuthenticationCallback {
|
||||||
/**
|
/**
|
||||||
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
|
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
|
||||||
* with the remote IDP
|
* with the remote IDP
|
||||||
@@ -43,9 +44,15 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||||||
* @param context
|
* @param context
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Response authenticated(BrokeredIdentityContext context);
|
Response authenticated(BrokeredIdentityContext context);
|
||||||
|
|
||||||
|
Response cancelled(String code);
|
||||||
|
|
||||||
|
Response error(String code, String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context);
|
||||||
void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context);
|
void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context);
|
||||||
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
||||||
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context);
|
||||||
|
|||||||
Regular → Executable
+2
@@ -18,6 +18,8 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
+56
-26
@@ -1,26 +1,56 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ConfiguredProvider;
|
import org.keycloak.provider.ConfiguredProvider;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
|
public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
|
||||||
public static final String ANY_PROVIDER = "*";
|
public static final String ANY_PROVIDER = "*";
|
||||||
|
|
||||||
String[] getCompatibleProviders();
|
String[] getCompatibleProviders();
|
||||||
String getDisplayCategory();
|
String getDisplayCategory();
|
||||||
String getDisplayType();
|
String getDisplayType();
|
||||||
|
|
||||||
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
/**
|
||||||
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
* Called to determine what keycloak username and email to use to process the login request from the external IDP
|
||||||
|
* Usually used to map BrokeredIdentityContet.username or email.
|
||||||
|
*
|
||||||
}
|
* @param session
|
||||||
|
* @param realm
|
||||||
|
* @param mapperModel
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after UserModel is created for first time for this user.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* @param realm
|
||||||
|
* @param user
|
||||||
|
* @param mapperModel
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when this user has logged in before and has already been imported.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* @param realm
|
||||||
|
* @param user
|
||||||
|
* @param mapperModel
|
||||||
|
* @param context
|
||||||
|
*/
|
||||||
|
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,6 +9,11 @@ import org.keycloak.provider.Spi;
|
|||||||
*/
|
*/
|
||||||
public class IdentityProviderMapperSpi implements Spi {
|
public class IdentityProviderMapperSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "identity-provider-mapper";
|
return "identity-provider-mapper";
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ public class IdentityProviderSpi implements Spi {
|
|||||||
|
|
||||||
public static final String IDENTITY_PROVIDER_SPI_NAME = "identity_provider";
|
public static final String IDENTITY_PROVIDER_SPI_NAME = "identity_provider";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return IDENTITY_PROVIDER_SPI_NAME;
|
return IDENTITY_PROVIDER_SPI_NAME;
|
||||||
|
|||||||
+3
-1
@@ -1 +1,3 @@
|
|||||||
org.keycloak.broker.provider.HardcodedRoleMapper
|
org.keycloak.broker.provider.HardcodedRoleMapper
|
||||||
|
org.keycloak.broker.provider.HardcodedAttributeMapper
|
||||||
|
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
org.keycloak.broker.provider.IdentityProviderSpi
|
org.keycloak.broker.provider.IdentityProviderSpi
|
||||||
org.keycloak.broker.provider.IdentityProviderMapperSpi
|
org.keycloak.broker.provider.IdentityProviderMapperSpi
|
||||||
+58
-58
@@ -1,58 +1,58 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-broker-oidc</artifactId>
|
<artifactId>keycloak-broker-oidc</artifactId>
|
||||||
<name>Keycloak Broker - OpenID Connect Identity Provider</name>
|
<name>Keycloak Broker - OpenID Connect Identity Provider</name>
|
||||||
<description/>
|
<description/>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-broker-core</artifactId>
|
<artifactId>keycloak-broker-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-core-asl</artifactId>
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-mapper-asl</artifactId>
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-events-api</artifactId>
|
<artifactId>keycloak-events-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-services</artifactId>
|
<artifactId>keycloak-services</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
+7
-4
@@ -20,7 +20,7 @@ package org.keycloak.broker.oidc;
|
|||||||
import org.codehaus.jackson.JsonNode;
|
import org.codehaus.jackson.JsonNode;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||||
@@ -58,6 +58,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||||||
public static final String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
|
public static final String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
|
||||||
public static final String FEDERATED_REFRESH_TOKEN = "FEDERATED_REFRESH_TOKEN";
|
public static final String FEDERATED_REFRESH_TOKEN = "FEDERATED_REFRESH_TOKEN";
|
||||||
public static final String FEDERATED_TOKEN_EXPIRATION = "FEDERATED_TOKEN_EXPIRATION";
|
public static final String FEDERATED_TOKEN_EXPIRATION = "FEDERATED_TOKEN_EXPIRATION";
|
||||||
|
public static final String ACCESS_DENIED = "access_denied";
|
||||||
protected static ObjectMapper mapper = new ObjectMapper();
|
protected static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
public static final String OAUTH2_PARAMETER_ACCESS_TOKEN = "access_token";
|
public static final String OAUTH2_PARAMETER_ACCESS_TOKEN = "access_token";
|
||||||
@@ -213,9 +214,11 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||||||
@QueryParam(OAuth2Constants.ERROR) String error) {
|
@QueryParam(OAuth2Constants.ERROR) String error) {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
|
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
|
||||||
event.event(EventType.LOGIN);
|
if (error.equals(ACCESS_DENIED)) {
|
||||||
event.error(error);
|
return callback.cancelled(state);
|
||||||
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
} else {
|
||||||
|
return callback.error(state, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
+114
-114
@@ -1,114 +1,114 @@
|
|||||||
package org.keycloak.broker.oidc;
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
import org.keycloak.representations.adapters.action.AdminAction;
|
import org.keycloak.representations.adapters.action.AdminAction;
|
||||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||||
|
|
||||||
public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
|
public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
|
||||||
|
|
||||||
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||||
return new KeycloakEndpoint(callback, realm, event);
|
return new KeycloakEndpoint(callback, realm, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
|
protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
|
||||||
JsonWebToken access = validateToken(idpKey, response.getToken());
|
JsonWebToken access = validateToken(idpKey, response.getToken());
|
||||||
context.getContextData().put(VALIDATED_ACCESS_TOKEN, access);
|
context.getContextData().put(VALIDATED_ACCESS_TOKEN, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class KeycloakEndpoint extends OIDCEndpoint {
|
protected class KeycloakEndpoint extends OIDCEndpoint {
|
||||||
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||||
super(callback, realm, event);
|
super(callback, realm, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path(AdapterConstants.K_LOGOUT)
|
@Path(AdapterConstants.K_LOGOUT)
|
||||||
public Response backchannelLogout(String input) {
|
public Response backchannelLogout(String input) {
|
||||||
JWSInput token = new JWSInput(input);
|
JWSInput token = new JWSInput(input);
|
||||||
PublicKey key = getExternalIdpKey();
|
PublicKey key = getExternalIdpKey();
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
if (!verify(token, key)) {
|
if (!verify(token, key)) {
|
||||||
logger.warn("Failed to verify logout request");
|
logger.warn("Failed to verify logout request");
|
||||||
return Response.status(400).build();
|
return Response.status(400).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogoutAction action = null;
|
LogoutAction action = null;
|
||||||
try {
|
try {
|
||||||
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
if (!validateAction(action)) return Response.status(400).build();
|
if (!validateAction(action)) return Response.status(400).build();
|
||||||
if (action.getKeycloakSessionIds() != null) {
|
if (action.getKeycloakSessionIds() != null) {
|
||||||
for (String sessionId : action.getKeycloakSessionIds()) {
|
for (String sessionId : action.getKeycloakSessionIds()) {
|
||||||
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
||||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||||
if (userSession != null
|
if (userSession != null
|
||||||
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT
|
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT
|
||||||
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT
|
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT
|
||||||
) {
|
) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean validateAction(AdminAction action) {
|
protected boolean validateAction(AdminAction action) {
|
||||||
if (!action.validate()) {
|
if (!action.validate()) {
|
||||||
logger.warn("admin request failed, not validated" + action.getAction());
|
logger.warn("admin request failed, not validated" + action.getAction());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (action.isExpired()) {
|
if (action.isExpired()) {
|
||||||
logger.warn("admin request failed, expired token");
|
logger.warn("admin request failed, expired token");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!getConfig().getClientId().equals(action.getResource())) {
|
if (!getConfig().getClientId().equals(action.getResource())) {
|
||||||
logger.warn("Resource name does not match");
|
logger.warn("Resource name does not match");
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||||
return super.generateTokenRequest(authorizationCode)
|
return super.generateTokenRequest(authorizationCode)
|
||||||
.param(AdapterConstants.CLIENT_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
.param(AdapterConstants.CLIENT_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+7
-1
@@ -17,10 +17,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.broker.oidc;
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.mappers.UsernameTemplateMapper;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,4 +55,5 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide
|
|||||||
return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
|
return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ package org.keycloak.broker.oidc;
|
|||||||
|
|
||||||
import org.codehaus.jackson.JsonNode;
|
import org.codehaus.jackson.JsonNode;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
|
||||||
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
import org.keycloak.broker.oidc.util.JsonSimpleHttp;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
@@ -41,7 +42,7 @@ import org.keycloak.services.messages.Messages;
|
|||||||
import org.keycloak.services.resources.IdentityBrokerService;
|
import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
@@ -50,6 +51,7 @@ import javax.ws.rs.core.Context;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
@@ -224,7 +226,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||||||
name = getJsonProperty(userInfo, "name");
|
name = getJsonProperty(userInfo, "name");
|
||||||
preferredUsername = getJsonProperty(userInfo, "preferred_username");
|
preferredUsername = getJsonProperty(userInfo, "preferred_username");
|
||||||
email = getJsonProperty(userInfo, "email");
|
email = getJsonProperty(userInfo, "email");
|
||||||
identity.getContextData().put(USER_INFO, userInfo);
|
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
|
||||||
}
|
}
|
||||||
identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
|
identity.getContextData().put(FEDERATED_ACCESS_TOKEN_RESPONSE, tokenResponse);
|
||||||
identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
|
identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);
|
||||||
@@ -287,10 +289,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||||||
}
|
}
|
||||||
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
|
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
|
||||||
|
|
||||||
String aud = token.getAudience();
|
|
||||||
String iss = token.getIssuer();
|
String iss = token.getIssuer();
|
||||||
|
|
||||||
if (aud != null && !aud.equals(getConfig().getClientId())) {
|
if (!token.hasAudience(getConfig().getClientId())) {
|
||||||
throw new IdentityBrokerException("Wrong audience from token.");
|
throw new IdentityBrokerException("Wrong audience from token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.broker.oidc;
|
package org.keycloak.broker.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.mappers.UsernameTemplateMapper;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKParser;
|
import org.keycloak.jose.jwk.JWKParser;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
|
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||||
@@ -30,6 +34,7 @@ import org.keycloak.util.JsonSerialization;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +94,7 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("F ailed to query JWKSet from: " + uri, e);
|
throw new RuntimeException("Failed to query JWKSet from: " + uri, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -99,4 +104,5 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
protected static boolean keyTypeSupported(String type) {
|
protected static boolean keyTypeSupported(String type) {
|
||||||
return type != null && type.equals("RSA");
|
return type != null && type.equals("RSA");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+97
-93
@@ -1,93 +1,97 @@
|
|||||||
package org.keycloak.broker.oidc.mappers;
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper {
|
public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper {
|
||||||
public static final String CLAIM = "claim";
|
public static final String CLAIM = "claim";
|
||||||
public static final String CLAIM_VALUE = "claim.value";
|
public static final String CLAIM_VALUE = "claim.value";
|
||||||
|
|
||||||
public static Object getClaimValue(JsonWebToken token, String claim) {
|
public static Object getClaimValue(JsonWebToken token, String claim) {
|
||||||
String[] split = claim.split("\\.");
|
String[] split = claim.split("\\.");
|
||||||
Map<String, Object> jsonObject = token.getOtherClaims();
|
Map<String, Object> jsonObject = token.getOtherClaims();
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (int i = 0; i < split.length; i++) {
|
||||||
if (i == split.length - 1) {
|
if (i == split.length - 1) {
|
||||||
return jsonObject.get(split[i]);
|
return jsonObject.get(split[i]);
|
||||||
} else {
|
} else {
|
||||||
Object val = jsonObject.get(split[i]);
|
Object val = jsonObject.get(split[i]);
|
||||||
if (!(val instanceof Map)) return null;
|
if (!(val instanceof Map)) return null;
|
||||||
jsonObject = (Map<String, Object>)val;
|
jsonObject = (Map<String, Object>)val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object getClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public static Object getClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String claim = mapperModel.getConfig().get(CLAIM);
|
String claim = mapperModel.getConfig().get(CLAIM);
|
||||||
{ // search access token
|
return getClaimValue(context, claim);
|
||||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
}
|
||||||
if (token != null) {
|
|
||||||
Object value = getClaimValue(token, claim);
|
public static Object getClaimValue(BrokeredIdentityContext context, String claim) {
|
||||||
if (value != null) return value;
|
{ // search access token
|
||||||
}
|
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||||
|
if (token != null) {
|
||||||
}
|
Object value = getClaimValue(token, claim);
|
||||||
{ // search ID Token
|
if (value != null) return value;
|
||||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN);
|
}
|
||||||
if (token != null) {
|
|
||||||
Object value = getClaimValue(token, claim);
|
}
|
||||||
if (value != null) return value;
|
{ // search ID Token
|
||||||
}
|
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN);
|
||||||
|
if (token != null) {
|
||||||
}
|
Object value = getClaimValue(token, claim);
|
||||||
return null;
|
if (value != null) return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
protected boolean hasClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
return null;
|
||||||
Object value = getClaimValue(mapperModel, context);
|
}
|
||||||
String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE);
|
|
||||||
return valueEquals(desiredValue, value);
|
|
||||||
}
|
protected boolean hasClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
Object value = getClaimValue(mapperModel, context);
|
||||||
public boolean valueEquals(String desiredValue, Object value) {
|
String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE);
|
||||||
if (value instanceof String) {
|
return valueEquals(desiredValue, value);
|
||||||
if (desiredValue.equals(value)) return true;
|
}
|
||||||
} else if (value instanceof Double) {
|
|
||||||
try {
|
public boolean valueEquals(String desiredValue, Object value) {
|
||||||
if (Double.valueOf(desiredValue).equals(value)) return true;
|
if (value instanceof String) {
|
||||||
} catch (Exception e) {
|
if (desiredValue.equals(value)) return true;
|
||||||
|
} else if (value instanceof Double) {
|
||||||
}
|
try {
|
||||||
} else if (value instanceof Integer) {
|
if (Double.valueOf(desiredValue).equals(value)) return true;
|
||||||
try {
|
} catch (Exception e) {
|
||||||
if (Integer.valueOf(desiredValue).equals(value)) return true;
|
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
} else if (value instanceof Integer) {
|
||||||
}
|
try {
|
||||||
} else if (value instanceof Boolean) {
|
if (Integer.valueOf(desiredValue).equals(value)) return true;
|
||||||
try {
|
} catch (Exception e) {
|
||||||
if (Boolean.valueOf(desiredValue).equals(value)) return true;
|
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
} else if (value instanceof Boolean) {
|
||||||
}
|
try {
|
||||||
} else if (value instanceof List) {
|
if (Boolean.valueOf(desiredValue).equals(value)) return true;
|
||||||
List list = (List)value;
|
} catch (Exception e) {
|
||||||
for (Object val : list) {
|
|
||||||
return valueEquals(desiredValue, val);
|
}
|
||||||
}
|
} else if (value instanceof List) {
|
||||||
}
|
List list = (List)value;
|
||||||
return false;
|
for (Object val : list) {
|
||||||
}
|
return valueEquals(desiredValue, val);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+206
@@ -0,0 +1,206 @@
|
|||||||
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.JsonNode;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProvider;
|
||||||
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
|
||||||
|
* attribute. Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who
|
||||||
|
* uses {@link JsonNode} user profile.
|
||||||
|
*
|
||||||
|
* @author Vlastimil Elias (velias at redhat dot com)
|
||||||
|
*/
|
||||||
|
public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
|
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
|
||||||
|
|
||||||
|
protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump");
|
||||||
|
|
||||||
|
private static final String JSON_PATH_DELIMITER = ".";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config param where name of mapping source JSON User Profile field is stored.
|
||||||
|
*/
|
||||||
|
public static final String CONF_JSON_FIELD = "jsonField";
|
||||||
|
/**
|
||||||
|
* Config param where name of mapping target USer attribute is stored.
|
||||||
|
*/
|
||||||
|
public static final String CONF_USER_ATTRIBUTE = "userAttribute";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored.
|
||||||
|
*/
|
||||||
|
public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO;
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
ProviderConfigProperty property1;
|
||||||
|
property1 = new ProviderConfigProperty();
|
||||||
|
property1.setName(CONF_JSON_FIELD);
|
||||||
|
property1.setLabel("Social Profile JSON Field Path");
|
||||||
|
property1.setHelpText("Path of field in Social provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. Eg. 'contact.address[0].country'.");
|
||||||
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property1);
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(CONF_USER_ATTRIBUTE);
|
||||||
|
property.setLabel("User Attribute Name");
|
||||||
|
property.setHelpText("User attribute name to store information into.");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store used profile JsonNode into user context for later use by this mapper. Profile data are dumped into special logger if enabled also to allow investigation of the structure.
|
||||||
|
*
|
||||||
|
* @param user context to store profile data into
|
||||||
|
* @param profile to store into context
|
||||||
|
* @param provider identification of social provider to be used in log dump
|
||||||
|
*
|
||||||
|
* @see #importNewUser(KeycloakSession, RealmModel, UserModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
||||||
|
* @see BrokeredIdentityContext#getContextData()
|
||||||
|
*/
|
||||||
|
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
||||||
|
user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile);
|
||||||
|
if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled())
|
||||||
|
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Attribute Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Attribute Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Import user profile information if it exists in Social provider JSON data into the specified user attribute.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
||||||
|
if (attribute == null || attribute.trim().isEmpty()) {
|
||||||
|
logger.debug("Attribute is not configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attribute = attribute.trim();
|
||||||
|
|
||||||
|
String value = getJsonValue(mapperModel, context);
|
||||||
|
if (value != null) {
|
||||||
|
user.setSingleAttribute(attribute, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
// we do not update user profile from social provider
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
||||||
|
if (jsonField == null || jsonField.trim().isEmpty()) {
|
||||||
|
logger.debug("JSON field path is not configured");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
jsonField = jsonField.trim();
|
||||||
|
|
||||||
|
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
||||||
|
logger.debug("JSON field path is invalid " + jsonField);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE);
|
||||||
|
|
||||||
|
String value = getJsonValue(profileJsonNode, jsonField);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
logger.debug("User profile JSON value '" + jsonField + "' is not available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getJsonValue(JsonNode baseNode, String fieldPath) {
|
||||||
|
logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode);
|
||||||
|
if (baseNode != null) {
|
||||||
|
|
||||||
|
int idx = fieldPath.indexOf(JSON_PATH_DELIMITER);
|
||||||
|
|
||||||
|
String currentFieldName = fieldPath;
|
||||||
|
if (idx > 0) {
|
||||||
|
currentFieldName = fieldPath.substring(0, idx).trim();
|
||||||
|
if (currentFieldName.isEmpty()) {
|
||||||
|
logger.debug("JSON path is invalid " + fieldPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentNodeName = currentFieldName;
|
||||||
|
int arrayIndex = -1;
|
||||||
|
if (currentFieldName.endsWith("]")) {
|
||||||
|
int bi = currentFieldName.indexOf("[");
|
||||||
|
if (bi == -1) {
|
||||||
|
logger.debug("Invalid array index construct in " + currentFieldName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String is = currentFieldName.substring(bi+1, currentFieldName.length() - 1).trim();
|
||||||
|
arrayIndex = Integer.parseInt(is);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Invalid array index construct in " + currentFieldName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
currentNodeName = currentFieldName.substring(0,bi).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode currentNode = baseNode.get(currentNodeName);
|
||||||
|
if (arrayIndex > -1 && currentNode.isArray()) {
|
||||||
|
logger.debug("Going to take array node at index " + arrayIndex);
|
||||||
|
currentNode = currentNode.get(arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentNode == null) {
|
||||||
|
logger.debug("JsonNode not found for name " + currentFieldName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx < 0) {
|
||||||
|
if (!currentNode.isValueNode()) {
|
||||||
|
logger.debug("JsonNode is not value node for name " + currentFieldName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String ret = currentNode.asText();
|
||||||
|
if (ret != null && !ret.trim().isEmpty())
|
||||||
|
return ret.trim();
|
||||||
|
} else {
|
||||||
|
return getJsonValue(currentNode, fieldPath.substring(idx + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+105
-105
@@ -1,105 +1,105 @@
|
|||||||
package org.keycloak.broker.oidc.mappers;
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ClaimToRoleMapper extends AbstractClaimMapper {
|
public class ClaimToRoleMapper extends AbstractClaimMapper {
|
||||||
|
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
ProviderConfigProperty property1;
|
ProviderConfigProperty property1;
|
||||||
property1 = new ProviderConfigProperty();
|
property1 = new ProviderConfigProperty();
|
||||||
property1.setName(CLAIM);
|
property1.setName(CLAIM);
|
||||||
property1.setLabel("Claim");
|
property1.setLabel("Claim");
|
||||||
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
||||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property1);
|
configProperties.add(property1);
|
||||||
property1 = new ProviderConfigProperty();
|
property1 = new ProviderConfigProperty();
|
||||||
property1.setName(CLAIM_VALUE);
|
property1.setName(CLAIM_VALUE);
|
||||||
property1.setLabel("Claim Value");
|
property1.setLabel("Claim Value");
|
||||||
property1.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array.");
|
property1.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array.");
|
||||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property1);
|
configProperties.add(property1);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(HardcodedRoleMapper.ROLE);
|
property.setName(HardcodedRoleMapper.ROLE);
|
||||||
property.setLabel("Role");
|
property.setLabel("Role");
|
||||||
property.setHelpText("Role to grant to user if claim is present. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
property.setHelpText("Role to grant to user if claim is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
|
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return configProperties;
|
return configProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getCompatibleProviders() {
|
public String[] getCompatibleProviders() {
|
||||||
return COMPATIBLE_PROVIDERS;
|
return COMPATIBLE_PROVIDERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Role Importer";
|
return "Role Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "Claim to Role";
|
return "Claim to Role";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (hasClaimValue(mapperModel, context)) {
|
if (hasClaimValue(mapperModel, context)) {
|
||||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (!hasClaimValue(mapperModel, context)) {
|
if (!hasClaimValue(mapperModel, context)) {
|
||||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.deleteRoleMapping(role);
|
user.deleteRoleMapping(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "If a claim exists, grant the user the specified realm or application role.";
|
return "If a claim exists, grant the user the specified realm or application role.";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+118
-118
@@ -1,118 +1,118 @@
|
|||||||
package org.keycloak.broker.oidc.mappers;
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
|
||||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
||||||
|
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
|
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
private static final String EXTERNAL_ROLE = "external.role";
|
private static final String EXTERNAL_ROLE = "external.role";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
ProviderConfigProperty property1;
|
ProviderConfigProperty property1;
|
||||||
property1 = new ProviderConfigProperty();
|
property1 = new ProviderConfigProperty();
|
||||||
property1.setName(EXTERNAL_ROLE);
|
property1.setName(EXTERNAL_ROLE);
|
||||||
property1.setLabel("External role");
|
property1.setLabel("External role");
|
||||||
property1.setHelpText("External role to check for. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.");
|
property1.setHelpText("External role to check for. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.");
|
||||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property1);
|
configProperties.add(property1);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(HardcodedRoleMapper.ROLE);
|
property.setName(HardcodedRoleMapper.ROLE);
|
||||||
property.setLabel("Role");
|
property.setLabel("Role");
|
||||||
property.setHelpText("Role to grant to user if external role is present. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
property.setHelpText("Role to grant to user if external role is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
|
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return configProperties;
|
return configProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getCompatibleProviders() {
|
public String[] getCompatibleProviders() {
|
||||||
return COMPATIBLE_PROVIDERS;
|
return COMPATIBLE_PROVIDERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Role Importer";
|
return "Role Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "External Role to Role";
|
return "External Role to Role";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
RoleModel role = hasRole(realm, mapperModel, context);
|
RoleModel role = hasRole(realm, mapperModel, context);
|
||||||
if (role != null) {
|
if (role != null) {
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||||
//if (token == null) return;
|
//if (token == null) return;
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
||||||
String externalRoleName = parseRole[1];
|
String externalRoleName = parseRole[1];
|
||||||
String claimName = null;
|
String claimName = null;
|
||||||
if (parseRole[0] == null) {
|
if (parseRole[0] == null) {
|
||||||
claimName = "realm_access.roles";
|
claimName = "realm_access.roles";
|
||||||
} else {
|
} else {
|
||||||
claimName = "resource_access." + parseRole[0] + ".roles";
|
claimName = "resource_access." + parseRole[0] + ".roles";
|
||||||
}
|
}
|
||||||
Object claim = getClaimValue(token, claimName);
|
Object claim = getClaimValue(token, claimName);
|
||||||
if (valueEquals(externalRoleName, claim)) {
|
if (valueEquals(externalRoleName, claim)) {
|
||||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
RoleModel role = hasRole(realm, mapperModel, context);
|
RoleModel role = hasRole(realm, mapperModel, context);
|
||||||
if (role == null) {
|
if (role == null) {
|
||||||
user.deleteRoleMapping(role);
|
user.deleteRoleMapping(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "Looks for an external role in a keycloak access token. If external role exists, grant the user the specified realm or application role.";
|
return "Looks for an external role in a keycloak access token. If external role exists, grant the user the specified realm or application role.";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+98
-100
@@ -1,100 +1,98 @@
|
|||||||
package org.keycloak.broker.oidc.mappers;
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import java.util.ArrayList;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
/**
|
* @version $Revision: 1 $
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
*/
|
||||||
* @version $Revision: 1 $
|
public class UserAttributeMapper extends AbstractClaimMapper {
|
||||||
*/
|
|
||||||
public class UserAttributeMapper extends AbstractClaimMapper {
|
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
public static final String USER_ATTRIBUTE = "user.attribute";
|
||||||
|
|
||||||
public static final String USER_ATTRIBUTE = "user.attribute";
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
static {
|
ProviderConfigProperty property1;
|
||||||
ProviderConfigProperty property;
|
property1 = new ProviderConfigProperty();
|
||||||
ProviderConfigProperty property1;
|
property1.setName(CLAIM);
|
||||||
property1 = new ProviderConfigProperty();
|
property1.setLabel("Claim");
|
||||||
property1.setName(CLAIM);
|
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
||||||
property1.setLabel("Claim");
|
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
|
configProperties.add(property1);
|
||||||
property1.setType(ProviderConfigProperty.STRING_TYPE);
|
property = new ProviderConfigProperty();
|
||||||
configProperties.add(property1);
|
property.setName(USER_ATTRIBUTE);
|
||||||
property = new ProviderConfigProperty();
|
property.setLabel("User Attribute Name");
|
||||||
property.setName(USER_ATTRIBUTE);
|
property.setHelpText("User attribute name to store claim.");
|
||||||
property.setLabel("User Attribute Name");
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
property.setHelpText("User attribute name to store claim.");
|
configProperties.add(property);
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
}
|
||||||
configProperties.add(property);
|
|
||||||
}
|
public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper";
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper";
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
@Override
|
return configProperties;
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
}
|
||||||
return configProperties;
|
|
||||||
}
|
@Override
|
||||||
|
public String getId() {
|
||||||
@Override
|
return PROVIDER_ID;
|
||||||
public String getId() {
|
}
|
||||||
return PROVIDER_ID;
|
|
||||||
}
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
@Override
|
return COMPATIBLE_PROVIDERS;
|
||||||
public String[] getCompatibleProviders() {
|
}
|
||||||
return COMPATIBLE_PROVIDERS;
|
|
||||||
}
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
@Override
|
return "Attribute Importer";
|
||||||
public String getDisplayCategory() {
|
}
|
||||||
return "Attribute Importer";
|
|
||||||
}
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
@Override
|
return "Attribute Importer";
|
||||||
public String getDisplayType() {
|
}
|
||||||
return "Attribute Importer";
|
|
||||||
}
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
@Override
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
Object value = getClaimValue(mapperModel, context);
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
if (value != null) {
|
||||||
Object value = getClaimValue(mapperModel, context);
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
if (value != null) {
|
}
|
||||||
user.setAttribute(attribute, value.toString());
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
@Override
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
Object value = getClaimValue(mapperModel, context);
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String current = user.getFirstAttribute(attribute);
|
||||||
Object value = getClaimValue(mapperModel, context);
|
if (value != null && !value.equals(current)) {
|
||||||
String current = user.getAttribute(attribute);
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
if (value != null && !value.equals(current)) {
|
} else if (value == null) {
|
||||||
user.setAttribute(attribute, value.toString());
|
user.removeAttribute(attribute);
|
||||||
} else if (value == null) {
|
}
|
||||||
user.removeAttribute(attribute);
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
@Override
|
return "Import declared claim if it exists in ID or access token into the specified user attribute.";
|
||||||
public String getHelpText() {
|
}
|
||||||
return "Import declared claim if it exists in ID or access token into the specified user attribute.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UsernameTemplateMapper extends AbstractClaimMapper {
|
||||||
|
|
||||||
|
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
public static final String TEMPLATE = "template";
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(TEMPLATE);
|
||||||
|
property.setLabel("Template");
|
||||||
|
property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM.<NAME> references an ID or Access token claim.");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
property.setDefaultValue("${ALIAS}.${CLAIM.preferred_username}");
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "oidc-username-idp-mapper";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Preprocessor";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Username Template Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String template = mapperModel.getConfig().get(TEMPLATE);
|
||||||
|
Matcher m = substitution.matcher(template);
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while (m.find()) {
|
||||||
|
String variable = m.group(1);
|
||||||
|
if (variable.equals("ALIAS")) {
|
||||||
|
m.appendReplacement(sb, context.getIdpConfig().getAlias());
|
||||||
|
} else if (variable.equals("UUID")) {
|
||||||
|
m.appendReplacement(sb, KeycloakModelUtils.generateId());
|
||||||
|
} else if (variable.startsWith("CLAIM.")) {
|
||||||
|
String name = variable.substring("CLAIM.".length());
|
||||||
|
Object value = AbstractClaimMapper.getClaimValue(context, name);
|
||||||
|
if (value == null) value = "";
|
||||||
|
m.appendReplacement(sb, value.toString());
|
||||||
|
} else {
|
||||||
|
m.appendReplacement(sb, m.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
String username = sb.toString();
|
||||||
|
context.setModelUsername(username);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Format the username to import.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
package org.keycloak.broker.oidc.util;
|
package org.keycloak.broker.oidc.util;
|
||||||
|
|
||||||
import org.codehaus.jackson.JsonNode;
|
import org.codehaus.jackson.JsonNode;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JsonSimpleHttp extends SimpleHttp {
|
public class JsonSimpleHttp extends SimpleHttp {
|
||||||
public JsonSimpleHttp(String url, String method) {
|
public JsonSimpleHttp(String url, String method) {
|
||||||
super(url, method);
|
super(url, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonSimpleHttp doGet(String url) {
|
public static JsonSimpleHttp doGet(String url) {
|
||||||
return new JsonSimpleHttp(url, "GET");
|
return new JsonSimpleHttp(url, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonSimpleHttp doPost(String url) {
|
public static JsonSimpleHttp doPost(String url) {
|
||||||
return new JsonSimpleHttp(url, "POST");
|
return new JsonSimpleHttp(url, "POST");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ObjectMapper mapper = new ObjectMapper();
|
private static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
public static JsonNode asJson(SimpleHttp request) throws IOException {
|
public static JsonNode asJson(SimpleHttp request) throws IOException {
|
||||||
return mapper.readTree(request.asString());
|
return mapper.readTree(request.asString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||||
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
||||||
+4
-3
@@ -1,3 +1,4 @@
|
|||||||
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
|
||||||
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
org.keycloak.broker.oidc.mappers.UserAttributeMapper
|
||||||
|
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* JBoss, Home of Professional Open Source
|
||||||
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
|
* as indicated by the @authors tag. All rights reserved.
|
||||||
|
*/
|
||||||
|
package org.keycloak.broker.oidc.mappers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.codehaus.jackson.JsonNode;
|
||||||
|
import org.codehaus.jackson.JsonProcessingException;
|
||||||
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link AbstractJsonUserAttributeMapper}
|
||||||
|
*
|
||||||
|
* @author Vlastimil Elias (velias at redhat dot com)
|
||||||
|
*/
|
||||||
|
public class AbstractJsonUserAttributeMapperTest {
|
||||||
|
|
||||||
|
private static ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private static JsonNode baseNode;
|
||||||
|
|
||||||
|
private JsonNode getJsonNode() throws JsonProcessingException, IOException {
|
||||||
|
if (baseNode == null)
|
||||||
|
baseNode = mapper.readTree("{ \"value1\" : \"v1 \",\"value_empty\" : \"\", \"value_b\" : true, \"value_i\" : 454, " + " \"value_array\":[\"a1\",\"a2\"], " +" \"nest1\": {\"value1\": \" fgh \",\"value_empty\" : \"\", \"nest2\":{\"value_b\" : false, \"value_i\" : 43}}, "+ " \"nesta\": { \"a\":[{\"av1\": \"vala1\"},{\"av1\": \"vala2\"}]}"+" }");
|
||||||
|
return baseNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getJsonValue_invalidPath() throws JsonProcessingException, IOException {
|
||||||
|
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "."));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), ".."));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "...value1"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), ".value1"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value1."));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "[]"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "[value1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getJsonValue_simpleValues() throws JsonProcessingException, IOException {
|
||||||
|
|
||||||
|
//unknown field returns null
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_unknown"));
|
||||||
|
|
||||||
|
// we check value is trimmed also!
|
||||||
|
Assert.assertEquals("v1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_empty"));
|
||||||
|
|
||||||
|
Assert.assertEquals("true", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_b"));
|
||||||
|
Assert.assertEquals("454", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_i"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getJsonValue_nestedSimpleValues() throws JsonProcessingException, IOException {
|
||||||
|
|
||||||
|
// null if path points to JSON object
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2"));
|
||||||
|
|
||||||
|
//unknown field returns null
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value_unknown"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_unknown"));
|
||||||
|
|
||||||
|
// we check value is trimmed also!
|
||||||
|
Assert.assertEquals("fgh", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.value_empty"));
|
||||||
|
|
||||||
|
Assert.assertEquals("false", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_b"));
|
||||||
|
Assert.assertEquals("43", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2.value_i"));
|
||||||
|
|
||||||
|
// null if invalid nested path
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1."));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nest1.nest2."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getJsonValue_simpleArray() throws JsonProcessingException, IOException {
|
||||||
|
|
||||||
|
// array field itself returns null if no index is provided
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array"));
|
||||||
|
// outside index returns null
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[2]"));
|
||||||
|
|
||||||
|
//corect index
|
||||||
|
Assert.assertEquals("a1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[0]"));
|
||||||
|
Assert.assertEquals("a2", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[1]"));
|
||||||
|
|
||||||
|
//incorrect array constructs
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[]"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array]"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array["));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[a]"));
|
||||||
|
Assert.assertNull(AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "value_array[-2]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getJsonValue_nestedArrayWithObjects() throws JsonProcessingException, IOException {
|
||||||
|
Assert.assertEquals("vala1", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0].av1"));
|
||||||
|
Assert.assertEquals("vala2", AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[1].av1"));
|
||||||
|
|
||||||
|
//different path erros or nonexisting indexes or fields return null
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[2].av1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0]"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[0].av_unknown"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[].av1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a.av1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a].av1"));
|
||||||
|
Assert.assertEquals(null, AbstractJsonUserAttributeMapper.getJsonValue(getJsonNode(), "nesta.a[.av1"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+23
-23
@@ -1,23 +1,23 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-broker-parent</artifactId>
|
<artifactId>keycloak-broker-parent</artifactId>
|
||||||
<name>Keycloak Broker Parent</name>
|
<name>Keycloak Broker Parent</name>
|
||||||
<description/>
|
<description/>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>oidc</module>
|
<module>oidc</module>
|
||||||
<module>saml</module>
|
<module>saml</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
+50
-50
@@ -1,50 +1,50 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-broker-saml</artifactId>
|
<artifactId>keycloak-broker-saml</artifactId>
|
||||||
<name>Keycloak Broker - SAML Identity Provider</name>
|
<name>Keycloak Broker - SAML Identity Provider</name>
|
||||||
<description/>
|
<description/>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-broker-core</artifactId>
|
<artifactId>keycloak-broker-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-protocol</artifactId>
|
<artifactId>keycloak-saml-protocol</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-core</artifactId>
|
<artifactId>keycloak-saml-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-services</artifactId>
|
<artifactId>keycloak-services</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-events-api</artifactId>
|
<artifactId>keycloak-events-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -1,485 +1,438 @@
|
|||||||
package org.keycloak.broker.saml;
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
|
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||||
import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
|
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||||
import org.keycloak.protocol.saml.SAMLRequestParser;
|
import org.keycloak.saml.SAMLRequestParser;
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
import org.keycloak.saml.common.util.StaxParserUtil;
|
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||||
import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
|
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
||||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
|
|
||||||
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
import javax.ws.rs.Consumes;
|
||||||
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
import javax.ws.rs.FormParam;
|
||||||
import org.keycloak.services.ErrorPage;
|
import javax.ws.rs.GET;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import javax.ws.rs.POST;
|
||||||
import org.keycloak.services.messages.Messages;
|
import javax.ws.rs.QueryParam;
|
||||||
import org.w3c.dom.Document;
|
import javax.ws.rs.core.Context;
|
||||||
import org.w3c.dom.Element;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import org.w3c.dom.Node;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import javax.ws.rs.GET;
|
import java.io.IOException;
|
||||||
import javax.ws.rs.POST;
|
import java.security.PublicKey;
|
||||||
import javax.ws.rs.QueryParam;
|
import java.security.cert.X509Certificate;
|
||||||
import javax.ws.rs.core.Context;
|
import java.util.List;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
/**
|
||||||
import javax.ws.rs.core.Response;
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
import javax.ws.rs.core.UriBuilder;
|
* @version $Revision: 1 $
|
||||||
import javax.ws.rs.core.UriInfo;
|
*/
|
||||||
import javax.xml.namespace.QName;
|
public class SAMLEndpoint {
|
||||||
import java.io.IOException;
|
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
||||||
import java.security.PrivateKey;
|
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
|
||||||
import java.security.PublicKey;
|
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
|
||||||
import java.security.cert.X509Certificate;
|
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
|
||||||
import java.util.List;
|
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
|
||||||
|
public static final String SAML_ASSERTION = "SAML_ASSERTION";
|
||||||
/**
|
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
protected RealmModel realm;
|
||||||
* @version $Revision: 1 $
|
protected EventBuilder event;
|
||||||
*/
|
protected SAMLIdentityProviderConfig config;
|
||||||
public class SAMLEndpoint {
|
protected IdentityProvider.AuthenticationCallback callback;
|
||||||
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
protected SAMLIdentityProvider provider;
|
||||||
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
|
|
||||||
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
|
@Context
|
||||||
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
|
private UriInfo uriInfo;
|
||||||
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
|
|
||||||
public static final String SAML_ASSERTION = "SAML_ASSERTION";
|
@Context
|
||||||
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
|
private KeycloakSession session;
|
||||||
protected RealmModel realm;
|
|
||||||
protected EventBuilder event;
|
@Context
|
||||||
protected SAMLIdentityProviderConfig config;
|
private ClientConnection clientConnection;
|
||||||
protected IdentityProvider.AuthenticationCallback callback;
|
|
||||||
protected SAMLIdentityProvider provider;
|
@Context
|
||||||
|
private HttpHeaders headers;
|
||||||
@Context
|
|
||||||
private UriInfo uriInfo;
|
|
||||||
|
public SAMLEndpoint(RealmModel realm, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) {
|
||||||
@Context
|
this.realm = realm;
|
||||||
private KeycloakSession session;
|
this.config = config;
|
||||||
|
this.callback = callback;
|
||||||
@Context
|
this.provider = provider;
|
||||||
private ClientConnection clientConnection;
|
}
|
||||||
|
|
||||||
@Context
|
@GET
|
||||||
private HttpHeaders headers;
|
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
|
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
|
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||||
public SAMLEndpoint(RealmModel realm, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) {
|
return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
|
||||||
this.realm = realm;
|
}
|
||||||
this.config = config;
|
|
||||||
this.callback = callback;
|
|
||||||
this.provider = provider;
|
/**
|
||||||
}
|
*/
|
||||||
|
@POST
|
||||||
@GET
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||||
return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
|
return new PostBinding().execute(samlRequest, samlResponse, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract class Binding {
|
||||||
/**
|
private boolean checkSsl() {
|
||||||
*/
|
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||||
@POST
|
return true;
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
} else {
|
||||||
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
return !realm.getSslRequired().isRequired(clientConnection);
|
||||||
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
}
|
||||||
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
}
|
||||||
return new PostBinding().execute(samlRequest, samlResponse, relayState);
|
|
||||||
}
|
protected Response basicChecks(String samlRequest, String samlResponse) {
|
||||||
|
if (!checkSsl()) {
|
||||||
protected abstract class Binding {
|
event.event(EventType.LOGIN);
|
||||||
private boolean checkSsl() {
|
event.error(Errors.SSL_REQUIRED);
|
||||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
|
||||||
return true;
|
}
|
||||||
} else {
|
if (!realm.isEnabled()) {
|
||||||
return !realm.getSslRequired().isRequired(clientConnection);
|
event.event(EventType.LOGIN_ERROR);
|
||||||
}
|
event.error(Errors.REALM_DISABLED);
|
||||||
}
|
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
|
||||||
|
}
|
||||||
protected Response basicChecks(String samlRequest, String samlResponse) {
|
|
||||||
if (!checkSsl()) {
|
if (samlRequest == null && samlResponse == null) {
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
event.error(Errors.SSL_REQUIRED);
|
event.error(Errors.INVALID_REQUEST);
|
||||||
return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
|
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
||||||
}
|
|
||||||
if (!realm.isEnabled()) {
|
}
|
||||||
event.event(EventType.LOGIN_ERROR);
|
return null;
|
||||||
event.error(Errors.REALM_DISABLED);
|
}
|
||||||
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
|
|
||||||
}
|
protected abstract String getBindingType();
|
||||||
|
protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
|
||||||
if (samlRequest == null && samlResponse == null) {
|
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
||||||
event.event(EventType.LOGIN);
|
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||||
event.error(Errors.INVALID_REQUEST);
|
protected PublicKey getIDPKey() {
|
||||||
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
X509Certificate certificate = null;
|
||||||
|
try {
|
||||||
}
|
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
|
||||||
return null;
|
} catch (ProcessingException e) {
|
||||||
}
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
protected abstract String getBindingType();
|
return certificate.getPublicKey();
|
||||||
protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
|
}
|
||||||
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
|
||||||
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||||
protected PublicKey getIDPKey() {
|
event = new EventBuilder(realm, session, clientConnection);
|
||||||
X509Certificate certificate = null;
|
Response response = basicChecks(samlRequest, samlResponse);
|
||||||
try {
|
if (response != null) return response;
|
||||||
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
|
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
||||||
} catch (ProcessingException e) {
|
else return handleSamlResponse(samlResponse, relayState);
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
}
|
|
||||||
return certificate.getPublicKey();
|
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
||||||
}
|
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
|
||||||
|
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
|
||||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
// validate destination
|
||||||
event = new EventBuilder(realm, session, clientConnection);
|
if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
|
||||||
Response response = basicChecks(samlRequest, samlResponse);
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
if (response != null) return response;
|
event.detail(Details.REASON, "invalid_destination");
|
||||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||||
else return handleSamlResponse(samlResponse, relayState);
|
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
||||||
}
|
}
|
||||||
|
if (config.isValidateSignature()) {
|
||||||
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
try {
|
||||||
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
|
verifySignature(GeneralConstants.SAML_REQUEST_KEY, holder);
|
||||||
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
|
} catch (VerificationException e) {
|
||||||
// validate destination
|
logger.error("validation failed", e);
|
||||||
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
event.error(Errors.INVALID_SIGNATURE);
|
||||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
|
||||||
event.detail(Details.REASON, "invalid_destination");
|
}
|
||||||
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
}
|
||||||
}
|
|
||||||
if (config.isValidateSignature()) {
|
if (requestAbstractType instanceof LogoutRequestType) {
|
||||||
try {
|
logger.debug("** logout request");
|
||||||
verifySignature(holder);
|
event.event(EventType.LOGOUT);
|
||||||
} catch (VerificationException e) {
|
LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
|
||||||
logger.error("validation failed", e);
|
return logoutRequest(logout, relayState);
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
|
||||||
event.error(Errors.INVALID_SIGNATURE);
|
} else {
|
||||||
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
|
event.event(EventType.LOGIN);
|
||||||
}
|
event.error(Errors.INVALID_TOKEN);
|
||||||
}
|
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
||||||
|
}
|
||||||
if (requestAbstractType instanceof LogoutRequestType) {
|
}
|
||||||
logger.debug("** logout request");
|
|
||||||
event.event(EventType.LOGOUT);
|
protected Response logoutRequest(LogoutRequestType request, String relayState) {
|
||||||
LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
|
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
|
||||||
return logoutRequest(logout, relayState);
|
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
|
||||||
|
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
|
||||||
} else {
|
for (UserSessionModel userSession : userSessions) {
|
||||||
event.event(EventType.LOGIN);
|
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
|
||||||
event.error(Errors.INVALID_TOKEN);
|
continue;
|
||||||
return ErrorPage.error(session, Messages.INVALID_REQUEST);
|
}
|
||||||
}
|
try {
|
||||||
}
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||||
|
} catch (Exception e) {
|
||||||
protected Response logoutRequest(LogoutRequestType request, String relayState) {
|
logger.warn("failed to do backchannel logout for userSession", e);
|
||||||
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
|
}
|
||||||
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
|
}
|
||||||
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
|
|
||||||
for (UserSessionModel userSession : userSessions) {
|
} else {
|
||||||
try {
|
for (String sessionIndex : request.getSessionIndex()) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
String brokerSessionId = brokerUserId + "." + sessionIndex;
|
||||||
} catch (Exception e) {
|
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||||
logger.warn("failed to do backchannel logout for userSession", e);
|
if (userSession != null) {
|
||||||
}
|
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
} else {
|
try {
|
||||||
for (String sessionIndex : request.getSessionIndex()) {
|
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
||||||
String brokerSessionId = brokerUserId + "." + sessionIndex;
|
} catch (Exception e) {
|
||||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
logger.warn("failed to do backchannel logout for userSession", e);
|
||||||
if (userSession != null) {
|
}
|
||||||
try {
|
}
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
logger.warn("failed to do backchannel logout for userSession", e);
|
|
||||||
}
|
String issuerURL = getEntityId(uriInfo, realm);
|
||||||
}
|
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
||||||
}
|
builder.logoutRequestID(request.getID());
|
||||||
}
|
builder.destination(config.getSingleLogoutServiceUrl());
|
||||||
|
builder.issuer(issuerURL);
|
||||||
String issuerURL = getEntityId(uriInfo, realm);
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||||
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
.relayState(relayState);
|
||||||
builder.logoutRequestID(request.getID());
|
if (config.isWantAuthnRequestsSigned()) {
|
||||||
builder.destination(config.getSingleLogoutServiceUrl());
|
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
builder.issuer(issuerURL);
|
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
||||||
builder.relayState(relayState);
|
.signDocument();
|
||||||
if (config.isWantAuthnRequestsSigned()) {
|
}
|
||||||
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
try {
|
||||||
.signDocument();
|
if (config.isPostBindingResponse()) {
|
||||||
}
|
return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
|
||||||
try {
|
} else {
|
||||||
if (config.isPostBindingResponse()) {
|
return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
|
||||||
return builder.postBinding().response();
|
}
|
||||||
} else {
|
} catch (ConfigurationException e) {
|
||||||
return builder.redirectBinding().response();
|
throw new RuntimeException(e);
|
||||||
}
|
} catch (ProcessingException e) {
|
||||||
} catch (ConfigurationException e) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException(e);
|
} catch (IOException e) {
|
||||||
} catch (ProcessingException e) {
|
throw new RuntimeException(e);
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
}
|
|
||||||
|
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
||||||
}
|
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
|
||||||
|
}
|
||||||
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
||||||
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
|
|
||||||
}
|
try {
|
||||||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
|
||||||
|
SubjectType subject = assertion.getSubject();
|
||||||
try {
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
AssertionType assertion = getAssertion(responseType);
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
SubjectType subject = assertion.getSubject();
|
//Map<String, String> notes = new HashMap<>();
|
||||||
SubjectType.STSubType subType = subject.getSubType();
|
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
identity.setCode(relayState);
|
||||||
//Map<String, String> notes = new HashMap<>();
|
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
||||||
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
identity.getContextData().put(SAML_ASSERTION, assertion);
|
||||||
identity.setCode(relayState);
|
|
||||||
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
identity.setUsername(subjectNameID.getValue());
|
||||||
identity.getContextData().put(SAML_ASSERTION, assertion);
|
|
||||||
|
//SAML Spec 2.2.2 Format is optional
|
||||||
identity.setUsername(subjectNameID.getValue());
|
if (subjectNameID.getFormat() != null && subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||||
|
identity.setEmail(subjectNameID.getValue());
|
||||||
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
}
|
||||||
identity.setEmail(subjectNameID.getValue());
|
|
||||||
}
|
if (config.isStoreToken()) {
|
||||||
|
identity.setToken(samlResponse);
|
||||||
if (config.isStoreToken()) {
|
}
|
||||||
identity.setToken(samlResponse);
|
|
||||||
}
|
AuthnStatementType authn = null;
|
||||||
|
for (Object statement : assertion.getStatements()) {
|
||||||
AuthnStatementType authn = null;
|
if (statement instanceof AuthnStatementType) {
|
||||||
for (Object statement : assertion.getStatements()) {
|
authn = (AuthnStatementType)statement;
|
||||||
if (statement instanceof AuthnStatementType) {
|
identity.getContextData().put(SAML_AUTHN_STATEMENT, authn);
|
||||||
authn = (AuthnStatementType)statement;
|
break;
|
||||||
identity.getContextData().put(SAML_AUTHN_STATEMENT, authn);
|
}
|
||||||
break;
|
}
|
||||||
}
|
if (assertion.getAttributeStatements() != null ) {
|
||||||
}
|
for (AttributeStatementType attrStatement : assertion.getAttributeStatements()) {
|
||||||
if (assertion.getAttributeStatements() != null ) {
|
for (AttributeStatementType.ASTChoiceType choice : attrStatement.getAttributes()) {
|
||||||
for (AttributeStatementType attrStatement : assertion.getAttributeStatements()) {
|
AttributeType attribute = choice.getAttribute();
|
||||||
for (AttributeStatementType.ASTChoiceType choice : attrStatement.getAttributes()) {
|
if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attribute.getFriendlyName())
|
||||||
AttributeType attribute = choice.getAttribute();
|
|| X500SAMLProfileConstants.EMAIL.get().equals(attribute.getName())) {
|
||||||
if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attribute.getFriendlyName())
|
if (!attribute.getAttributeValue().isEmpty()) identity.setEmail(attribute.getAttributeValue().get(0).toString());
|
||||||
|| X500SAMLProfileConstants.EMAIL.get().equals(attribute.getName())) {
|
}
|
||||||
if (!attribute.getAttributeValue().isEmpty()) identity.setEmail(attribute.getAttributeValue().get(0).toString());
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
|
||||||
}
|
identity.setBrokerUserId(brokerUserId);
|
||||||
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
|
identity.setIdpConfig(config);
|
||||||
identity.setBrokerUserId(brokerUserId);
|
identity.setIdp(provider);
|
||||||
identity.setIdpConfig(config);
|
if (authn != null && authn.getSessionIndex() != null) {
|
||||||
identity.setIdp(provider);
|
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
||||||
if (authn != null && authn.getSessionIndex() != null) {
|
}
|
||||||
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
|
||||||
}
|
|
||||||
|
return callback.authenticated(identity);
|
||||||
|
|
||||||
return callback.authenticated(identity);
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
|
||||||
} catch (Exception e) {
|
}
|
||||||
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
public Response handleSamlResponse(String samlResponse, String relayState) {
|
||||||
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
|
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||||
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
|
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
||||||
|
// validate destination
|
||||||
if (assertions.isEmpty()) {
|
if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
||||||
throw new IdentityBrokerException("No assertion from response.");
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
}
|
event.detail(Details.REASON, "invalid_destination");
|
||||||
|
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||||
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
|
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
||||||
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
|
}
|
||||||
|
if (config.isValidateSignature()) {
|
||||||
if (encryptedAssertion != null) {
|
try {
|
||||||
decryptAssertion(responseType, realm.getPrivateKey());
|
verifySignature(GeneralConstants.SAML_RESPONSE_KEY, holder);
|
||||||
|
} catch (VerificationException e) {
|
||||||
}
|
logger.error("validation failed", e);
|
||||||
return responseType.getAssertions().get(0).getAssertion();
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
}
|
event.error(Errors.INVALID_SIGNATURE);
|
||||||
|
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
||||||
public Response handleSamlResponse(String samlResponse, String relayState) {
|
}
|
||||||
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
}
|
||||||
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
if (statusResponse instanceof ResponseType) {
|
||||||
// validate destination
|
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
|
||||||
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
} else {
|
||||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
// todo need to check that it is actually a LogoutResponse
|
||||||
event.detail(Details.REASON, "invalid_destination");
|
return handleLogoutResponse(holder, statusResponse, relayState);
|
||||||
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
}
|
||||||
}
|
//throw new RuntimeException("Unknown response type");
|
||||||
if (config.isValidateSignature()) {
|
|
||||||
try {
|
}
|
||||||
verifySignature(holder);
|
|
||||||
} catch (VerificationException e) {
|
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
|
||||||
logger.error("validation failed", e);
|
if (relayState == null) {
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
logger.error("no valid user session");
|
||||||
event.error(Errors.INVALID_SIGNATURE);
|
event.event(EventType.LOGOUT);
|
||||||
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
}
|
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||||
}
|
}
|
||||||
if (statusResponse instanceof ResponseType) {
|
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
|
||||||
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
|
if (userSession == null) {
|
||||||
|
logger.error("no valid user session");
|
||||||
} else {
|
event.event(EventType.LOGOUT);
|
||||||
// todo need to check that it is actually a LogoutResponse
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
return handleLogoutResponse(holder, statusResponse, relayState);
|
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||||
}
|
}
|
||||||
//throw new RuntimeException("Unknown response type");
|
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||||
|
logger.error("usersession in different state");
|
||||||
}
|
event.event(EventType.LOGOUT);
|
||||||
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
|
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
|
||||||
if (relayState == null) {
|
}
|
||||||
logger.error("no valid user session");
|
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
event.event(EventType.LOGOUT);
|
}
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
|
||||||
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
|
||||||
}
|
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
|
|
||||||
if (userSession == null) {
|
|
||||||
logger.error("no valid user session");
|
}
|
||||||
event.event(EventType.LOGOUT);
|
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
protected class PostBinding extends Binding {
|
||||||
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
@Override
|
||||||
}
|
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
||||||
logger.error("usersession in different state");
|
}
|
||||||
event.event(EventType.LOGOUT);
|
|
||||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
@Override
|
||||||
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
}
|
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
|
||||||
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
}
|
||||||
}
|
@Override
|
||||||
|
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||||
|
byte[] samlBytes = PostBindingUtil.base64Decode(response);
|
||||||
protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
|
String xml = new String(samlBytes);
|
||||||
SAML2Response saml2Response = new SAML2Response();
|
return SAMLRequestParser.parseResponseDocument(samlBytes);
|
||||||
|
}
|
||||||
try {
|
|
||||||
Document doc = saml2Response.convert(responseType);
|
@Override
|
||||||
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
protected String getBindingType() {
|
||||||
|
return SamlProtocol.SAML_POST_BINDING;
|
||||||
if (enc == null) {
|
}
|
||||||
throw new IdentityBrokerException("No encrypted assertion found.");
|
}
|
||||||
}
|
|
||||||
|
protected class RedirectBinding extends Binding {
|
||||||
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
|
@Override
|
||||||
Document newDoc = DocumentUtil.createDocument();
|
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
Node importedNode = newDoc.importNode(enc, true);
|
PublicKey publicKey = getIDPKey();
|
||||||
newDoc.appendChild(importedNode);
|
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
|
||||||
|
}
|
||||||
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
|
|
||||||
SAMLParser parser = new SAMLParser();
|
|
||||||
|
|
||||||
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
|
@Override
|
||||||
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
.getNodeAsStream(decryptedDocumentElement)));
|
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||||
|
}
|
||||||
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
|
||||||
|
@Override
|
||||||
return responseType;
|
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||||
} catch (Exception e) {
|
return SAMLRequestParser.parseRequestRedirectBinding(response);
|
||||||
throw new IdentityBrokerException("Could not decrypt assertion.", e);
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
|
protected String getBindingType() {
|
||||||
|
return SamlProtocol.SAML_REDIRECT_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class PostBinding extends Binding {
|
}
|
||||||
@Override
|
|
||||||
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
}
|
||||||
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
|
||||||
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
|
||||||
byte[] samlBytes = PostBindingUtil.base64Decode(response);
|
|
||||||
String xml = new String(samlBytes);
|
|
||||||
return SAMLRequestParser.parseResponseDocument(samlBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getBindingType() {
|
|
||||||
return SamlProtocol.SAML_POST_BINDING;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class RedirectBinding extends Binding {
|
|
||||||
@Override
|
|
||||||
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
|
||||||
PublicKey publicKey = getIDPKey();
|
|
||||||
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
|
||||||
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
|
||||||
return SAMLRequestParser.parseRequestRedirectBinding(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getBindingType() {
|
|
||||||
return SamlProtocol.SAML_REDIRECT_BINDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -33,20 +33,18 @@ import org.keycloak.models.ClientSessionModel;
|
|||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||||
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
|
import org.keycloak.saml.SAML2AuthnRequestBuilder;
|
||||||
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
import org.keycloak.saml.SAML2LogoutRequestBuilder;
|
||||||
|
import org.keycloak.saml.SAML2NameIDPolicyBuilder;
|
||||||
|
import org.keycloak.saml.SignatureAlgorithm;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@@ -92,7 +90,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
.issuer(issuerURL)
|
.issuer(issuerURL)
|
||||||
.forceAuthn(getConfig().isForceAuthn())
|
.forceAuthn(getConfig().isForceAuthn())
|
||||||
.protocolBinding(protocolBinding)
|
.protocolBinding(protocolBinding)
|
||||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
|
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
||||||
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||||
.relayState(request.getState());
|
.relayState(request.getState());
|
||||||
|
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
@@ -109,14 +108,15 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
|
|
||||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
||||||
|
|
||||||
authnRequestBuilder.signWith(keypair);
|
binding.signWith(keypair);
|
||||||
authnRequestBuilder.signDocument();
|
binding.signatureAlgorithm(getSignatureAlgorithm());
|
||||||
|
binding.signDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getConfig().isPostBindingAuthnRequest()) {
|
if (getConfig().isPostBindingAuthnRequest()) {
|
||||||
return authnRequestBuilder.postBinding().request();
|
return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
|
||||||
} else {
|
} else {
|
||||||
return authnRequestBuilder.redirectBinding().request();
|
return binding.redirectBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||||
@@ -153,9 +153,10 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
|
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
||||||
try {
|
try {
|
||||||
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
||||||
.param(GeneralConstants.SAML_REQUEST_KEY, logoutBuilder.postBinding().encoded())
|
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
|
||||||
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
|
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
|
||||||
boolean success = status >=200 && status < 400;
|
boolean success = status >=200 && status < 400;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -178,7 +179,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
return logoutBuilder.postBinding().request();
|
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
||||||
|
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -192,13 +194,19 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
.issuer(getEntityId(uriInfo, realm))
|
.issuer(getEntityId(uriInfo, realm))
|
||||||
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||||
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
||||||
.destination(singleLogoutServiceUrl)
|
.destination(singleLogoutServiceUrl);
|
||||||
|
return logoutBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
|
||||||
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||||
.relayState(userSession.getId());
|
.relayState(userSession.getId());
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
|
.signatureAlgorithm(getSignatureAlgorithm())
|
||||||
.signDocument();
|
.signDocument();
|
||||||
}
|
}
|
||||||
return logoutBuilder;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -220,7 +228,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
|
|
||||||
|
|
||||||
String descriptor =
|
String descriptor =
|
||||||
"<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
|
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
|
||||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
|
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
|
||||||
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
||||||
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
||||||
@@ -245,4 +253,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
"</EntityDescriptor>\n";
|
"</EntityDescriptor>\n";
|
||||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SignatureAlgorithm getSignatureAlgorithm() {
|
||||||
|
String alg = getConfig().getSignatureAlgorithm();
|
||||||
|
if (alg != null) {
|
||||||
|
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
|
||||||
|
if (algorithm != null) return algorithm;
|
||||||
|
}
|
||||||
|
return SignatureAlgorithm.RSA_SHA256;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
|||||||
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSignatureAlgorithm() {
|
||||||
|
return getConfig().get("signatureAlgorithm");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureAlgorithm(String signatureAlgorithm) {
|
||||||
|
getConfig().put("signatureAlgorithm", signatureAlgorithm);
|
||||||
|
}
|
||||||
|
|
||||||
public String getEncryptionPublicKey() {
|
public String getEncryptionPublicKey() {
|
||||||
return getConfig().get("encryptionPublicKey");
|
return getConfig().get("encryptionPublicKey");
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-65
@@ -18,13 +18,17 @@
|
|||||||
package org.keycloak.broker.saml;
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||||
|
import org.keycloak.broker.saml.mappers.UsernameTemplateMapper;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
import org.keycloak.dom.saml.v2.metadata.EndpointType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
|
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
import org.keycloak.saml.common.util.DocumentUtil;
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
@@ -69,75 +73,79 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
|
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
|
||||||
|
|
||||||
if (!choiceType.isEmpty()) {
|
if (!choiceType.isEmpty()) {
|
||||||
EntityDescriptorType.EDTChoiceType edtChoiceType = choiceType.get(0);
|
IDPSSODescriptorType idpDescriptor = null;
|
||||||
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
|
|
||||||
|
|
||||||
if (!descriptors.isEmpty()) {
|
//Metadata documents can contain multiple Descriptors (See ADFS metadata documents) such as RoleDescriptor, SPSSODescriptor, IDPSSODescriptor.
|
||||||
EntityDescriptorType.EDTDescriptorChoiceType edtDescriptorChoiceType = descriptors.get(0);
|
//So we need to loop through to find the IDPSSODescriptor.
|
||||||
IDPSSODescriptorType idpDescriptor = edtDescriptorChoiceType.getIdpDescriptor();
|
for(EntityDescriptorType.EDTChoiceType edtChoiceType : entityType.getChoiceType()) {
|
||||||
|
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
|
||||||
|
|
||||||
if (idpDescriptor != null) {
|
if(!descriptors.isEmpty() && descriptors.get(0).getIdpDescriptor() != null) {
|
||||||
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
|
idpDescriptor = descriptors.get(0).getIdpDescriptor();
|
||||||
String singleSignOnServiceUrl = null;
|
|
||||||
boolean postBinding = false;
|
|
||||||
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
|
|
||||||
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
|
||||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
|
||||||
postBinding = true;
|
|
||||||
break;
|
|
||||||
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
|
||||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String singleLogoutServiceUrl = null;
|
|
||||||
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
|
||||||
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
|
||||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
|
||||||
break;
|
|
||||||
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
|
||||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
|
||||||
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
|
||||||
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
|
||||||
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
|
||||||
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
|
||||||
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
|
||||||
|
|
||||||
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
|
|
||||||
String defaultCertificate = null;
|
|
||||||
|
|
||||||
if (keyDescriptor != null) {
|
|
||||||
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
|
|
||||||
Element keyInfo = keyDescriptorType.getKeyInfo();
|
|
||||||
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
|
||||||
|
|
||||||
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
|
||||||
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
|
||||||
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
|
||||||
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
|
||||||
} else if (keyDescriptorType.getUse() == null) {
|
|
||||||
defaultCertificate = x509KeyInfo.getTextContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultCertificate != null) {
|
|
||||||
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
|
||||||
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
|
||||||
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return samlIdentityProviderConfig.getConfig();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (idpDescriptor != null) {
|
||||||
|
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
|
||||||
|
String singleSignOnServiceUrl = null;
|
||||||
|
boolean postBinding = false;
|
||||||
|
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
|
||||||
|
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||||
|
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||||
|
postBinding = true;
|
||||||
|
break;
|
||||||
|
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||||
|
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String singleLogoutServiceUrl = null;
|
||||||
|
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
||||||
|
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
||||||
|
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
||||||
|
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
||||||
|
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
||||||
|
|
||||||
|
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
|
||||||
|
String defaultCertificate = null;
|
||||||
|
|
||||||
|
if (keyDescriptor != null) {
|
||||||
|
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
|
||||||
|
Element keyInfo = keyDescriptorType.getKeyInfo();
|
||||||
|
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
||||||
|
|
||||||
|
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
||||||
|
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
||||||
|
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
||||||
|
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
||||||
|
} else if (keyDescriptorType.getUse() == null) {
|
||||||
|
defaultCertificate = x509KeyInfo.getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultCertificate != null) {
|
||||||
|
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
||||||
|
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
||||||
|
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return samlIdentityProviderConfig.getConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ParsingException pe) {
|
} catch (ParsingException pe) {
|
||||||
throw new RuntimeException("Could not parse IdP SAML Metadata", pe);
|
throw new RuntimeException("Could not parse IdP SAML Metadata", pe);
|
||||||
@@ -150,4 +158,5 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+140
-140
@@ -1,140 +1,140 @@
|
|||||||
package org.keycloak.broker.saml.mappers;
|
package org.keycloak.broker.saml.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
import org.keycloak.broker.provider.HardcodedRoleMapper;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||||
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
||||||
|
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
|
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
public static final String ATTRIBUTE_NAME = "attribute.name";
|
public static final String ATTRIBUTE_NAME = "attribute.name";
|
||||||
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
|
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
|
||||||
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
public static final String ATTRIBUTE_VALUE = "attribute.value";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(ATTRIBUTE_NAME);
|
property.setName(ATTRIBUTE_NAME);
|
||||||
property.setLabel("Attribute Name");
|
property.setLabel("Attribute Name");
|
||||||
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
|
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(ATTRIBUTE_FRIENDLY_NAME);
|
property.setName(ATTRIBUTE_FRIENDLY_NAME);
|
||||||
property.setLabel("Friendly Name");
|
property.setLabel("Friendly Name");
|
||||||
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
|
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(ATTRIBUTE_VALUE);
|
property.setName(ATTRIBUTE_VALUE);
|
||||||
property.setLabel("Attribute Value");
|
property.setLabel("Attribute Value");
|
||||||
property.setHelpText("Value the attribute must have. If the attribute is a list, then the value must be contained in the list.");
|
property.setHelpText("Value the attribute must have. If the attribute is a list, then the value must be contained in the list.");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
property = new ProviderConfigProperty();
|
property = new ProviderConfigProperty();
|
||||||
property.setName(HardcodedRoleMapper.ROLE);
|
property.setName(HardcodedRoleMapper.ROLE);
|
||||||
property.setLabel("Role");
|
property.setLabel("Role");
|
||||||
property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
property.setHelpText("Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setType(ProviderConfigProperty.ROLE_TYPE);
|
||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String PROVIDER_ID = "saml-role-idp-mapper";
|
public static final String PROVIDER_ID = "saml-role-idp-mapper";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return configProperties;
|
return configProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return PROVIDER_ID;
|
return PROVIDER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getCompatibleProviders() {
|
public String[] getCompatibleProviders() {
|
||||||
return COMPATIBLE_PROVIDERS;
|
return COMPATIBLE_PROVIDERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayCategory() {
|
public String getDisplayCategory() {
|
||||||
return "Role Mapper";
|
return "Role Mapper";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayType() {
|
public String getDisplayType() {
|
||||||
return "SAML Attribute to Role";
|
return "SAML Attribute to Role";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (isAttributePresent(mapperModel, context)) {
|
if (isAttributePresent(mapperModel, context)) {
|
||||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isAttributePresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
protected boolean isAttributePresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
|
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
|
||||||
if (name != null && name.trim().equals("")) name = null;
|
if (name != null && name.trim().equals("")) name = null;
|
||||||
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
|
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
|
||||||
if (friendly != null && friendly.trim().equals("")) friendly = null;
|
if (friendly != null && friendly.trim().equals("")) friendly = null;
|
||||||
String desiredValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
String desiredValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
|
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
|
||||||
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
|
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
|
||||||
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
|
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
|
||||||
AttributeType attr = choice.getAttribute();
|
AttributeType attr = choice.getAttribute();
|
||||||
if (name != null && !name.equals(attr.getName())) continue;
|
if (name != null && !name.equals(attr.getName())) continue;
|
||||||
if (friendly != null && !name.equals(attr.getFriendlyName())) continue;
|
if (friendly != null && !name.equals(attr.getFriendlyName())) continue;
|
||||||
for (Object val : attr.getAttributeValue()) {
|
for (Object val : attr.getAttributeValue()) {
|
||||||
if (val.equals(desiredValue)) return true;
|
if (val.equals(desiredValue)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (!isAttributePresent(mapperModel, context)) {
|
if (!isAttributePresent(mapperModel, context)) {
|
||||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.deleteRoleMapping(role);
|
user.deleteRoleMapping(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "If a claim exists, grant the user the specified realm or application role.";
|
return "If a claim exists, grant the user the specified realm or application role.";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+129
-132
@@ -1,132 +1,129 @@
|
|||||||
package org.keycloak.broker.saml.mappers;
|
package org.keycloak.broker.saml.mappers;
|
||||||
|
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||||
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import java.util.ArrayList;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import java.util.List;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
/**
|
||||||
import java.util.List;
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
/**
|
*/
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
|
||||||
public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
|
|
||||||
|
public static final String ATTRIBUTE_NAME = "attribute.name";
|
||||||
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
|
||||||
|
public static final String USER_ATTRIBUTE = "user.attribute";
|
||||||
public static final String ATTRIBUTE_NAME = "attribute.name";
|
|
||||||
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
|
static {
|
||||||
public static final String USER_ATTRIBUTE = "user.attribute";
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
static {
|
property.setName(ATTRIBUTE_NAME);
|
||||||
ProviderConfigProperty property;
|
property.setLabel("Attribute Name");
|
||||||
property = new ProviderConfigProperty();
|
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
|
||||||
property.setName(ATTRIBUTE_NAME);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
property.setLabel("Attribute Name");
|
configProperties.add(property);
|
||||||
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
|
property = new ProviderConfigProperty();
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setName(ATTRIBUTE_FRIENDLY_NAME);
|
||||||
configProperties.add(property);
|
property.setLabel("Friendly Name");
|
||||||
property = new ProviderConfigProperty();
|
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
|
||||||
property.setName(ATTRIBUTE_FRIENDLY_NAME);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
property.setLabel("Friendly Name");
|
configProperties.add(property);
|
||||||
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
|
property = new ProviderConfigProperty();
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
property.setName(USER_ATTRIBUTE);
|
||||||
configProperties.add(property);
|
property.setLabel("User Attribute Name");
|
||||||
property = new ProviderConfigProperty();
|
property.setHelpText("User attribute name to store saml attribute.");
|
||||||
property.setName(USER_ATTRIBUTE);
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
property.setLabel("User Attribute Name");
|
configProperties.add(property);
|
||||||
property.setHelpText("User attribute name to store saml attribute.");
|
}
|
||||||
property.setType(ProviderConfigProperty.STRING_TYPE);
|
|
||||||
configProperties.add(property);
|
public static final String PROVIDER_ID = "saml-user-attribute-idp-mapper";
|
||||||
}
|
|
||||||
|
@Override
|
||||||
public static final String PROVIDER_ID = "saml-user-attribute-idp-mapper";
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
@Override
|
}
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
|
||||||
return configProperties;
|
@Override
|
||||||
}
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
@Override
|
}
|
||||||
public String getId() {
|
|
||||||
return PROVIDER_ID;
|
@Override
|
||||||
}
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
@Override
|
}
|
||||||
public String[] getCompatibleProviders() {
|
|
||||||
return COMPATIBLE_PROVIDERS;
|
@Override
|
||||||
}
|
public String getDisplayCategory() {
|
||||||
|
return "Attribute Importer";
|
||||||
@Override
|
}
|
||||||
public String getDisplayCategory() {
|
|
||||||
return "Attribute Importer";
|
@Override
|
||||||
}
|
public String getDisplayType() {
|
||||||
|
return "Attribute Importer";
|
||||||
@Override
|
}
|
||||||
public String getDisplayType() {
|
|
||||||
return "Attribute Importer";
|
@Override
|
||||||
}
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
@Override
|
Object value = getAttribute(mapperModel, context);
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
if (value != null) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
Object value = getAttribute(mapperModel, context);
|
}
|
||||||
if (value != null) {
|
}
|
||||||
user.setAttribute(attribute, value.toString());
|
|
||||||
}
|
protected String getAttribute(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
}
|
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
|
||||||
|
if (name != null && name.trim().equals("")) name = null;
|
||||||
protected String getAttribute(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
|
||||||
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
|
if (friendly != null && friendly.trim().equals("")) friendly = null;
|
||||||
if (name != null && name.trim().equals("")) name = null;
|
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
|
||||||
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
|
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
|
||||||
if (friendly != null && friendly.trim().equals("")) friendly = null;
|
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
|
||||||
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
|
AttributeType attr = choice.getAttribute();
|
||||||
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
|
if (name != null && !name.equals(attr.getName())) continue;
|
||||||
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
|
if (friendly != null && !friendly.equals(attr.getFriendlyName())) continue;
|
||||||
AttributeType attr = choice.getAttribute();
|
|
||||||
if (name != null && !name.equals(attr.getName())) continue;
|
List<Object> attributeValue = attr.getAttributeValue();
|
||||||
if (friendly != null && !name.equals(attr.getFriendlyName())) continue;
|
if (attributeValue == null || attributeValue.isEmpty()) return null;
|
||||||
|
return attributeValue.get(0).toString();
|
||||||
List<Object> attributeValue = attr.getAttributeValue();
|
}
|
||||||
if (attributeValue == null || attributeValue.isEmpty()) return null;
|
}
|
||||||
return attributeValue.get(0).toString();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null;
|
@Override
|
||||||
}
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
@Override
|
Object value = getAttribute(mapperModel, context);
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
String current = user.getFirstAttribute(attribute);
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
if (value != null && !value.equals(current)) {
|
||||||
Object value = getAttribute(mapperModel, context);
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
String current = user.getAttribute(attribute);
|
} else if (value == null) {
|
||||||
if (value != null && !value.equals(current)) {
|
user.removeAttribute(attribute);
|
||||||
user.setAttribute(attribute, value.toString());
|
}
|
||||||
} else if (value == null) {
|
|
||||||
user.removeAttribute(attribute);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
}
|
public String getHelpText() {
|
||||||
|
return "Import declared saml attribute if it exists in assertion into the specified user attribute.";
|
||||||
@Override
|
}
|
||||||
public String getHelpText() {
|
|
||||||
return "Import declared saml attribute if it exists in assertion into the specified user attribute.";
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
package org.keycloak.broker.saml.mappers;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||||
|
import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
||||||
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
|
||||||
|
|
||||||
|
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
|
||||||
|
|
||||||
|
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
|
public static final String TEMPLATE = "template";
|
||||||
|
|
||||||
|
static {
|
||||||
|
ProviderConfigProperty property;
|
||||||
|
property = new ProviderConfigProperty();
|
||||||
|
property.setName(TEMPLATE);
|
||||||
|
property.setLabel("Template");
|
||||||
|
property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${NAMEID}'. ALIAS is the provider alias. NAMEID is that SAML name id assertion. ATTRIBUTE.<NAME> references a SAML attribute where name is the attribute name or friendly name.");
|
||||||
|
property.setType(ProviderConfigProperty.STRING_TYPE);
|
||||||
|
property.setDefaultValue("${ALIAS}.${NAMEID}");
|
||||||
|
configProperties.add(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "saml-username-idp-mapper";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return configProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getCompatibleProviders() {
|
||||||
|
return COMPATIBLE_PROVIDERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayCategory() {
|
||||||
|
return "Preprocessor";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Username Template Importer";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
|
||||||
|
String template = mapperModel.getConfig().get(TEMPLATE);
|
||||||
|
Matcher m = substitution.matcher(template);
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
while (m.find()) {
|
||||||
|
String variable = m.group(1);
|
||||||
|
if (variable.equals("ALIAS")) {
|
||||||
|
m.appendReplacement(sb, context.getIdpConfig().getAlias());
|
||||||
|
} else if (variable.equals("UUID")) {
|
||||||
|
m.appendReplacement(sb, KeycloakModelUtils.generateId());
|
||||||
|
} else if (variable.equals("NAMEID")) {
|
||||||
|
SubjectType subject = assertion.getSubject();
|
||||||
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
|
m.appendReplacement(sb, subjectNameID.getValue());
|
||||||
|
} else if (variable.startsWith("ATTRIBUTE.")) {
|
||||||
|
String name = variable.substring("ATTRIBUTE.".length());
|
||||||
|
String value = "";
|
||||||
|
for (AttributeStatementType statement : assertion.getAttributeStatements()) {
|
||||||
|
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
|
||||||
|
AttributeType attr = choice.getAttribute();
|
||||||
|
if (name.equals(attr.getName()) || name.equals(attr.getFriendlyName())) {
|
||||||
|
List<Object> attributeValue = attr.getAttributeValue();
|
||||||
|
if (attributeValue != null && !attributeValue.isEmpty()) {
|
||||||
|
value = attributeValue.get(0).toString();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.appendReplacement(sb, value);
|
||||||
|
} else {
|
||||||
|
m.appendReplacement(sb, m.group(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
m.appendTail(sb);
|
||||||
|
context.setModelUsername(sb.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Format the username to import.";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+3
-2
@@ -1,2 +1,3 @@
|
|||||||
org.keycloak.broker.saml.mappers.AttributeToRoleMapper
|
org.keycloak.broker.saml.mappers.AttributeToRoleMapper
|
||||||
org.keycloak.broker.saml.mappers.UserAttributeMapper
|
org.keycloak.broker.saml.mappers.UserAttributeMapper
|
||||||
|
org.keycloak.broker.saml.mappers.UsernameTemplateMapper
|
||||||
@@ -1,29 +1,31 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
</parent>
|
||||||
</parent>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
<artifactId>keycloak-client-api</artifactId>
|
||||||
<artifactId>keycloak-model-sessions-mem</artifactId>
|
<name>Keycloak Client API</name>
|
||||||
<name>Keycloak Model Sessions Mem</name>
|
<description/>
|
||||||
<description/>
|
|
||||||
|
<dependencies>
|
||||||
<dependencies>
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.keycloak</groupId>
|
||||||
<groupId>org.keycloak</groupId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
</dependency>
|
||||||
<scope>provided</scope>
|
<dependency>
|
||||||
</dependency>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<dependency>
|
<artifactId>httpclient</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
</dependency>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<dependency>
|
||||||
<scope>provided</scope>
|
<groupId>junit</groupId>
|
||||||
</dependency>
|
<artifactId>junit</artifactId>
|
||||||
</dependencies>
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</project>
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package org.keycloak.client.registration;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpDelete;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpPut;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.common.util.Base64;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ClientRegistration {
|
||||||
|
|
||||||
|
private String clientRegistrationUrl;
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private Auth auth;
|
||||||
|
|
||||||
|
public static ClientRegistrationBuilder create() {
|
||||||
|
return new ClientRegistrationBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientRegistration() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
|
String content = serialize(client);
|
||||||
|
InputStream resultStream = doPost(content);
|
||||||
|
return deserialize(resultStream, ClientRepresentation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRepresentation get() throws ClientRegistrationException {
|
||||||
|
if (auth instanceof ClientIdSecretAuth) {
|
||||||
|
String clientId = ((ClientIdSecretAuth) auth).clientId;
|
||||||
|
return get(clientId);
|
||||||
|
} else {
|
||||||
|
throw new ClientRegistrationException("Requires client authentication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||||
|
InputStream resultStream = doGet(clientId);
|
||||||
|
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
|
String content = serialize(client);
|
||||||
|
doPut(content, client.getClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete() throws ClientRegistrationException {
|
||||||
|
if (auth instanceof ClientIdSecretAuth) {
|
||||||
|
String clientId = ((ClientIdSecretAuth) auth).clientId;
|
||||||
|
delete(clientId);
|
||||||
|
} else {
|
||||||
|
throw new ClientRegistrationException("Requires client authentication");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String clientId) throws ClientRegistrationException {
|
||||||
|
doDelete(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws ClientRegistrationException {
|
||||||
|
if (httpClient instanceof CloseableHttpClient) {
|
||||||
|
try {
|
||||||
|
((CloseableHttpClient) httpClient).close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to close http client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream doPost(String content) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpPost request = new HttpPost(clientRegistrationUrl);
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
|
auth.addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
InputStream responseStream = null;
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
responseStream = response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == 201) {
|
||||||
|
return responseStream;
|
||||||
|
} else {
|
||||||
|
responseStream.close();
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream doGet(String endpoint) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint);
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
|
||||||
|
auth.addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
InputStream responseStream = null;
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
responseStream = response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return responseStream;
|
||||||
|
} else if (response.getStatusLine().getStatusCode() == 404) {
|
||||||
|
responseStream.close();
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
responseStream.close();
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPut(String content, String endpoint) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint);
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
|
auth.addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
response.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() != 200) {
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDelete(String endpoint) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint);
|
||||||
|
|
||||||
|
auth.addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
response.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() != 200) {
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
return JsonSerialization.writeValueAsString(client);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to write json object", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(inputStream, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to read json object", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClientRegistrationBuilder {
|
||||||
|
|
||||||
|
private String realm;
|
||||||
|
|
||||||
|
private String authServerUrl;
|
||||||
|
|
||||||
|
private Auth auth;
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
public ClientRegistrationBuilder realm(String realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
|
||||||
|
this.authServerUrl = authServerUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationBuilder auth(String token) {
|
||||||
|
this.auth = new TokenAuth(token);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
|
||||||
|
this.auth = new ClientIdSecretAuth(clientId, clientSecret);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistration build() {
|
||||||
|
ClientRegistration clientRegistration = new ClientRegistration();
|
||||||
|
clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
|
||||||
|
|
||||||
|
clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
|
||||||
|
clientRegistration.auth = auth;
|
||||||
|
|
||||||
|
return clientRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Auth {
|
||||||
|
void addAuth(HttpRequest httpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AuthorizationHeaderAuth implements Auth {
|
||||||
|
private String credentials;
|
||||||
|
|
||||||
|
public AuthorizationHeaderAuth(String credentials) {
|
||||||
|
this.credentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAuth(HttpRequest httpRequest) {
|
||||||
|
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TokenAuth extends AuthorizationHeaderAuth {
|
||||||
|
public TokenAuth(String token) {
|
||||||
|
super("Bearer " + token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
public ClientIdSecretAuth(String clientId, String clientSecret) {
|
||||||
|
super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package org.keycloak.client.registration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ClientRegistrationException extends Exception {
|
||||||
|
|
||||||
|
public ClientRegistrationException(String s, Throwable throwable) {
|
||||||
|
super(s, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientRegistrationException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.keycloak.client.registration;
|
||||||
|
|
||||||
|
import org.apache.http.StatusLine;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class HttpErrorException extends IOException {
|
||||||
|
|
||||||
|
private StatusLine statusLine;
|
||||||
|
|
||||||
|
public HttpErrorException(StatusLine statusLine) {
|
||||||
|
this.statusLine = statusLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusLine getStatusLine() {
|
||||||
|
return statusLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Executable
+94
@@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<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/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.6.1.Final</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-common</artifactId>
|
||||||
|
<name>Keycloak Common</name>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>Common library and dependencies shared with server and all adapters</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<timestamp>${maven.build.timestamp}</timestamp>
|
||||||
|
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
||||||
|
<keycloak.osgi.export>
|
||||||
|
org.keycloak.common.*
|
||||||
|
</keycloak.osgi.export>
|
||||||
|
<keycloak.osgi.import>
|
||||||
|
*;resolution:=optional
|
||||||
|
</keycloak.osgi.import>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.source}</source>
|
||||||
|
<target>${maven.compiler.target}</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>bundle-manifest</id>
|
||||||
|
<phase>process-classes</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>manifest</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Bundle-ClassPath>.</Bundle-ClassPath>
|
||||||
|
<Bundle-Name>${project.name}</Bundle-Name>
|
||||||
|
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||||
|
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||||
|
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+13
-13
@@ -1,13 +1,13 @@
|
|||||||
package org.keycloak;
|
package org.keycloak.common;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information about the client connection
|
* Information about the client connection
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface ClientConnection {
|
public interface ClientConnection {
|
||||||
String getRemoteAddr();
|
String getRemoteAddr();
|
||||||
String getRemoteHost();
|
String getRemoteHost();
|
||||||
int getReportPort();
|
int getReportPort();
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak;
|
package org.keycloak.common;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package org.keycloak.common;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class Version {
|
||||||
|
public static final String UNKNOWN = "UNKNOWN";
|
||||||
|
public static String VERSION;
|
||||||
|
public static String RESOURCES_VERSION;
|
||||||
|
public static String BUILD_TIME;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Properties props = new Properties();
|
||||||
|
InputStream is = Version.class.getResourceAsStream("/keycloak-version.properties");
|
||||||
|
try {
|
||||||
|
props.load(is);
|
||||||
|
Version.VERSION = props.getProperty("version");
|
||||||
|
Version.BUILD_TIME = props.getProperty("build-time");
|
||||||
|
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
||||||
|
if (Version.RESOURCES_VERSION.endsWith("-snapshot")) {
|
||||||
|
Version.RESOURCES_VERSION = Version.RESOURCES_VERSION.replace("-snapshot", "-" + Time.currentTime());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Version.VERSION= Version.UNKNOWN;
|
||||||
|
Version.BUILD_TIME= Version.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.constants;
|
package org.keycloak.common.constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.constants;
|
package org.keycloak.common.constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.keycloak.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface ServiceAccountConstants {
|
||||||
|
|
||||||
|
String CLIENT_AUTH = "client_auth";
|
||||||
|
|
||||||
|
String SERVICE_ACCOUNT_USER_PREFIX = "service-account-";
|
||||||
|
|
||||||
|
String CLIENT_ID_PROTOCOL_MAPPER = "Client ID";
|
||||||
|
String CLIENT_HOST_PROTOCOL_MAPPER = "Client Host";
|
||||||
|
String CLIENT_ADDRESS_PROTOCOL_MAPPER = "Client IP Address";
|
||||||
|
String CLIENT_ID = "clientId";
|
||||||
|
String CLIENT_HOST = "clientHost";
|
||||||
|
String CLIENT_ADDRESS = "clientAddress";
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.enums;
|
package org.keycloak.common.enums;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package org.keycloak.enums;
|
package org.keycloak.common.enums;
|
||||||
|
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
File diff suppressed because it is too large.
Load diff
+42
-44
@@ -1,44 +1,42 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
|
||||||
import net.iharder.Base64;
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
/**
|
* @version $Revision: 1 $
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
*/
|
||||||
* @version $Revision: 1 $
|
public class Base64Url {
|
||||||
*/
|
public static String encode(byte[] bytes) {
|
||||||
public class Base64Url {
|
String s = Base64.encodeBytes(bytes);
|
||||||
public static String encode(byte[] bytes) {
|
s = s.split("=")[0]; // Remove any trailing '='s
|
||||||
String s = Base64.encodeBytes(bytes);
|
s = s.replace('+', '-'); // 62nd char of encoding
|
||||||
s = s.split("=")[0]; // Remove any trailing '='s
|
s = s.replace('/', '_'); // 63rd char of encoding
|
||||||
s = s.replace('+', '-'); // 62nd char of encoding
|
return s;
|
||||||
s = s.replace('/', '_'); // 63rd char of encoding
|
}
|
||||||
return s;
|
|
||||||
}
|
public static byte[] decode(String s) {
|
||||||
|
s = s.replace('-', '+'); // 62nd char of encoding
|
||||||
public static byte[] decode(String s) {
|
s = s.replace('_', '/'); // 63rd char of encoding
|
||||||
s = s.replace('-', '+'); // 62nd char of encoding
|
switch (s.length() % 4) // Pad with trailing '='s
|
||||||
s = s.replace('_', '/'); // 63rd char of encoding
|
{
|
||||||
switch (s.length() % 4) // Pad with trailing '='s
|
case 0:
|
||||||
{
|
break; // No pad chars in this case
|
||||||
case 0:
|
case 2:
|
||||||
break; // No pad chars in this case
|
s += "==";
|
||||||
case 2:
|
break; // Two pad chars
|
||||||
s += "==";
|
case 3:
|
||||||
break; // Two pad chars
|
s += "=";
|
||||||
case 3:
|
break; // One pad char
|
||||||
s += "=";
|
default:
|
||||||
break; // One pad char
|
throw new RuntimeException(
|
||||||
default:
|
"Illegal base64url string!");
|
||||||
throw new RuntimeException(
|
}
|
||||||
"Illegal base64url string!");
|
try {
|
||||||
}
|
return Base64.decode(s);
|
||||||
try {
|
} catch (Exception e) {
|
||||||
return Base64.decode(s);
|
throw new RuntimeException(e);
|
||||||
} catch (Exception e) {
|
}
|
||||||
throw new RuntimeException(e);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
+165
-166
@@ -1,166 +1,165 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.cert.X509Certificate;
|
import java.util.Calendar;
|
||||||
import java.util.Calendar;
|
import java.util.Date;
|
||||||
import java.util.Date;
|
|
||||||
|
import org.bouncycastle.asn1.ASN1Sequence;
|
||||||
import org.bouncycastle.asn1.ASN1Sequence;
|
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
|
||||||
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
import org.bouncycastle.asn1.x509.Extension;
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
import org.bouncycastle.cert.X509CertificateHolder;
|
import org.bouncycastle.cert.X509ExtensionUtils;
|
||||||
import org.bouncycastle.cert.X509ExtensionUtils;
|
import org.bouncycastle.cert.X509v1CertificateBuilder;
|
||||||
import org.bouncycastle.cert.X509v1CertificateBuilder;
|
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
||||||
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
import org.bouncycastle.operator.DigestCalculator;
|
||||||
import org.bouncycastle.operator.DigestCalculator;
|
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
|
||||||
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
|
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
|
||||||
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
|
/**
|
||||||
/**
|
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
||||||
* The Class CertificateUtils provides utility functions for generation of V1 and V3 {@link java.security.cert.X509Certificate}
|
*
|
||||||
*
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
||||||
* @author <a href="mailto:giriraj.sharma27@gmail.com">Giriraj Sharma</a>
|
* @version $Revision: 2 $
|
||||||
* @version $Revision: 2 $
|
*/
|
||||||
*/
|
public class CertificateUtils {
|
||||||
public class CertificateUtils {
|
static {
|
||||||
static {
|
BouncyIntegration.init();
|
||||||
BouncyIntegration.init();
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
||||||
* Generates version 3 {@link java.security.cert.X509Certificate}.
|
*
|
||||||
*
|
* @param keyPair the key pair
|
||||||
* @param keyPair the key pair
|
* @param caPrivateKey the CA private key
|
||||||
* @param caPrivateKey the CA private key
|
* @param caCert the CA certificate
|
||||||
* @param caCert the CA certificate
|
* @param subject the subject name
|
||||||
* @param subject the subject name
|
*
|
||||||
*
|
* @return the x509 certificate
|
||||||
* @return the x509 certificate
|
*
|
||||||
*
|
* @throws Exception the exception
|
||||||
* @throws Exception the exception
|
*/
|
||||||
*/
|
public static X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
||||||
public static X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert,
|
String subject) throws Exception {
|
||||||
String subject) throws Exception {
|
|
||||||
|
try {
|
||||||
try {
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
|
||||||
|
// Serial Number
|
||||||
// Serial Number
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt()));
|
||||||
BigInteger serialNumber = BigInteger.valueOf(Math.abs(random.nextInt()));
|
|
||||||
|
// Validity
|
||||||
// Validity
|
Date notBefore = new Date(System.currentTimeMillis());
|
||||||
Date notBefore = new Date(System.currentTimeMillis());
|
Date notAfter = new Date(System.currentTimeMillis() + (((1000L * 60 * 60 * 24 * 30)) * 12) * 3);
|
||||||
Date notAfter = new Date(System.currentTimeMillis() + (((1000L * 60 * 60 * 24 * 30)) * 12) * 3);
|
|
||||||
|
// SubjectPublicKeyInfo
|
||||||
// SubjectPublicKeyInfo
|
SubjectPublicKeyInfo subjPubKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance(keyPair.getPublic()
|
||||||
SubjectPublicKeyInfo subjPubKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance(keyPair.getPublic()
|
.getEncoded()));
|
||||||
.getEncoded()));
|
|
||||||
|
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(new X500Name(caCert.getSubjectDN().getName()),
|
||||||
X509v3CertificateBuilder certGen = new X509v3CertificateBuilder(new X500Name(caCert.getSubjectDN().getName()),
|
serialNumber, notBefore, notAfter, subjectDN, subjPubKeyInfo);
|
||||||
serialNumber, notBefore, notAfter, subjectDN, subjPubKeyInfo);
|
|
||||||
|
DigestCalculator digCalc = new BcDigestCalculatorProvider()
|
||||||
DigestCalculator digCalc = new BcDigestCalculatorProvider()
|
.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
|
||||||
.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
|
X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils(digCalc);
|
||||||
X509ExtensionUtils x509ExtensionUtils = new X509ExtensionUtils(digCalc);
|
|
||||||
|
// Subject Key Identifier
|
||||||
// Subject Key Identifier
|
certGen.addExtension(Extension.subjectKeyIdentifier, false,
|
||||||
certGen.addExtension(Extension.subjectKeyIdentifier, false,
|
x509ExtensionUtils.createSubjectKeyIdentifier(subjPubKeyInfo));
|
||||||
x509ExtensionUtils.createSubjectKeyIdentifier(subjPubKeyInfo));
|
|
||||||
|
// Authority Key Identifier
|
||||||
// Authority Key Identifier
|
certGen.addExtension(Extension.authorityKeyIdentifier, false,
|
||||||
certGen.addExtension(Extension.authorityKeyIdentifier, false,
|
x509ExtensionUtils.createAuthorityKeyIdentifier(subjPubKeyInfo));
|
||||||
x509ExtensionUtils.createAuthorityKeyIdentifier(subjPubKeyInfo));
|
|
||||||
|
// Key Usage
|
||||||
// Key Usage
|
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign
|
||||||
certGen.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign
|
| KeyUsage.cRLSign));
|
||||||
| KeyUsage.cRLSign));
|
|
||||||
|
// Extended Key Usage
|
||||||
// Extended Key Usage
|
KeyPurposeId[] EKU = new KeyPurposeId[2];
|
||||||
KeyPurposeId[] EKU = new KeyPurposeId[2];
|
EKU[0] = KeyPurposeId.id_kp_emailProtection;
|
||||||
EKU[0] = KeyPurposeId.id_kp_emailProtection;
|
EKU[1] = KeyPurposeId.id_kp_serverAuth;
|
||||||
EKU[1] = KeyPurposeId.id_kp_serverAuth;
|
|
||||||
|
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU));
|
||||||
certGen.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(EKU));
|
|
||||||
|
// Basic Constraints
|
||||||
// Basic Constraints
|
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
|
||||||
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
|
|
||||||
|
// Content Signer
|
||||||
// Content Signer
|
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider("BC").build(caPrivateKey);
|
||||||
ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider("BC").build(caPrivateKey);
|
|
||||||
|
// Certificate
|
||||||
// Certificate
|
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
|
||||||
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
throw new RuntimeException("Error creating X509v3Certificate.", e);
|
||||||
throw new RuntimeException("Error creating X509v3Certificate.", e);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
||||||
* Generate version 1 self signed {@link java.security.cert.X509Certificate}..
|
*
|
||||||
*
|
* @param caKeyPair the CA key pair
|
||||||
* @param caKeyPair the CA key pair
|
* @param subject the subject name
|
||||||
* @param subject the subject name
|
*
|
||||||
*
|
* @return the x509 certificate
|
||||||
* @return the x509 certificate
|
*
|
||||||
*
|
* @throws Exception the exception
|
||||||
* @throws Exception the exception
|
*/
|
||||||
*/
|
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) throws Exception {
|
||||||
public static X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) throws Exception {
|
|
||||||
|
try {
|
||||||
try {
|
X500Name subjectDN = new X500Name("CN=" + subject);
|
||||||
X500Name subjectDN = new X500Name("CN=" + subject);
|
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
|
||||||
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
|
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
||||||
Date validityStartDate = new Date(System.currentTimeMillis() - 100000);
|
Calendar calendar = Calendar.getInstance();
|
||||||
Calendar calendar = Calendar.getInstance();
|
calendar.add(Calendar.YEAR, 10);
|
||||||
calendar.add(Calendar.YEAR, 10);
|
Date validityEndDate = new Date(calendar.getTime().getTime());
|
||||||
Date validityEndDate = new Date(calendar.getTime().getTime());
|
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(caKeyPair.getPublic().getEncoded());
|
||||||
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(caKeyPair.getPublic().getEncoded());
|
|
||||||
|
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(subjectDN, serialNumber, validityStartDate,
|
||||||
X509v1CertificateBuilder builder = new X509v1CertificateBuilder(subjectDN, serialNumber, validityStartDate,
|
validityEndDate, subjectDN, subPubKeyInfo);
|
||||||
validityEndDate, subjectDN, subPubKeyInfo);
|
X509CertificateHolder holder = builder.build(createSigner(caKeyPair.getPrivate()));
|
||||||
X509CertificateHolder holder = builder.build(createSigner(caKeyPair.getPrivate()));
|
|
||||||
|
return new JcaX509CertificateConverter().getCertificate(holder);
|
||||||
return new JcaX509CertificateConverter().getCertificate(holder);
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
throw new RuntimeException("Error creating X509v1Certificate.", e);
|
||||||
throw new RuntimeException("Error creating X509v1Certificate.", e);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
/**
|
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
||||||
* Creates the content signer for generation of Version 1 {@link java.security.cert.X509Certificate}.
|
*
|
||||||
*
|
* @param privateKey the private key
|
||||||
* @param privateKey the private key
|
*
|
||||||
*
|
* @return the content signer
|
||||||
* @return the content signer
|
*/
|
||||||
*/
|
public static ContentSigner createSigner(PrivateKey privateKey) {
|
||||||
public static ContentSigner createSigner(PrivateKey privateKey) {
|
try {
|
||||||
try {
|
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
|
||||||
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
|
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
||||||
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
|
||||||
|
return new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
|
||||||
return new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
|
.build(PrivateKeyFactory.createKey(privateKey.getEncoded()));
|
||||||
.build(PrivateKeyFactory.createKey(privateKey.getEncoded()));
|
} catch (Exception e) {
|
||||||
} catch (Exception e) {
|
throw new RuntimeException("Could not create content signer.", e);
|
||||||
throw new RuntimeException("Could not create content signer.", e);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
+37
-37
@@ -1,37 +1,37 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public final class EnvUtil {
|
public final class EnvUtil {
|
||||||
private static final Pattern p = Pattern.compile("[$][{]([^}]+)[}]");
|
private static final Pattern p = Pattern.compile("[$][{]([^}]+)[}]");
|
||||||
|
|
||||||
private EnvUtil() {
|
private EnvUtil() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces any ${} strings with their corresponding environent variable.
|
* Replaces any ${} strings with their corresponding environent variable.
|
||||||
*
|
*
|
||||||
* @param val
|
* @param val
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String replace(String val) {
|
public static String replace(String val) {
|
||||||
Matcher matcher = p.matcher(val);
|
Matcher matcher = p.matcher(val);
|
||||||
StringBuffer buf = new StringBuffer();
|
StringBuffer buf = new StringBuffer();
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String envVar = matcher.group(1);
|
String envVar = matcher.group(1);
|
||||||
String envVal = System.getProperty(envVar);
|
String envVal = System.getProperty(envVar);
|
||||||
if (envVal == null) envVal = "NOT-SPECIFIED";
|
if (envVal == null) envVal = "NOT-SPECIFIED";
|
||||||
matcher.appendReplacement(buf, envVal.replace("\\", "\\\\"));
|
matcher.appendReplacement(buf, envVal.replace("\\", "\\\\"));
|
||||||
}
|
}
|
||||||
matcher.appendTail(buf);
|
matcher.appendTail(buf);
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
+37
-37
@@ -1,37 +1,37 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import org.keycloak.constants.GenericConstants;
|
import org.keycloak.common.constants.GenericConstants;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class FindFile {
|
public class FindFile {
|
||||||
public static InputStream findFile(String keycloakConfigFile) {
|
public static InputStream findFile(String keycloakConfigFile) {
|
||||||
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
|
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
|
||||||
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
|
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
|
||||||
// Try current class classloader first
|
// Try current class classloader first
|
||||||
InputStream is = FindFile.class.getClassLoader().getResourceAsStream(classPathLocation);
|
InputStream is = FindFile.class.getClassLoader().getResourceAsStream(classPathLocation);
|
||||||
if (is == null) {
|
if (is == null) {
|
||||||
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
|
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is != null) {
|
if (is != null) {
|
||||||
return is;
|
return is;
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unable to find config from classpath: " + keycloakConfigFile);
|
throw new RuntimeException("Unable to find config from classpath: " + keycloakConfigFile);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to file
|
// Fallback to file
|
||||||
try {
|
try {
|
||||||
return new FileInputStream(keycloakConfigFile);
|
return new FileInputStream(keycloakConfigFile);
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
throw new RuntimeException(fnfe);
|
throw new RuntimeException(fnfe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
Regular → Executable
+12
-2
@@ -15,7 +15,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author pedroigor
|
* @author pedroigor
|
||||||
@@ -34,7 +34,17 @@ public class HtmlUtils {
|
|||||||
for (int i = 0; i < value.length(); i++) {
|
for (int i = 0; i < value.length(); i++) {
|
||||||
char chr = value.charAt(i);
|
char chr = value.charAt(i);
|
||||||
|
|
||||||
if (chr != '\'' && chr != '"' && chr != '<' && chr != '>' && chr != '/') {
|
if (chr == '<') {
|
||||||
|
escaped.append("<");
|
||||||
|
} else if (chr == '>') {
|
||||||
|
escaped.append(">");
|
||||||
|
} else if (chr == '"') {
|
||||||
|
escaped.append(""");
|
||||||
|
} else if (chr == '\'') {
|
||||||
|
escaped.append("'");
|
||||||
|
} else if (chr == '&') {
|
||||||
|
escaped.append("&");
|
||||||
|
} else {
|
||||||
escaped.append(chr);
|
escaped.append(chr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-4
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -12,13 +12,12 @@ import java.lang.reflect.Method;
|
|||||||
|
|
||||||
import javax.security.auth.kerberos.KerberosTicket;
|
import javax.security.auth.kerberos.KerberosTicket;
|
||||||
|
|
||||||
import net.iharder.Base64;
|
|
||||||
import org.ietf.jgss.GSSCredential;
|
import org.ietf.jgss.GSSCredential;
|
||||||
import org.ietf.jgss.GSSException;
|
import org.ietf.jgss.GSSException;
|
||||||
import org.ietf.jgss.GSSManager;
|
import org.ietf.jgss.GSSManager;
|
||||||
import org.ietf.jgss.Oid;
|
import org.ietf.jgss.Oid;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.common.constants.KerberosConstants;
|
||||||
import org.keycloak.util.reflections.Reflections;
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
import sun.security.jgss.GSSCredentialImpl;
|
import sun.security.jgss.GSSCredentialImpl;
|
||||||
import sun.security.jgss.GSSManagerImpl;
|
import sun.security.jgss.GSSManagerImpl;
|
||||||
import sun.security.jgss.krb5.Krb5InitCredential;
|
import sun.security.jgss.krb5.Krb5InitCredential;
|
||||||
+713
-713
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,56 @@
|
|||||||
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
import org.keycloak.common.constants.GenericConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class KeystoreUtil {
|
||||||
|
static {
|
||||||
|
BouncyIntegration.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum KeystoreFormat {
|
||||||
|
JKS,
|
||||||
|
PKCS12
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyStore loadKeyStore(String filename, String password) throws Exception {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
InputStream trustStream = (filename.startsWith(GenericConstants.PROTOCOL_CLASSPATH))
|
||||||
|
?KeystoreUtil.class.getResourceAsStream(filename.replace(GenericConstants.PROTOCOL_CLASSPATH, ""))
|
||||||
|
:new FileInputStream(new File(filename));
|
||||||
|
trustStore.load(trustStream, password.toCharArray());
|
||||||
|
trustStream.close();
|
||||||
|
return trustStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrivateKey loadPrivateKeyFromKeystore(String keystoreFile, String storePassword, String keyPassword, String keyAlias, KeystoreFormat format) {
|
||||||
|
InputStream stream = FindFile.findFile(keystoreFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = null;
|
||||||
|
if (format == KeystoreFormat.JKS) {
|
||||||
|
keyStore = KeyStore.getInstance(format.toString());
|
||||||
|
} else {
|
||||||
|
keyStore = KeyStore.getInstance(format.toString(), "BC");
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStore.load(stream, storePassword.toCharArray());
|
||||||
|
PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());
|
||||||
|
if (key == null) {
|
||||||
|
throw new RuntimeException("Couldn't load key with alias '" + keyAlias + "' from keystore");
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load private key: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import javax.activation.MimetypesFileTypeMap;
|
import javax.activation.MimetypesFileTypeMap;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* JBoss, Home of Professional Open Source.
|
* JBoss, Home of Professional Open Source.
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
+108
-110
@@ -1,110 +1,108 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
|
||||||
import net.iharder.Base64;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.IOException;
|
||||||
import java.io.DataInputStream;
|
import java.io.InputStream;
|
||||||
import java.io.IOException;
|
import java.security.PrivateKey;
|
||||||
import java.io.InputStream;
|
import java.security.PublicKey;
|
||||||
import java.security.PrivateKey;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
/**
|
||||||
|
* Utility classes to extract PublicKey, PrivateKey, and X509Certificate from openssl generated PEM files
|
||||||
/**
|
*
|
||||||
* Utility classes to extract PublicKey, PrivateKey, and X509Certificate from openssl generated PEM files
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
*
|
* @version $Revision: 1 $
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
*/
|
||||||
* @version $Revision: 1 $
|
public final class PemUtils {
|
||||||
*/
|
static {
|
||||||
public final class PemUtils {
|
BouncyIntegration.init();
|
||||||
static {
|
}
|
||||||
BouncyIntegration.init();
|
|
||||||
}
|
private PemUtils() {
|
||||||
|
}
|
||||||
private PemUtils() {
|
|
||||||
}
|
public static X509Certificate decodeCertificate(InputStream is) throws Exception {
|
||||||
|
byte[] der = pemToDer(is);
|
||||||
public static X509Certificate decodeCertificate(InputStream is) throws Exception {
|
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
||||||
byte[] der = pemToDer(is);
|
return DerUtils.decodeCertificate(bis);
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
}
|
||||||
return DerUtils.decodeCertificate(bis);
|
|
||||||
}
|
public static X509Certificate decodeCertificate(String cert) throws Exception {
|
||||||
|
byte[] der = pemToDer(cert);
|
||||||
public static X509Certificate decodeCertificate(String cert) throws Exception {
|
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
||||||
byte[] der = pemToDer(cert);
|
return DerUtils.decodeCertificate(bis);
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(der);
|
}
|
||||||
return DerUtils.decodeCertificate(bis);
|
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Extract a public key from a PEM string
|
||||||
/**
|
*
|
||||||
* Extract a public key from a PEM string
|
* @param pem
|
||||||
*
|
* @return
|
||||||
* @param pem
|
* @throws Exception
|
||||||
* @return
|
*/
|
||||||
* @throws Exception
|
public static PublicKey decodePublicKey(String pem) throws Exception {
|
||||||
*/
|
byte[] der = pemToDer(pem);
|
||||||
public static PublicKey decodePublicKey(String pem) throws Exception {
|
return DerUtils.decodePublicKey(der);
|
||||||
byte[] der = pemToDer(pem);
|
}
|
||||||
return DerUtils.decodePublicKey(der);
|
|
||||||
}
|
/**
|
||||||
|
* Extract a private key that is a PKCS8 pem string (base64 encoded PKCS8)
|
||||||
/**
|
*
|
||||||
* Extract a private key that is a PKCS8 pem string (base64 encoded PKCS8)
|
* @param pem
|
||||||
*
|
* @return
|
||||||
* @param pem
|
* @throws Exception
|
||||||
* @return
|
*/
|
||||||
* @throws Exception
|
public static PrivateKey decodePrivateKey(String pem) throws Exception {
|
||||||
*/
|
byte[] der = pemToDer(pem);
|
||||||
public static PrivateKey decodePrivateKey(String pem) throws Exception {
|
return DerUtils.decodePrivateKey(der);
|
||||||
byte[] der = pemToDer(pem);
|
}
|
||||||
return DerUtils.decodePrivateKey(der);
|
|
||||||
}
|
public static PrivateKey decodePrivateKey(InputStream is) throws Exception {
|
||||||
|
String pem = pemFromStream(is);
|
||||||
public static PrivateKey decodePrivateKey(InputStream is) throws Exception {
|
return decodePrivateKey(pem);
|
||||||
String pem = pemFromStream(is);
|
}
|
||||||
return decodePrivateKey(pem);
|
|
||||||
}
|
/**
|
||||||
|
* Decode a PEM file to DER format
|
||||||
/**
|
*
|
||||||
* Decode a PEM file to DER format
|
* @param is
|
||||||
*
|
* @return
|
||||||
* @param is
|
* @throws java.io.IOException
|
||||||
* @return
|
*/
|
||||||
* @throws java.io.IOException
|
public static byte[] pemToDer(InputStream is) throws IOException {
|
||||||
*/
|
String pem = pemFromStream(is);
|
||||||
public static byte[] pemToDer(InputStream is) throws IOException {
|
return pemToDer(pem);
|
||||||
String pem = pemFromStream(is);
|
}
|
||||||
return pemToDer(pem);
|
|
||||||
}
|
/**
|
||||||
|
* Decode a PEM string to DER format
|
||||||
/**
|
*
|
||||||
* Decode a PEM string to DER format
|
* @param pem
|
||||||
*
|
* @return
|
||||||
* @param pem
|
* @throws java.io.IOException
|
||||||
* @return
|
*/
|
||||||
* @throws java.io.IOException
|
public static byte[] pemToDer(String pem) throws IOException {
|
||||||
*/
|
pem = removeBeginEnd(pem);
|
||||||
public static byte[] pemToDer(String pem) throws IOException {
|
return Base64.decode(pem);
|
||||||
pem = removeBeginEnd(pem);
|
}
|
||||||
return Base64.decode(pem);
|
|
||||||
}
|
public static String removeBeginEnd(String pem) {
|
||||||
|
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
||||||
public static String removeBeginEnd(String pem) {
|
pem = pem.replaceAll("-----END (.*)----", "");
|
||||||
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
|
pem = pem.replaceAll("\r\n", "");
|
||||||
pem = pem.replaceAll("-----END (.*)----", "");
|
pem = pem.replaceAll("\n", "");
|
||||||
pem = pem.replaceAll("\r\n", "");
|
return pem.trim();
|
||||||
pem = pem.replaceAll("\n", "");
|
}
|
||||||
return pem.trim();
|
|
||||||
}
|
|
||||||
|
public static String pemFromStream(InputStream is) throws IOException {
|
||||||
|
DataInputStream dis = new DataInputStream(is);
|
||||||
public static String pemFromStream(InputStream is) throws IOException {
|
byte[] keyBytes = new byte[dis.available()];
|
||||||
DataInputStream dis = new DataInputStream(is);
|
dis.readFully(keyBytes);
|
||||||
byte[] keyBytes = new byte[dis.available()];
|
dis.close();
|
||||||
dis.readFully(keyBytes);
|
return new String(keyBytes, "utf-8");
|
||||||
dis.close();
|
}
|
||||||
return new String(keyBytes, "utf-8");
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
+35
-35
@@ -1,35 +1,35 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public final class StreamUtil {
|
public final class StreamUtil {
|
||||||
|
|
||||||
private StreamUtil() {
|
private StreamUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readString(InputStream in) throws IOException
|
public static String readString(InputStream in) throws IOException
|
||||||
{
|
{
|
||||||
char[] buffer = new char[1024];
|
char[] buffer = new char[1024];
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||||
int wasRead;
|
int wasRead;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
wasRead = reader.read(buffer, 0, 1024);
|
wasRead = reader.read(buffer, 0, 1024);
|
||||||
if (wasRead > 0)
|
if (wasRead > 0)
|
||||||
{
|
{
|
||||||
builder.append(buffer, 0, wasRead);
|
builder.append(buffer, 0, wasRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (wasRead > -1);
|
while (wasRead > -1);
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||||
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class SystemEnvProperties extends Properties {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProperty(String key) {
|
||||||
|
if (key.startsWith("env.")) {
|
||||||
|
return System.getenv().get(key.substring(4));
|
||||||
|
} else {
|
||||||
|
return System.getProperty(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProperty(String key, String defaultValue) {
|
||||||
|
String value = getProperty(key);
|
||||||
|
return value != null ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util;
|
package org.keycloak.common.util;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util.reflections;
|
package org.keycloak.common.util.reflections;
|
||||||
|
|
||||||
import java.beans.Introspector;
|
import java.beans.Introspector;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util.reflections;
|
package org.keycloak.common.util.reflections;
|
||||||
|
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.keycloak.util.reflections;
|
package org.keycloak.common.util.reflections;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
version=${product.version}
|
||||||
|
build-time=${product.build-time}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-12
@@ -90,18 +90,7 @@ public class DefaultFileConnectionProviderFactory implements FileConnectionProvi
|
|||||||
ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING);
|
ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING);
|
||||||
session.realms().getMigrationModel().setStoredVersion(model.getModelVersion());
|
session.realms().getMigrationModel().setStoredVersion(model.getModelVersion());
|
||||||
|
|
||||||
List<RealmRepresentation> realmReps = new ArrayList<RealmRepresentation>();
|
ImportUtils.importRealms(session, model.getRealms(), Strategy.IGNORE_EXISTING);
|
||||||
for (RealmRepresentation realmRep : model.getRealms()) {
|
|
||||||
if (Config.getAdminRealm().equals(realmRep.getRealm())) {
|
|
||||||
realmReps.add(0, realmRep);
|
|
||||||
} else {
|
|
||||||
realmReps.add(realmRep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (RealmRepresentation realmRep : realmReps) {
|
|
||||||
ImportUtils.importRealm(session, realmRep, Strategy.IGNORE_EXISTING);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
|
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ import org.keycloak.provider.Spi;
|
|||||||
*/
|
*/
|
||||||
public class FileConnectionSpi implements Spi {
|
public class FileConnectionSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "connectionsFile";
|
return "connectionsFile";
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
package org.keycloak.connections.file;
|
package org.keycloak.connections.file;
|
||||||
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class Model {
|
public class Model {
|
||||||
private String modelVersion;
|
private String modelVersion;
|
||||||
private List<RealmRepresentation> realms;
|
private List<RealmRepresentation> realms;
|
||||||
|
|
||||||
public String getModelVersion() {
|
public String getModelVersion() {
|
||||||
return modelVersion;
|
return modelVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setModelVersion(String modelVersion) {
|
public void setModelVersion(String modelVersion) {
|
||||||
this.modelVersion = modelVersion;
|
this.modelVersion = modelVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RealmRepresentation> getRealms() {
|
public List<RealmRepresentation> getRealms() {
|
||||||
return realms;
|
return realms;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRealms(List<RealmRepresentation> realms) {
|
public void setRealms(List<RealmRepresentation> realms) {
|
||||||
this.realms = realms;
|
this.realms = realms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,36 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-connections-http-client</artifactId>
|
<artifactId>keycloak-connections-http-client</artifactId>
|
||||||
<name>Keycloak Connections Apache HttpClient</name>
|
<name>Keycloak Connections Apache HttpClient</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+2
-2
@@ -12,8 +12,8 @@ import org.jboss.logging.Logger;
|
|||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.util.EnvUtil;
|
import org.keycloak.common.util.EnvUtil;
|
||||||
import org.keycloak.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
+280
-280
@@ -1,281 +1,281 @@
|
|||||||
package org.keycloak.connections.httpclient;
|
package org.keycloak.connections.httpclient;
|
||||||
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
import org.apache.http.client.config.RequestConfig;
|
||||||
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||||
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||||
import org.apache.http.conn.ssl.SSLContexts;
|
import org.apache.http.conn.ssl.SSLContexts;
|
||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.KeyStoreException;
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.UnrecoverableKeyException;
|
import java.security.UnrecoverableKeyException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstraction for creating HttpClients. Allows SSL configuration.
|
* Abstraction for creating HttpClients. Allows SSL configuration.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class HttpClientBuilder {
|
public class HttpClientBuilder {
|
||||||
public static enum HostnameVerificationPolicy {
|
public static enum HostnameVerificationPolicy {
|
||||||
/**
|
/**
|
||||||
* Hostname verification is not done on the server's certificate
|
* Hostname verification is not done on the server's certificate
|
||||||
*/
|
*/
|
||||||
ANY,
|
ANY,
|
||||||
/**
|
/**
|
||||||
* Allows wildcards in subdomain names i.e. *.foo.com
|
* Allows wildcards in subdomain names i.e. *.foo.com
|
||||||
*/
|
*/
|
||||||
WILDCARD,
|
WILDCARD,
|
||||||
/**
|
/**
|
||||||
* CN must match hostname connecting to
|
* CN must match hostname connecting to
|
||||||
*/
|
*/
|
||||||
STRICT
|
STRICT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
private static class PassthroughTrustManager implements X509TrustManager {
|
private static class PassthroughTrustManager implements X509TrustManager {
|
||||||
public void checkClientTrusted(X509Certificate[] chain,
|
public void checkClientTrusted(X509Certificate[] chain,
|
||||||
String authType) throws CertificateException {
|
String authType) throws CertificateException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkServerTrusted(X509Certificate[] chain,
|
public void checkServerTrusted(X509Certificate[] chain,
|
||||||
String authType) throws CertificateException {
|
String authType) throws CertificateException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeyStore truststore;
|
protected KeyStore truststore;
|
||||||
protected KeyStore clientKeyStore;
|
protected KeyStore clientKeyStore;
|
||||||
protected String clientPrivateKeyPassword;
|
protected String clientPrivateKeyPassword;
|
||||||
protected boolean disableTrustManager;
|
protected boolean disableTrustManager;
|
||||||
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
|
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
|
||||||
protected SSLContext sslContext;
|
protected SSLContext sslContext;
|
||||||
protected int connectionPoolSize = 100;
|
protected int connectionPoolSize = 100;
|
||||||
protected int maxPooledPerRoute = 0;
|
protected int maxPooledPerRoute = 0;
|
||||||
protected long connectionTTL = -1;
|
protected long connectionTTL = -1;
|
||||||
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
|
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
|
||||||
protected HostnameVerifier verifier = null;
|
protected HostnameVerifier verifier = null;
|
||||||
protected long socketTimeout = -1;
|
protected long socketTimeout = -1;
|
||||||
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
protected long establishConnectionTimeout = -1;
|
protected long establishConnectionTimeout = -1;
|
||||||
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
protected boolean disableCookies = false;
|
protected boolean disableCookies = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Socket inactivity timeout
|
* Socket inactivity timeout
|
||||||
*
|
*
|
||||||
* @param timeout
|
* @param timeout
|
||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
||||||
{
|
{
|
||||||
this.socketTimeout = timeout;
|
this.socketTimeout = timeout;
|
||||||
this.socketTimeoutUnits = unit;
|
this.socketTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When trying to make an initial socket connection, what is the timeout?
|
* When trying to make an initial socket connection, what is the timeout?
|
||||||
*
|
*
|
||||||
* @param timeout
|
* @param timeout
|
||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
||||||
{
|
{
|
||||||
this.establishConnectionTimeout = timeout;
|
this.establishConnectionTimeout = timeout;
|
||||||
this.establishConnectionTimeoutUnits = unit;
|
this.establishConnectionTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
|
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
|
||||||
this.connectionTTL = ttl;
|
this.connectionTTL = ttl;
|
||||||
this.connectionTTLUnit = unit;
|
this.connectionTTLUnit = unit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
|
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
|
||||||
this.maxPooledPerRoute = maxPooledPerRoute;
|
this.maxPooledPerRoute = maxPooledPerRoute;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
|
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
|
||||||
this.connectionPoolSize = connectionPoolSize;
|
this.connectionPoolSize = connectionPoolSize;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
|
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
|
||||||
* hole, so only set this option if you cannot or do not want to verify the identity of the
|
* hole, so only set this option if you cannot or do not want to verify the identity of the
|
||||||
* host you are communicating with.
|
* host you are communicating with.
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder disableTrustManager() {
|
public HttpClientBuilder disableTrustManager() {
|
||||||
this.disableTrustManager = true;
|
this.disableTrustManager = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable cookie management.
|
* Disable cookie management.
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder disableCookies(boolean disable) {
|
public HttpClientBuilder disableCookies(boolean disable) {
|
||||||
this.disableTrustManager = disable;
|
this.disableTrustManager = disable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SSL policy used to verify hostnames
|
* SSL policy used to verify hostnames
|
||||||
*
|
*
|
||||||
* @param policy
|
* @param policy
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
|
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
|
||||||
this.policy = policy;
|
this.policy = policy;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public HttpClientBuilder sslContext(SSLContext sslContext) {
|
public HttpClientBuilder sslContext(SSLContext sslContext) {
|
||||||
this.sslContext = sslContext;
|
this.sslContext = sslContext;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder trustStore(KeyStore truststore) {
|
public HttpClientBuilder trustStore(KeyStore truststore) {
|
||||||
this.truststore = truststore;
|
this.truststore = truststore;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
|
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
|
||||||
this.clientKeyStore = keyStore;
|
this.clientKeyStore = keyStore;
|
||||||
this.clientPrivateKeyPassword = password;
|
this.clientPrivateKeyPassword = password;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
|
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
|
||||||
this.clientKeyStore = keyStore;
|
this.clientKeyStore = keyStore;
|
||||||
this.clientPrivateKeyPassword = new String(password);
|
this.clientPrivateKeyPassword = new String(password);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class VerifierWrapper implements X509HostnameVerifier {
|
static class VerifierWrapper implements X509HostnameVerifier {
|
||||||
protected HostnameVerifier verifier;
|
protected HostnameVerifier verifier;
|
||||||
|
|
||||||
VerifierWrapper(HostnameVerifier verifier) {
|
VerifierWrapper(HostnameVerifier verifier) {
|
||||||
this.verifier = verifier;
|
this.verifier = verifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String host, SSLSocket ssl) throws IOException {
|
public void verify(String host, SSLSocket ssl) throws IOException {
|
||||||
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
|
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String host, X509Certificate cert) throws SSLException {
|
public void verify(String host, X509Certificate cert) throws SSLException {
|
||||||
throw new SSLException("This verification path not implemented");
|
throw new SSLException("This verification path not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
|
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
|
||||||
throw new SSLException("This verification path not implemented");
|
throw new SSLException("This verification path not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(String s, SSLSession sslSession) {
|
public boolean verify(String s, SSLSession sslSession) {
|
||||||
return verifier.verify(s, sslSession);
|
return verifier.verify(s, sslSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CloseableHttpClient build() {
|
public CloseableHttpClient build() {
|
||||||
X509HostnameVerifier verifier = null;
|
X509HostnameVerifier verifier = null;
|
||||||
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
|
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
|
||||||
else {
|
else {
|
||||||
switch (policy) {
|
switch (policy) {
|
||||||
case ANY:
|
case ANY:
|
||||||
verifier = new AllowAllHostnameVerifier();
|
verifier = new AllowAllHostnameVerifier();
|
||||||
break;
|
break;
|
||||||
case WILDCARD:
|
case WILDCARD:
|
||||||
verifier = new BrowserCompatHostnameVerifier();
|
verifier = new BrowserCompatHostnameVerifier();
|
||||||
break;
|
break;
|
||||||
case STRICT:
|
case STRICT:
|
||||||
verifier = new StrictHostnameVerifier();
|
verifier = new StrictHostnameVerifier();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
SSLConnectionSocketFactory sslsf = null;
|
SSLConnectionSocketFactory sslsf = null;
|
||||||
SSLContext theContext = sslContext;
|
SSLContext theContext = sslContext;
|
||||||
if (disableTrustManager) {
|
if (disableTrustManager) {
|
||||||
theContext = SSLContext.getInstance("TLS");
|
theContext = SSLContext.getInstance("TLS");
|
||||||
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
||||||
new SecureRandom());
|
new SecureRandom());
|
||||||
verifier = new AllowAllHostnameVerifier();
|
verifier = new AllowAllHostnameVerifier();
|
||||||
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
||||||
} else if (theContext != null) {
|
} else if (theContext != null) {
|
||||||
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
||||||
} else if (clientKeyStore != null || truststore != null) {
|
} else if (clientKeyStore != null || truststore != null) {
|
||||||
theContext = createSslContext("TLS", clientKeyStore, clientPrivateKeyPassword, truststore, null);
|
theContext = createSslContext("TLS", clientKeyStore, clientPrivateKeyPassword, truststore, null);
|
||||||
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
sslsf = new SSLConnectionSocketFactory(theContext, verifier);
|
||||||
} else {
|
} else {
|
||||||
final SSLContext tlsContext = SSLContext.getInstance("TLS");
|
final SSLContext tlsContext = SSLContext.getInstance("TLS");
|
||||||
tlsContext.init(null, null, null);
|
tlsContext.init(null, null, null);
|
||||||
sslsf = new SSLConnectionSocketFactory(tlsContext, verifier);
|
sslsf = new SSLConnectionSocketFactory(tlsContext, verifier);
|
||||||
}
|
}
|
||||||
RequestConfig requestConfig = RequestConfig.custom()
|
RequestConfig requestConfig = RequestConfig.custom()
|
||||||
.setConnectTimeout((int) establishConnectionTimeout)
|
.setConnectTimeout((int) establishConnectionTimeout)
|
||||||
.setSocketTimeout((int) socketTimeout).build();
|
.setSocketTimeout((int) socketTimeout).build();
|
||||||
|
|
||||||
org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
|
org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
|
||||||
.setDefaultRequestConfig(requestConfig)
|
.setDefaultRequestConfig(requestConfig)
|
||||||
.setSSLSocketFactory(sslsf)
|
.setSSLSocketFactory(sslsf)
|
||||||
.setMaxConnTotal(connectionPoolSize)
|
.setMaxConnTotal(connectionPoolSize)
|
||||||
.setMaxConnPerRoute(maxPooledPerRoute);
|
.setMaxConnPerRoute(maxPooledPerRoute);
|
||||||
if (disableCookies) builder.disableCookieManagement();
|
if (disableCookies) builder.disableCookieManagement();
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SSLContext createSslContext(
|
private SSLContext createSslContext(
|
||||||
final String algorithm,
|
final String algorithm,
|
||||||
final KeyStore keystore,
|
final KeyStore keystore,
|
||||||
final String keyPassword,
|
final String keyPassword,
|
||||||
final KeyStore truststore,
|
final KeyStore truststore,
|
||||||
final SecureRandom random)
|
final SecureRandom random)
|
||||||
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
return SSLContexts.custom()
|
return SSLContexts.custom()
|
||||||
.useProtocol(algorithm)
|
.useProtocol(algorithm)
|
||||||
.setSecureRandom(random)
|
.setSecureRandom(random)
|
||||||
.loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
|
.loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
|
||||||
.loadTrustMaterial(truststore)
|
.loadTrustMaterial(truststore)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+5
@@ -9,6 +9,11 @@ import org.keycloak.provider.Spi;
|
|||||||
*/
|
*/
|
||||||
public class HttpClientSpi implements Spi {
|
public class HttpClientSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "connectionsHttpClient";
|
return "connectionsHttpClient";
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||||
<name>Keycloak Connections Infinispan</name>
|
<name>Keycloak Connections Infinispan</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.infinispan</groupId>
|
<groupId>org.infinispan</groupId>
|
||||||
<artifactId>infinispan-core</artifactId>
|
<artifactId>infinispan-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
|
||||||
</project>
|
</dependencies>
|
||||||
|
</project>
|
||||||
+13
@@ -66,6 +66,19 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||||||
} else {
|
} else {
|
||||||
initEmbedded();
|
initEmbedded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backwards compatibility
|
||||||
|
if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) {
|
||||||
|
logger.warnf("No configuration provided for '%s' cache. Using '%s' configuration as template",
|
||||||
|
InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
|
|
||||||
|
Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME);
|
||||||
|
if (sessionCacheConfig != null) {
|
||||||
|
ConfigurationBuilder confBuilder = new ConfigurationBuilder().read(sessionCacheConfig);
|
||||||
|
Configuration offlineSessionConfig = confBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, offlineSessionConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -11,6 +11,7 @@ public interface InfinispanConnectionProvider extends Provider {
|
|||||||
static final String REALM_CACHE_NAME = "realms";
|
static final String REALM_CACHE_NAME = "realms";
|
||||||
static final String USER_CACHE_NAME = "users";
|
static final String USER_CACHE_NAME = "users";
|
||||||
static final String SESSION_CACHE_NAME = "sessions";
|
static final String SESSION_CACHE_NAME = "sessions";
|
||||||
|
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
||||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||||
|
|
||||||
<K, V> Cache<K, V> getCache(String name);
|
<K, V> Cache<K, V> getCache(String name);
|
||||||
|
|||||||
+5
@@ -9,6 +9,11 @@ import org.keycloak.provider.Spi;
|
|||||||
*/
|
*/
|
||||||
public class InfinispanConnectionSpi implements Spi {
|
public class InfinispanConnectionSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "connectionsInfinispan";
|
return "connectionsInfinispan";
|
||||||
|
|||||||
@@ -1,76 +1,76 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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/maven-v4_0_0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.2.0.RC1-SNAPSHOT</version>
|
<version>1.6.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-connections-jpa-liquibase</artifactId>
|
<artifactId>keycloak-connections-jpa-liquibase</artifactId>
|
||||||
<name>Keycloak Connections JPA Liquibase Updater</name>
|
<name>Keycloak Connections JPA Liquibase Updater</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-connections-jpa</artifactId>
|
<artifactId>keycloak-connections-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.liquibase</groupId>
|
<groupId>org.liquibase</groupId>
|
||||||
<artifactId>liquibase-core</artifactId>
|
<artifactId>liquibase-core</artifactId>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.yaml</groupId>
|
<groupId>org.yaml</groupId>
|
||||||
<artifactId>snakeyaml</artifactId>
|
<artifactId>snakeyaml</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-services</artifactId>
|
<artifactId>keycloak-services</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>h2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.liquibase</groupId>
|
<groupId>org.liquibase</groupId>
|
||||||
<artifactId>liquibase-maven-plugin</artifactId>
|
<artifactId>liquibase-maven-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
|
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
|
||||||
|
|
||||||
<url>${url}</url>
|
<url>${project.url}</url>
|
||||||
<driver>${driver}</driver>
|
<driver>${driver}</driver>
|
||||||
<username>${username}</username>
|
<username>${username}</username>
|
||||||
<password>${password}</password>
|
<password>${password}</password>
|
||||||
|
|
||||||
<referenceUrl>${referenceUrl}</referenceUrl>
|
<referenceUrl>${referenceUrl}</referenceUrl>
|
||||||
<referenceDriver>${driver}</referenceDriver>
|
<referenceDriver>${driver}</referenceDriver>
|
||||||
<referenceUsername>${username}</referenceUsername>
|
<referenceUsername>${username}</referenceUsername>
|
||||||
<referencePassword>${password}</referencePassword>
|
<referencePassword>${password}</referencePassword>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<username>sa</username>
|
<username>sa</username>
|
||||||
<password></password>
|
<password></password>
|
||||||
<driver>org.h2.Driver</driver>
|
<driver>org.h2.Driver</driver>
|
||||||
</properties>
|
</properties>
|
||||||
</project>
|
</project>
|
||||||
+15
-8
@@ -46,26 +46,26 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
|||||||
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCurrentVersionSql() {
|
public String getCurrentVersionSql(String defaultSchema) {
|
||||||
return "SELECT ID from DATABASECHANGELOG ORDER BY DATEEXECUTED DESC LIMIT 1";
|
return "SELECT ID from " + getTable("DATABASECHANGELOG", defaultSchema) + " ORDER BY DATEEXECUTED DESC LIMIT 1";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(KeycloakSession session, Connection connection) {
|
public void update(KeycloakSession session, Connection connection, String defaultSchema) {
|
||||||
logger.debug("Starting database update");
|
logger.debug("Starting database update");
|
||||||
|
|
||||||
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
|
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
|
||||||
ThreadLocalSessionContext.setCurrentSession(session);
|
ThreadLocalSessionContext.setCurrentSession(session);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Liquibase liquibase = getLiquibase(connection);
|
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
||||||
|
|
||||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||||
if (!changeSets.isEmpty()) {
|
if (!changeSets.isEmpty()) {
|
||||||
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
||||||
Statement statement = connection.createStatement();
|
Statement statement = connection.createStatement();
|
||||||
try {
|
try {
|
||||||
statement.executeQuery("SELECT id FROM REALM");
|
statement.executeQuery("SELECT id FROM " + getTable("REALM", defaultSchema));
|
||||||
|
|
||||||
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
|
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
|
||||||
liquibase.markNextChangeSetRan(null);
|
liquibase.markNextChangeSetRan(null);
|
||||||
@@ -93,9 +93,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validate(Connection connection) {
|
public void validate(Connection connection, String defaultSchema) {
|
||||||
try {
|
try {
|
||||||
Liquibase liquibase = getLiquibase(connection);
|
Liquibase liquibase = getLiquibase(connection, defaultSchema);
|
||||||
|
|
||||||
liquibase.validate();
|
liquibase.validate();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -103,7 +103,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Liquibase getLiquibase(Connection connection) throws Exception {
|
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception {
|
||||||
ServiceLocator sl = ServiceLocator.getInstance();
|
ServiceLocator sl = ServiceLocator.getInstance();
|
||||||
|
|
||||||
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
||||||
@@ -125,6 +125,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
|||||||
|
|
||||||
LogFactory.setInstance(new LogWrapper());
|
LogFactory.setInstance(new LogWrapper());
|
||||||
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||||
|
if (defaultSchema != null) {
|
||||||
|
database.setDefaultSchemaName(defaultSchema);
|
||||||
|
}
|
||||||
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,4 +231,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getTable(String table, String defaultSchema) {
|
||||||
|
return defaultSchema != null ? defaultSchema + "." + table : table;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loaded 100 of 3367 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user