Compare commits
646
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88aef967fe | ||
|
|
9f5f3a924c | ||
|
|
94be25a203 | ||
|
|
4291a2c9f0 | ||
|
|
e9beb83955 | ||
|
|
971fc8798a | ||
|
|
f39e6b45cc | ||
|
|
4af907295a | ||
|
|
7190030e16 | ||
|
|
8e6f7b558d | ||
|
|
fad8b2ab75 | ||
|
|
2c65361943 | ||
|
|
75d715d8a7 | ||
|
|
62b90ee4e3 | ||
|
|
2f88868e20 | ||
|
|
b0de77013c | ||
|
|
25cd3e3ba8 | ||
|
|
1b0bf966b6 | ||
|
|
d8e28ad96b | ||
|
|
3336d4d7ac | ||
|
|
ee3a880a55 | ||
|
|
c9f9ee9799 | ||
|
|
0536f2c247 | ||
|
|
6f262423f1 | ||
|
|
3c5e7872a7 | ||
|
|
421dc6043d | ||
|
|
9402f89d8e | ||
|
|
81a0135fa6 | ||
|
|
43dc250bf2 | ||
|
|
bc3222ab67 | ||
|
|
e761d6ce10 | ||
|
|
46991764d4 | ||
|
|
747c0f9869 | ||
|
|
5424cd80a8 | ||
|
|
8d2becf681 | ||
|
|
c3f69fdc54 | ||
|
|
45215f361f | ||
|
|
802488cea0 | ||
|
|
3ac3d79e30 | ||
|
|
fe8755d4a4 | ||
|
|
e2b889d727 | ||
|
|
2ec4cb82cc | ||
|
|
bc14cade62 | ||
|
|
a6c852603e | ||
|
|
f1602b3b8d | ||
|
|
7c8bc5814e | ||
|
|
33f83689d5 | ||
|
|
80e2b8eb39 | ||
|
|
fbff61bfba | ||
|
|
ba1a17e668 | ||
|
|
7bc7510d1b | ||
|
|
dc337d684a | ||
|
|
0f9d50924c | ||
|
|
cf0eb1184a | ||
|
|
ecd31010fe | ||
|
|
0985d3c88a | ||
|
|
ddd99c2411 | ||
|
|
7834637749 | ||
|
|
a90d7e54d4 | ||
|
|
bae2b62200 | ||
|
|
71bcf697cc | ||
|
|
9acb4b86aa | ||
|
|
dee0be4237 | ||
|
|
c4544e8caf | ||
|
|
bca3893070 | ||
|
|
280725a2ae | ||
|
|
2892b093ab | ||
|
|
78fe064cf0 | ||
|
|
30aaea73e4 | ||
|
|
4b1b697aa3 | ||
|
|
112a44676d | ||
|
|
f7282d71e5 | ||
|
|
9840c47970 | ||
|
|
8695e16971 | ||
|
|
65f5ce9f13 | ||
|
|
77962ec896 | ||
|
|
54712e29aa | ||
|
|
71b6ed80ae | ||
|
|
4a4bbf26f4 | ||
|
|
64de96d34b | ||
|
|
084873f7fb | ||
|
|
c684dac625 | ||
|
|
a5c159eeff | ||
|
|
1cbe5c4c80 | ||
|
|
4a472b8272 | ||
|
|
ddf74c0d9c | ||
|
|
b2a3918fc3 | ||
|
|
aca00dd42c | ||
|
|
2a8e868bc1 | ||
|
|
979205c827 | ||
|
|
55e36acfc0 | ||
|
|
4b6825806a | ||
|
|
2d878805c1 | ||
|
|
dd038ddbd5 | ||
|
|
f6a02bd408 | ||
|
|
7ec0dad88f | ||
|
|
1e3ec1983d | ||
|
|
7ec02761ef | ||
|
|
7b30a6c9c4 | ||
|
|
0d56713871 | ||
|
|
658f204d92 | ||
|
|
04401af470 | ||
|
|
39d5a07218 | ||
|
|
a65f619c9a | ||
|
|
01c408ba68 | ||
|
|
63975886cb | ||
|
|
c3f2ae1cd1 | ||
|
|
133e4c59e5 | ||
|
|
d3247bc66a | ||
|
|
b6718b44a1 | ||
|
|
15506e3db3 | ||
|
|
977f4051b3 | ||
|
|
49c15f200e | ||
|
|
3e5874b2a1 | ||
|
|
f7ac5fae2a | ||
|
|
22ba4847f4 | ||
|
|
90822e5b34 | ||
|
|
3f62cee836 | ||
|
|
3bacbdf6ff | ||
|
|
edcc39d906 | ||
|
|
3ec516d14c | ||
|
|
d8d0298498 | ||
|
|
8356409b44 | ||
|
|
57971ce0b2 | ||
|
|
b708cd671e | ||
|
|
1b857387f2 | ||
|
|
cea70337ef | ||
|
|
6d5de7b44e | ||
|
|
10eba78036 | ||
|
|
dd6b05224d | ||
|
|
e7009cac47 | ||
|
|
eece3689f2 | ||
|
|
f48753f6e1 | ||
|
|
392ce0b323 | ||
|
|
2fce7f8498 | ||
|
|
c5f19ff2b3 | ||
|
|
b24dfa0009 | ||
|
|
a9b5d07263 | ||
|
|
eddf3eef17 | ||
|
|
dbac147419 | ||
|
|
6752a4f9b0 | ||
|
|
35804aa0df | ||
|
|
41d22986d5 | ||
|
|
9172b5472e | ||
|
|
b27b0411a6 | ||
|
|
0c293089c3 | ||
|
|
5403296ac6 | ||
|
|
20548b402d | ||
|
|
1747e0981f | ||
|
|
ea6374163d | ||
|
|
4782780857 | ||
|
|
b90409c5e4 | ||
|
|
9a921f66ff | ||
|
|
4a492ce13c | ||
|
|
39904548eb | ||
|
|
18268f686c | ||
|
|
1594e07628 | ||
|
|
606e6fa479 | ||
|
|
1afd3d422c | ||
|
|
80fae7116b | ||
|
|
86a0995f29 | ||
|
|
a7c684b909 | ||
|
|
5caf3e0a2d | ||
|
|
d939b6a431 | ||
|
|
42d17577d0 | ||
|
|
eaf326dd37 | ||
|
|
53a542d1e2 | ||
|
|
c51d5e9820 | ||
|
|
817127a751 | ||
|
|
0e6e3d10d8 | ||
|
|
0197c69ac3 | ||
|
|
83eea5036f | ||
|
|
1b614a379b | ||
|
|
0527d441e3 | ||
|
|
6569b22c79 | ||
|
|
d0ec1c3b02 | ||
|
|
2fe7e2a952 | ||
|
|
0d52e4e6c5 | ||
|
|
358c273d39 | ||
|
|
215d59b1e5 | ||
|
|
2b409b3462 | ||
|
|
b4e53b1677 | ||
|
|
051f175f68 | ||
|
|
fd447702e7 | ||
|
|
ee79f5e69d | ||
|
|
de5baa9df7 | ||
|
|
566a58b5d8 | ||
|
|
f837cbfaa9 | ||
|
|
06a55419f1 | ||
|
|
82a573a536 | ||
|
|
d46bad56ae | ||
|
|
32af1bd3a5 | ||
|
|
38b9d05cd7 | ||
|
|
e26e31d8b4 | ||
|
|
2328e0a6f9 | ||
|
|
96e1813b34 | ||
|
|
97357d020b | ||
|
|
b466cec4e5 | ||
|
|
9228c6a16b | ||
|
|
35f8e3c19f | ||
|
|
9956f425eb | ||
|
|
5464e600ea | ||
|
|
cbdba622a5 | ||
|
|
49e9965125 | ||
|
|
dcaf7ea9d5 | ||
|
|
3f51f620b7 | ||
|
|
06e44b185e | ||
|
|
17a2bd266b | ||
|
|
081db0d353 | ||
|
|
b82346761c | ||
|
|
595e7a92e2 | ||
|
|
fee1dea3e6 | ||
|
|
9a58e1276a | ||
|
|
1d401767d6 | ||
|
|
68e3b6c504 | ||
|
|
813ddf365e | ||
|
|
edf3499662 | ||
|
|
df26caed07 | ||
|
|
9f9067663f | ||
|
|
0f3d8b8513 | ||
|
|
4ca3bf530f | ||
|
|
c8c0a45ce4 | ||
|
|
7313b843b9 | ||
|
|
321cac2ab8 | ||
|
|
162dd4051d | ||
|
|
cd19c1acdf | ||
|
|
44ed912154 | ||
|
|
39856c32d1 | ||
|
|
d3a893b171 | ||
|
|
ba861f227e | ||
|
|
4a57bcc0b4 | ||
|
|
87a8e017fa | ||
|
|
b8fe608aaf | ||
|
|
d3e6b38a8e | ||
|
|
8589327cdd | ||
|
|
1b936a1230 | ||
|
|
45c490b640 | ||
|
|
1dd3fc537b | ||
|
|
73cf2404ff | ||
|
|
c16b6e6a43 | ||
|
|
8d23422605 | ||
|
|
9cd8d1612e | ||
|
|
277faea1d0 | ||
|
|
58c7b6090b | ||
|
|
f45bb62fcc | ||
|
|
34c3ffaae1 | ||
|
|
a218cc3034 | ||
|
|
c62b3822f4 | ||
|
|
b0e9720f88 | ||
|
|
6507f58b49 | ||
|
|
46e2440d30 | ||
|
|
9b51b6e43b | ||
|
|
4772b8401a | ||
|
|
2b25c120d4 | ||
|
|
c19e65f93c | ||
|
|
87994e032d | ||
|
|
ee6753d31b | ||
|
|
09c18306c2 | ||
|
|
6124fd64f3 | ||
|
|
2a03d23098 | ||
|
|
ff806eae08 | ||
|
|
e44d6f9888 | ||
|
|
4e79dde17f | ||
|
|
29e05ecf0d | ||
|
|
a388782f9b | ||
|
|
352ea30d20 | ||
|
|
6e0444e17b | ||
|
|
b3f0dc7399 | ||
|
|
ad34d63cb3 | ||
|
|
703720fa43 | ||
|
|
e01c789a93 | ||
|
|
8064c88e3c | ||
|
|
b06e747d28 | ||
|
|
f9b89df326 | ||
|
|
778a96c9e4 | ||
|
|
f6f6b6a258 | ||
|
|
c4f3bd3dda | ||
|
|
9d5356a001 | ||
|
|
32fb604c88 | ||
|
|
b3ea8f3add | ||
|
|
382161c425 | ||
|
|
ec527da868 | ||
|
|
0038c1ce9a | ||
|
|
d8fb0f1540 | ||
|
|
866c11b25b | ||
|
|
64f5d032ed | ||
|
|
635310123e | ||
|
|
0bf53ad0ab | ||
|
|
bf7863b300 | ||
|
|
07e12f3b5f | ||
|
|
0f3d2bbfb4 | ||
|
|
ed30c4454a | ||
|
|
0d4771a763 | ||
|
|
a8317e0cc2 | ||
|
|
5b61a10b55 | ||
|
|
ec327c99f4 | ||
|
|
0492d84b59 | ||
|
|
6a7a823fb4 | ||
|
|
f394e2552a | ||
|
|
5a686c4260 | ||
|
|
642e93be93 | ||
|
|
2a6851b01f | ||
|
|
2726db0be9 | ||
|
|
61c2ec04a4 | ||
|
|
8d6a1d5a1d | ||
|
|
233b1e2553 | ||
|
|
956ea431c8 | ||
|
|
265b6d5e2a | ||
|
|
b22c28b20e | ||
|
|
243acdd04e | ||
|
|
c49394c308 | ||
|
|
fd5cb84dd1 | ||
|
|
54b5a6c41e | ||
|
|
83fa03e8cc | ||
|
|
6863b711e3 | ||
|
|
2c5510284d | ||
|
|
96cfb895db | ||
|
|
46f947113c | ||
|
|
fa4e32ddcb | ||
|
|
57b60797ce | ||
|
|
38d160fab2 | ||
|
|
ef80b64d1c | ||
|
|
8d2e4c0316 | ||
|
|
ae83ad258b | ||
|
|
5fa59262ce | ||
|
|
373fc23fc0 | ||
|
|
58dc8776d6 | ||
|
|
20212d8d70 | ||
|
|
cb20d360c4 | ||
|
|
8e127bf699 | ||
|
|
4d24a874f9 | ||
|
|
7200996ae0 | ||
|
|
34cc750b13 | ||
|
|
0ad6d708da | ||
|
|
c83e3bd2d1 | ||
|
|
5ea880cfff | ||
|
|
4276328794 | ||
|
|
7c4c77d70e | ||
|
|
632216d53f | ||
|
|
d5105164e9 | ||
|
|
7a9e2db18f | ||
|
|
0d582a1326 | ||
|
|
a99df49bc9 | ||
|
|
b996e88dbd | ||
|
|
426a2b46fc | ||
|
|
4a020d545b | ||
|
|
ee363a4c56 | ||
|
|
bf4d5f4df9 | ||
|
|
30ec60b4e2 | ||
|
|
a744ecba9e | ||
|
|
e764461b1a | ||
|
|
f376facb48 | ||
|
|
31614df7c4 | ||
|
|
335f852600 | ||
|
|
c26aeb654b | ||
|
|
969cc6eb9e | ||
|
|
f5326e1f42 | ||
|
|
e664a4a6b2 | ||
|
|
c15571f65c | ||
|
|
1eacdc6904 | ||
|
|
be040eaa18 | ||
|
|
889679c715 | ||
|
|
8ae3551c4d | ||
|
|
cfd4de033b | ||
|
|
d6e2bccb16 | ||
|
|
eeec89583e | ||
|
|
d2bdb74fef | ||
|
|
b04b124351 | ||
|
|
067cb120f8 | ||
|
|
0bab4e63fa | ||
|
|
b48f32380c | ||
|
|
0c8f3f734d | ||
|
|
4f401c7547 | ||
|
|
df74338cbc | ||
|
|
e3060e5e58 | ||
|
|
15309a94cc | ||
|
|
2e39221b98 | ||
|
|
c05e21c049 | ||
|
|
62d519ed37 | ||
|
|
18fa03bf97 | ||
|
|
0bdb05e152 | ||
|
|
09af45201b | ||
|
|
dd85c125cb | ||
|
|
274838aa64 | ||
|
|
ecb39cda0c | ||
|
|
3685a185d4 | ||
|
|
030a35476a | ||
|
|
4ffe86583b | ||
|
|
24f765ceef | ||
|
|
c729835c3e | ||
|
|
507fa4c250 | ||
|
|
bba781beee | ||
|
|
980a1eca5e | ||
|
|
ff63c5552a | ||
|
|
985661c1b9 | ||
|
|
b168656eab | ||
|
|
94ba87ac05 | ||
|
|
cfc28b861b | ||
|
|
38408658f9 | ||
|
|
998bb9986f | ||
|
|
8d2a95d66b | ||
|
|
500bced6ee | ||
|
|
81960a6996 | ||
|
|
ccd456dba6 | ||
|
|
bac39bc71e | ||
|
|
a8c2e79565 | ||
|
|
d6089597ff | ||
|
|
c901fc7480 | ||
|
|
4f2b97de7f | ||
|
|
9a3e023c43 | ||
|
|
142ef50728 | ||
|
|
d2ba4669ab | ||
|
|
ba0b59bfd0 | ||
|
|
f1c3295cec | ||
|
|
04ddf7ba4d | ||
|
|
8f346a5f59 | ||
|
|
22e3abdaa8 | ||
|
|
f1af3e9fb7 | ||
|
|
5e479ba4df | ||
|
|
f6743eff69 | ||
|
|
5a761f2f3a | ||
|
|
261d32f8e7 | ||
|
|
282e702ef6 | ||
|
|
cc014fe0e5 | ||
|
|
95f62901c2 | ||
|
|
cf8b0abaad | ||
|
|
e761fb24e6 | ||
|
|
b1deed67df | ||
|
|
d3cadc9a47 | ||
|
|
32e069eadc | ||
|
|
17c97c2f68 | ||
|
|
48bb4d86e0 | ||
|
|
eda018ba0a | ||
|
|
ac1baa059f | ||
|
|
670b620da7 | ||
|
|
1545593cde | ||
|
|
aedd23a43d | ||
|
|
7e8c80c0df | ||
|
|
9f6813fcad | ||
|
|
14292843ff | ||
|
|
71e25bcd65 | ||
|
|
9f1fa5ebb9 | ||
|
|
98958a2bc4 | ||
|
|
7da9f8085d | ||
|
|
6989589e72 | ||
|
|
4f00f6cceb | ||
|
|
95e729b0b7 | ||
|
|
38c460de82 | ||
|
|
41331111da | ||
|
|
bff334d365 | ||
|
|
41c9289f14 | ||
|
|
1d06d9dd64 | ||
|
|
ec9b2933cc | ||
|
|
1ff5d6eb06 | ||
|
|
bc22abf8cb | ||
|
|
81eb1e1e30 | ||
|
|
e8cb3a416d | ||
|
|
764c20d748 | ||
|
|
c81d0c0898 | ||
|
|
67fca8f1f3 | ||
|
|
22c5c803ca | ||
|
|
f6d716a386 | ||
|
|
62c5bc0e91 | ||
|
|
5e329d7b29 | ||
|
|
1bd9b18803 | ||
|
|
96fe4269ec | ||
|
|
83ff02ea53 | ||
|
|
1df741a307 | ||
|
|
409356f734 | ||
|
|
bad0a95123 | ||
|
|
1bc08fff16 | ||
|
|
ab436705c8 | ||
|
|
ea7709a104 | ||
|
|
1d5a01577e | ||
|
|
4288260aa6 | ||
|
|
600d1f8a56 | ||
|
|
f7b1af7e11 | ||
|
|
4b0e33e388 | ||
|
|
8caaabb2e5 | ||
|
|
1bd0e23f31 | ||
|
|
13a52c1bb2 | ||
|
|
fa7a7d35a9 | ||
|
|
fe5809db4d | ||
|
|
1510d9672a | ||
|
|
9b93468131 | ||
|
|
2e16a55815 | ||
|
|
f65665b5eb | ||
|
|
ce4a865579 | ||
|
|
f2e4e67ba0 | ||
|
|
37f1eaae0e | ||
|
|
6c3a3517b8 | ||
|
|
c4416a25e0 | ||
|
|
550f773293 | ||
|
|
d7ea66ad44 | ||
|
|
2022a8d36b | ||
|
|
0d20e3c7ff | ||
|
|
21119604c6 | ||
|
|
973609d39b | ||
|
|
77bccda7a4 | ||
|
|
0c9f0ea3e6 | ||
|
|
73197332c4 | ||
|
|
ee0dd7e200 | ||
|
|
9203971809 | ||
|
|
4e7fe1de8d | ||
|
|
4646a4aceb | ||
|
|
64baa28301 | ||
|
|
db84571546 | ||
|
|
99cddcef6c | ||
|
|
1891019067 | ||
|
|
b0b4d26fa3 | ||
|
|
6cc76b2e3d | ||
|
|
33ac048c8c | ||
|
|
e25157655b | ||
|
|
213abb87ba | ||
|
|
825a68c6c9 | ||
|
|
816ed9419b | ||
|
|
15f1a38b51 | ||
|
|
1f75f85a20 | ||
|
|
dfb39d13a2 | ||
|
|
b8391e0935 | ||
|
|
ab56460325 | ||
|
|
059ebacffc | ||
|
|
e1a07bd5a8 | ||
|
|
37d7182fc9 | ||
|
|
e1ba26bcae | ||
|
|
b39146f6f5 | ||
|
|
caea1a3458 | ||
|
|
7ef747e7c4 | ||
|
|
4ca442d1b2 | ||
|
|
99e75c69a0 | ||
|
|
1437f7da35 | ||
|
|
fa9abbf016 | ||
|
|
6bce32ba62 | ||
|
|
ee801c2c32 | ||
|
|
2b23497f92 | ||
|
|
04ba5c235e | ||
|
|
1521f56225 | ||
|
|
251adbba0b | ||
|
|
adbf2b22ad | ||
|
|
2b29c3acf4 | ||
|
|
076690e21e | ||
|
|
b586f93182 | ||
|
|
5a318df041 | ||
|
|
cd01ea49b4 | ||
|
|
dd2824ab20 | ||
|
|
8db6972441 | ||
|
|
027311c0c0 | ||
|
|
68edf9ce48 | ||
|
|
7b7fbd3257 | ||
|
|
d0c50fe56d | ||
|
|
4075d65241 | ||
|
|
267be83863 | ||
|
|
15828e899b | ||
|
|
ce089019e6 | ||
|
|
c9c49a9c64 | ||
|
|
623f39862d | ||
|
|
818c3ab1c5 | ||
|
|
1ce028ead7 | ||
|
|
d844e6b4af | ||
|
|
7b4dcb6965 | ||
|
|
7bd0b7ddfc | ||
|
|
d2912673b0 | ||
|
|
28ee95138a | ||
|
|
d46b4eb5fe | ||
|
|
151c56a304 | ||
|
|
c2ad918975 | ||
|
|
634eadb634 | ||
|
|
d26cfcda42 | ||
|
|
4a9a39c08d | ||
|
|
9796b7f06e | ||
|
|
c2e14cb924 | ||
|
|
d896800ec6 | ||
|
|
b74e0dad10 | ||
|
|
2c8b0fc3e1 | ||
|
|
cd640339fc | ||
|
|
52c02f03ae | ||
|
|
84da341912 | ||
|
|
2c3958ce27 | ||
|
|
515f049ce6 | ||
|
|
03acad9b9d | ||
|
|
0392ebab32 | ||
|
|
52804da497 | ||
|
|
31df2046f0 | ||
|
|
f14681f122 | ||
|
|
d7d52b3295 | ||
|
|
8e71910e00 | ||
|
|
e6620ca005 | ||
|
|
490b8ce86f | ||
|
|
480444ffc5 | ||
|
|
2fd7e5b355 | ||
|
|
f45e0aa387 | ||
|
|
6c77c295d5 | ||
|
|
ccfaf72b09 | ||
|
|
700740a378 | ||
|
|
8062f5f50e | ||
|
|
5e0851c25f | ||
|
|
81650a89f9 | ||
|
|
709449243c | ||
|
|
df24d8a01f | ||
|
|
f8f4de9389 | ||
|
|
9755d79879 | ||
|
|
70cbeaf336 | ||
|
|
b0a71fbeb0 | ||
|
|
44f56e1d74 | ||
|
|
74924f2f8c | ||
|
|
c498b06f68 | ||
|
|
525bee66a4 | ||
|
|
cd51a95764 | ||
|
|
57c282eca8 | ||
|
|
ebd02a1a7f | ||
|
|
cc4583bc0b | ||
|
|
11ef67722b | ||
|
|
5316dd6d50 | ||
|
|
3cee33847f | ||
|
|
a2b5b6dc71 | ||
|
|
a5dc91f9a6 | ||
|
|
3b4b1b20d8 | ||
|
|
b32423d96b | ||
|
|
4f2fe0197d | ||
|
|
aa25c899ee | ||
|
|
fa6b99eaab | ||
|
|
e04dbffb88 | ||
|
|
f823b17b90 | ||
|
|
7dfb7ddccd | ||
|
|
4b444d36b0 | ||
|
|
23546402fa | ||
|
|
edfc7ea103 | ||
|
|
3a7c84c9e1 | ||
|
|
c860c671d1 | ||
|
|
072950e9f3 | ||
|
|
8226abef40 | ||
|
|
0d05d38eb6 | ||
|
|
4f4368b29f | ||
|
|
3f8312427a | ||
|
|
73bb579dc5 | ||
|
|
c75528f81e | ||
|
|
28bfac7214 | ||
|
|
1c9a347391 | ||
|
|
df03f51563 | ||
|
|
7d590761dd | ||
|
|
944c416b45 | ||
|
|
028cbe1ce2 | ||
|
|
186de7235c | ||
|
|
9433ef284a | ||
|
|
32647d8682 | ||
|
|
d8ebf1e3d6 |
No files matched your search
+10
-8
@@ -1,19 +1,21 @@
|
||||
language: java
|
||||
|
||||
env:
|
||||
global:
|
||||
- MAVEN_SKIP_RC=true
|
||||
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
before_script:
|
||||
- export MAVEN_SKIP_RC=true
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||
|
||||
install: mvn install -Pdistribution -DskipTests=true -B -V
|
||||
install:
|
||||
- mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||
|
||||
script:
|
||||
- mvn test -B
|
||||
- mvn -file testsuite/integration-arquillian test -B
|
||||
- mvn -file testsuite/integration-arquillian test -B -Pno-console
|
||||
|
||||
sudo: false
|
||||
@@ -7,7 +7,7 @@ Keycloak is an SSO Service for web apps and REST services. For more information
|
||||
Building
|
||||
--------
|
||||
|
||||
Ensure you have JDK 7 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||
|
||||
java -version
|
||||
mvn -version
|
||||
|
||||
+5
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -45,6 +45,10 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.annotation</groupId>
|
||||
<artifactId>jboss-annotations-api_1.1_spec</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -92,4 +92,9 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderDataMarshaller getMarshaller() {
|
||||
return new DefaultDataMarshaller();
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -1,10 +1,10 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||
import org.keycloak.models.IdentityProviderMapperModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
@@ -35,4 +35,9 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,13 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -152,6 +156,22 @@ public class BrokeredIdentityContext {
|
||||
this.contextData = contextData;
|
||||
}
|
||||
|
||||
// Set the attribute, which will be available on "Update profile" page and in authenticators
|
||||
public void setUserAttribute(String attributeName, String attributeValue) {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add(attributeValue);
|
||||
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
|
||||
}
|
||||
|
||||
public String getUserAttribute(String attributeName) {
|
||||
List<String> userAttribute = (List<String>) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
|
||||
if (userAttribute == null || userAttribute.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return userAttribute.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
|
||||
|
||||
@Override
|
||||
public String serialize(Object value) {
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
} else {
|
||||
try {
|
||||
byte[] bytes = JsonSerialization.writeValueAsBytes(value);
|
||||
return Base64Url.encode(bytes);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
||||
try {
|
||||
if (clazz.equals(String.class)) {
|
||||
return clazz.cast(serialized);
|
||||
} else {
|
||||
byte[] bytes = Base64Url.decode(serialized);
|
||||
if (List.class.isAssignableFrom(clazz)) {
|
||||
List list = JsonSerialization.readValue(bytes, List.class);
|
||||
return clazz.cast(list);
|
||||
} else {
|
||||
return JsonSerialization.readValue(bytes, clazz);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -69,10 +69,10 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
user.setSingleAttribute(attribute, attributeValue);
|
||||
context.setUserAttribute(attribute, attributeValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
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.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -20,7 +17,7 @@ import java.util.List;
|
||||
*/
|
||||
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||
public static final String ROLE = "role";
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
||||
|
||||
static {
|
||||
ProviderConfigProperty property;
|
||||
@@ -32,34 +29,6 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||
configProperties.add(property);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String[] parseRole(String role) {
|
||||
int scopeIndex = role.lastIndexOf('.');
|
||||
if (scopeIndex > -1) {
|
||||
String appName = role.substring(0, scopeIndex);
|
||||
role = role.substring(scopeIndex + 1);
|
||||
String[] rtn = {appName, role};
|
||||
return rtn;
|
||||
} else {
|
||||
String[] rtn = {null, role};
|
||||
return rtn;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
|
||||
String[] parsedRole = parseRole(roleName);
|
||||
RoleModel role = null;
|
||||
if (parsedRole[0] == null) {
|
||||
role = realm.getRole(parsedRole[1]);
|
||||
} else {
|
||||
ClientModel client = realm.getClientByClientId(parsedRole[0]);
|
||||
role = client.getRole(parsedRole[1]);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configProperties;
|
||||
@@ -93,7 +62,7 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(ROLE);
|
||||
RoleModel role = getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.grantRole(role);
|
||||
}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||
|
||||
@@ -103,4 +103,10 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
||||
*/
|
||||
Response export(UriInfo uriInfo, RealmModel realm, String format);
|
||||
|
||||
/**
|
||||
* Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
|
||||
* @return
|
||||
*/
|
||||
IdentityProviderDataMarshaller getMarshaller();
|
||||
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package org.keycloak.broker.provider;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface IdentityProviderDataMarshaller {
|
||||
|
||||
String serialize(Object obj);
|
||||
<T> T deserialize(String serialized, Class<T> clazz);
|
||||
|
||||
}
|
||||
@@ -20,8 +20,10 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
||||
String getDisplayType();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Called to determine what keycloak username and email to use to process the login request from the external IDP.
|
||||
* It's called before "FirstBrokerLogin" flow, so can be used to map attributes to BrokeredIdentityContext ( BrokeredIdentityContext.setUserAttribute ),
|
||||
* which will be available on "Review Profile" page and in authenticators during FirstBrokerLogin flow
|
||||
*
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
@@ -31,7 +33,7 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||
|
||||
/**
|
||||
* Called after UserModel is created for first time for this user.
|
||||
* Called after UserModel is created for first time for this user. Called after "FirstBrokerLogin" flow
|
||||
*
|
||||
* @param session
|
||||
* @param realm
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.keycloak.broker.provider.util;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -24,6 +27,9 @@ public class SimpleHttp {
|
||||
private Map<String, String> headers;
|
||||
private Map<String, String> params;
|
||||
|
||||
private SSLSocketFactory sslFactory;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
|
||||
protected SimpleHttp(String url, String method) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
@@ -53,6 +59,15 @@ public class SimpleHttp {
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleHttp sslFactory(SSLSocketFactory factory) {
|
||||
sslFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleHttp hostnameVerifier(HostnameVerifier verifier) {
|
||||
hostnameVerifier = verifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String asString() throws IOException {
|
||||
boolean get = method.equals("GET");
|
||||
@@ -85,6 +100,7 @@ public class SimpleHttp {
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
setupTruststoreIfApplicable(connection);
|
||||
OutputStream os = null;
|
||||
InputStream is = null;
|
||||
|
||||
@@ -171,6 +187,7 @@ public class SimpleHttp {
|
||||
}
|
||||
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
setupTruststoreIfApplicable(connection);
|
||||
OutputStream os = null;
|
||||
InputStream is = null;
|
||||
|
||||
@@ -235,4 +252,13 @@ public class SimpleHttp {
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private void setupTruststoreIfApplicable(HttpURLConnection connection) {
|
||||
if (connection instanceof HttpsURLConnection && sslFactory != null) {
|
||||
HttpsURLConnection con = (HttpsURLConnection) connection;
|
||||
con.setSSLSocketFactory(sslFactory);
|
||||
if (hostnameVerifier != null) {
|
||||
con.setHostnameVerifier(hostnameVerifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+5
-1
@@ -27,6 +27,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.connections.truststore.JSSETruststoreConfigurator;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
@@ -247,12 +248,15 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||
}
|
||||
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
JSSETruststoreConfigurator configurator = new JSSETruststoreConfigurator(session);
|
||||
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
||||
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
||||
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
|
||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE)
|
||||
.sslFactory(configurator.getSSLSocketFactory())
|
||||
.hostnameVerifier(configurator.getHostnameVerifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -5,6 +5,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
@@ -51,7 +52,13 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||
@POST
|
||||
@Path(AdapterConstants.K_LOGOUT)
|
||||
public Response backchannelLogout(String input) {
|
||||
JWSInput token = new JWSInput(input);
|
||||
JWSInput token = null;
|
||||
try {
|
||||
token = new JWSInput(input);
|
||||
} catch (JWSInputException e) {
|
||||
logger.warn("Failed to verify logout request");
|
||||
return Response.status(400).build();
|
||||
}
|
||||
PublicKey key = getExternalIdpKey();
|
||||
if (key != null) {
|
||||
if (!verify(token, key)) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
@@ -282,40 +283,41 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
||||
throw new IdentityBrokerException("No token from server.");
|
||||
}
|
||||
|
||||
JsonWebToken token;
|
||||
try {
|
||||
JWSInput jws = new JWSInput(encodedToken);
|
||||
if (!verify(jws, key)) {
|
||||
throw new IdentityBrokerException("token signature validation failed");
|
||||
}
|
||||
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
|
||||
|
||||
String iss = token.getIssuer();
|
||||
|
||||
if (!token.hasAudience(getConfig().getClientId())) {
|
||||
throw new IdentityBrokerException("Wrong audience from token.");
|
||||
}
|
||||
|
||||
if (!token.isActive()) {
|
||||
throw new IdentityBrokerException("Token is no longer valid");
|
||||
}
|
||||
|
||||
String trustedIssuers = getConfig().getIssuer();
|
||||
|
||||
if (trustedIssuers != null) {
|
||||
String[] issuers = trustedIssuers.split(",");
|
||||
|
||||
for (String trustedIssuer : issuers) {
|
||||
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
||||
}
|
||||
return token;
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not decode token.", e);
|
||||
token = jws.readJsonContent(JsonWebToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new IdentityBrokerException("Invalid token", e);
|
||||
}
|
||||
|
||||
String iss = token.getIssuer();
|
||||
|
||||
if (!token.hasAudience(getConfig().getClientId())) {
|
||||
throw new IdentityBrokerException("Wrong audience from token.");
|
||||
}
|
||||
|
||||
if (!token.isActive()) {
|
||||
throw new IdentityBrokerException("Token is no longer valid");
|
||||
}
|
||||
|
||||
String trustedIssuers = getConfig().getIssuer();
|
||||
|
||||
if (trustedIssuers != null) {
|
||||
String[] issuers = trustedIssuers.split(",");
|
||||
|
||||
for (String trustedIssuer : issuers) {
|
||||
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -89,7 +89,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
|
||||
} else if (value instanceof List) {
|
||||
List list = (List)value;
|
||||
for (Object val : list) {
|
||||
return valueEquals(desiredValue, val);
|
||||
if (valueEquals(desiredValue, val)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
+8
-8
@@ -69,8 +69,8 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||
* @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 #preprocessFederatedIdentity(KeycloakSession, RealmModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
||||
* @see BrokeredIdentityContext#getContextData()
|
||||
*/
|
||||
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
||||
@@ -100,17 +100,17 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
||||
if (attribute == null || attribute.trim().isEmpty()) {
|
||||
logger.debug("Attribute is not configured");
|
||||
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
|
||||
return;
|
||||
}
|
||||
attribute = attribute.trim();
|
||||
|
||||
String value = getJsonValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setSingleAttribute(attribute, value);
|
||||
context.setUserAttribute(attribute, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,13 +123,13 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||
|
||||
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
||||
if (jsonField == null || jsonField.trim().isEmpty()) {
|
||||
logger.debug("JSON field path is not configured");
|
||||
logger.warnf("JSON field path is not configured for mapper %s", mapperModel.getName());
|
||||
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);
|
||||
logger.warnf("JSON field path is invalid %s", jsonField);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
||||
String value = getJsonValue(profileJsonNode, jsonField);
|
||||
|
||||
if (value == null) {
|
||||
logger.debug("User profile JSON value '" + jsonField + "' is not available.");
|
||||
logger.debugf("User profile JSON value '%s' is not available.", jsonField);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -80,7 +81,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (hasClaimValue(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.grantRole(role);
|
||||
}
|
||||
@@ -90,7 +91,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (!hasClaimValue(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.deleteRoleMapping(role);
|
||||
}
|
||||
|
||||
+3
-2
@@ -11,6 +11,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
@@ -85,7 +86,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||
//if (token == null) return;
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
||||
String[] parseRole = KeycloakModelUtils.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
||||
String externalRoleName = parseRole[1];
|
||||
String claimName = null;
|
||||
if (parseRole[0] == null) {
|
||||
@@ -95,7 +96,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
||||
}
|
||||
Object claim = getClaimValue(token, claimName);
|
||||
if (valueEquals(externalRoleName, claim)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
return role;
|
||||
}
|
||||
|
||||
@@ -70,11 +70,11 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getClaimValue(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
context.setUserAttribute(attribute, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,6 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
|
||||
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) {
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+6
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -45,6 +45,11 @@
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.keycloak.broker.saml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
|
||||
import org.keycloak.broker.provider.DefaultDataMarshaller;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||
import org.keycloak.saml.common.util.StaxUtil;
|
||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||
import org.keycloak.saml.processing.core.parsers.util.SAMLParserUtil;
|
||||
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
|
||||
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLResponseWriter;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SAMLDataMarshaller extends DefaultDataMarshaller {
|
||||
|
||||
@Override
|
||||
public String serialize(Object obj) {
|
||||
|
||||
// Lame impl, but hopefully sufficient for now. See if something better is needed...
|
||||
if (obj.getClass().getName().startsWith("org.keycloak.dom.saml")) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
if (obj instanceof ResponseType) {
|
||||
ResponseType responseType = (ResponseType) obj;
|
||||
SAMLResponseWriter samlWriter = new SAMLResponseWriter(StaxUtil.getXMLStreamWriter(bos));
|
||||
samlWriter.write(responseType);
|
||||
} else if (obj instanceof AssertionType) {
|
||||
AssertionType assertion = (AssertionType) obj;
|
||||
SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
|
||||
samlWriter.write(assertion);
|
||||
} else if (obj instanceof AuthnStatementType) {
|
||||
AuthnStatementType authnStatement = (AuthnStatementType) obj;
|
||||
SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
|
||||
samlWriter.write(authnStatement, true);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Don't know how to serialize object of type " + obj.getClass().getName());
|
||||
}
|
||||
} catch (ProcessingException pe) {
|
||||
throw new RuntimeException(pe);
|
||||
}
|
||||
|
||||
return new String(bos.toByteArray());
|
||||
} else {
|
||||
return super.serialize(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
||||
if (clazz.getName().startsWith("org.keycloak.dom.saml")) {
|
||||
String xmlString = serialized;
|
||||
|
||||
try {
|
||||
if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) {
|
||||
byte[] bytes = xmlString.getBytes();
|
||||
InputStream is = new ByteArrayInputStream(bytes);
|
||||
Object respType = new SAMLParser().parse(is);
|
||||
return clazz.cast(respType);
|
||||
} else if (clazz.equals(AuthnStatementType.class)) {
|
||||
byte[] bytes = xmlString.getBytes();
|
||||
InputStream is = new ByteArrayInputStream(bytes);
|
||||
XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is);
|
||||
AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader);
|
||||
return clazz.cast(authnStatement);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Don't know how to deserialize object of type " + clazz.getName());
|
||||
}
|
||||
} catch (ParsingException pe) {
|
||||
throw new RuntimeException(pe);
|
||||
}
|
||||
|
||||
} else {
|
||||
return super.deserialize(serialized, clazz);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.keycloak.broker.saml;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
@@ -45,6 +46,7 @@ import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
@@ -95,6 +97,13 @@ public class SAMLEndpoint {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("descriptor")
|
||||
public Response getSPDescriptor() {
|
||||
return provider.export(uriInfo, realm, null);
|
||||
}
|
||||
|
||||
@GET
|
||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||
@@ -263,4 +264,8 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||
return SignatureAlgorithm.RSA_SHA256;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderDataMarshaller getMarshaller() {
|
||||
return new SAMLDataMarshaller();
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -15,6 +15,7 @@ import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
@@ -95,7 +96,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (isAttributePresent(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.grantRole(role);
|
||||
}
|
||||
@@ -125,7 +126,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||
if (!isAttributePresent(mapperModel, context)) {
|
||||
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||
user.deleteRoleMapping(role);
|
||||
}
|
||||
|
||||
@@ -80,11 +80,11 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||
Object value = getAttribute(mapperModel, context);
|
||||
if (value != null) {
|
||||
user.setSingleAttribute(attribute, value.toString());
|
||||
context.setUserAttribute(attribute, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,11 +71,6 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
|
||||
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) {
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.keycloak.broker.saml;
|
||||
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SAMLDataMarshallerTest {
|
||||
|
||||
private static final String TEST_RESPONSE = "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_4804cf50-cd96-4b92-823e-89adaa0c78ba\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.920Z\" Destination=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\" InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></samlp:StatusCode></samlp:Status><saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
|
||||
|
||||
private static final String TEST_ASSERTION = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>";
|
||||
|
||||
private static final String TEST_AUTHN_TYPE = "<saml:AuthnStatement xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
|
||||
|
||||
@Test
|
||||
public void testParseResponse() throws Exception {
|
||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
||||
ResponseType responseType = serializer.deserialize(TEST_RESPONSE, ResponseType.class);
|
||||
|
||||
// test ResponseType
|
||||
Assert.assertEquals(responseType.getID(), "ID_4804cf50-cd96-4b92-823e-89adaa0c78ba");
|
||||
Assert.assertEquals(responseType.getDestination(), "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint");
|
||||
Assert.assertEquals(responseType.getIssuer().getValue(), "http://localhost:8082/auth/realms/realm-with-saml-idp-basic");
|
||||
Assert.assertEquals(responseType.getAssertions().get(0).getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
|
||||
|
||||
// back to String
|
||||
String serialized = serializer.serialize(responseType);
|
||||
Assert.assertEquals(TEST_RESPONSE, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAssertion() throws Exception {
|
||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
||||
AssertionType assertion = serializer.deserialize(TEST_ASSERTION, AssertionType.class);
|
||||
|
||||
// test assertion
|
||||
Assert.assertEquals(assertion.getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
|
||||
Assert.assertEquals(((NameIDType) assertion.getSubject().getSubType().getBaseID()).getValue(), "test-user");
|
||||
|
||||
// back to String
|
||||
String serialized = serializer.serialize(assertion);
|
||||
Assert.assertEquals(TEST_ASSERTION, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseAuthnType() throws Exception {
|
||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
||||
AuthnStatementType authnStatement = serializer.deserialize(TEST_AUTHN_TYPE, AuthnStatementType.class);
|
||||
|
||||
// test authnStatement
|
||||
Assert.assertEquals(authnStatement.getSessionIndex(), "fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5");
|
||||
|
||||
// back to String
|
||||
String serialized = serializer.serialize(authnStatement);
|
||||
Assert.assertEquals(TEST_AUTHN_TYPE, serialized);
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
<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>
|
||||
<artifactId>keycloak-client-registration-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-client-api</artifactId>
|
||||
<name>Keycloak Client API</name>
|
||||
<artifactId>keycloak-client-registration-api</artifactId>
|
||||
<name>Keycloak Client Registration API</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
@@ -21,11 +21,6 @@
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class Auth {
|
||||
|
||||
public abstract void addAuth(HttpRequest request);
|
||||
|
||||
public static Auth token(String token) {
|
||||
return new BearerTokenAuth(token);
|
||||
}
|
||||
|
||||
public static Auth token(ClientInitialAccessPresentation initialAccess) {
|
||||
return new BearerTokenAuth(initialAccess.getToken());
|
||||
}
|
||||
|
||||
public static Auth token(ClientRepresentation client) {
|
||||
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
||||
}
|
||||
|
||||
public static Auth token(OIDCClientRepresentation client) {
|
||||
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
||||
}
|
||||
|
||||
public static Auth client(String clientId, String clientSecret) {
|
||||
return new BasicAuth(clientId, clientSecret);
|
||||
}
|
||||
|
||||
private static class BearerTokenAuth extends Auth {
|
||||
|
||||
private String token;
|
||||
|
||||
public BearerTokenAuth(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAuth(HttpRequest request) {
|
||||
request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BasicAuth extends Auth {
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public BasicAuth(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAuth(HttpRequest request) {
|
||||
String val = Base64.encodeBytes((username + ":" + password).getBytes());
|
||||
request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
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 {
|
||||
|
||||
public static final ObjectMapper outputMapper = new ObjectMapper();
|
||||
static {
|
||||
outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
|
||||
outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class);
|
||||
outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
|
||||
}
|
||||
|
||||
private final String JSON = "application/json";
|
||||
private final String XML = "application/xml";
|
||||
|
||||
private final String DEFAULT = "default";
|
||||
private final String INSTALLATION = "install";
|
||||
private final String OIDC = "openid-connect";
|
||||
private final String SAML = "saml2-entity-descriptor";
|
||||
|
||||
private HttpUtil httpUtil;
|
||||
|
||||
public static ClientRegistrationBuilder create() {
|
||||
return new ClientRegistrationBuilder();
|
||||
}
|
||||
|
||||
ClientRegistration(HttpUtil httpUtil) {
|
||||
this.httpUtil = httpUtil;
|
||||
}
|
||||
|
||||
public void close() throws ClientRegistrationException {
|
||||
if (httpUtil != null) {
|
||||
httpUtil.close();
|
||||
}
|
||||
httpUtil = null;
|
||||
}
|
||||
|
||||
public ClientRegistration auth(Auth auth) {
|
||||
httpUtil.setAuth(auth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
|
||||
return deserialize(resultStream, ClientRepresentation.class);
|
||||
}
|
||||
|
||||
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId);
|
||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId);
|
||||
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
|
||||
}
|
||||
|
||||
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
|
||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
public void delete(ClientRepresentation client) throws ClientRegistrationException {
|
||||
delete(client.getClientId());
|
||||
}
|
||||
|
||||
public void delete(String clientId) throws ClientRegistrationException {
|
||||
httpUtil.doDelete(DEFAULT, clientId);
|
||||
}
|
||||
|
||||
public OIDCClientRegistration oidc() {
|
||||
return new OIDCClientRegistration();
|
||||
}
|
||||
|
||||
public SAMLClientRegistration saml() {
|
||||
return new SAMLClientRegistration();
|
||||
}
|
||||
|
||||
public static String serialize(Object obj) throws ClientRegistrationException {
|
||||
try {
|
||||
return outputMapper.writeValueAsString(obj);
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to write json object", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <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 class OIDCClientRegistration {
|
||||
|
||||
public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
|
||||
return deserialize(resultStream, OIDCClientRepresentation.class);
|
||||
}
|
||||
|
||||
public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId);
|
||||
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
|
||||
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
public void delete(OIDCClientRepresentation client) throws ClientRegistrationException {
|
||||
delete(client.getClientId());
|
||||
}
|
||||
|
||||
public void delete(String clientId) throws ClientRegistrationException {
|
||||
httpUtil.doDelete(OIDC, clientId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SAMLClientRegistration {
|
||||
|
||||
public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
|
||||
return deserialize(resultStream, ClientRepresentation.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ClientRegistrationBuilder {
|
||||
|
||||
private String url;
|
||||
private HttpClient httpClient;
|
||||
|
||||
ClientRegistrationBuilder() {
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder url(String realmUrl) {
|
||||
url = realmUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder url(String authUrl, String realm) {
|
||||
url = HttpUtil.getUrl(authUrl, "realms", realm, "clients");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistration build() {
|
||||
if (url == null) {
|
||||
throw new IllegalStateException("url not configured");
|
||||
}
|
||||
|
||||
if (httpClient == null) {
|
||||
httpClient = HttpClients.createDefault();
|
||||
}
|
||||
|
||||
return new ClientRegistration(new HttpUtil(httpClient, url));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File renamed without changes.
+13
@@ -0,0 +1,13 @@
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
abstract class ClientRepresentationMixIn {
|
||||
|
||||
@JsonIgnore
|
||||
String registrationAccessToken;
|
||||
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,171 @@
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
class HttpUtil {
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
private String baseUri;
|
||||
|
||||
private Auth auth;
|
||||
|
||||
HttpUtil(HttpClient httpClient, String baseUri) {
|
||||
this.httpClient = httpClient;
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
void setAuth(Auth auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
InputStream doPost(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream doGet(String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||
|
||||
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 {
|
||||
if (responseStream != null) {
|
||||
responseStream.close();
|
||||
}
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream doPut(String content, String contentType, String acceptType, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent();
|
||||
}
|
||||
|
||||
InputStream responseStream = null;
|
||||
if (response.getEntity() != null) {
|
||||
responseStream = response.getEntity().getContent();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 200) {
|
||||
return responseStream;
|
||||
} else {
|
||||
if (responseStream != null) {
|
||||
responseStream.close();
|
||||
}
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
void doDelete(String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpDelete request = new HttpDelete(getUrl(baseUri, path));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 204) {
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
void close() throws ClientRegistrationException {
|
||||
if (httpClient instanceof CloseableHttpClient) {
|
||||
try {
|
||||
((CloseableHttpClient) httpClient).close();
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to close http client", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String getUrl(String baseUri, String... path) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(baseUri);
|
||||
for (String p : path) {
|
||||
s.append('/');
|
||||
s.append(p);
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private void addAuth(HttpRequestBase request) {
|
||||
if (auth != null) {
|
||||
auth.addAuth(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package org.keycloak.client.registration;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
abstract class OIDCClientRepresentationMixIn {
|
||||
|
||||
@JsonIgnore
|
||||
private Integer client_id_issued_at;
|
||||
|
||||
@JsonIgnore
|
||||
private Integer client_secret_expires_at;
|
||||
|
||||
@JsonIgnore
|
||||
private String registration_client_uri;
|
||||
|
||||
@JsonIgnore
|
||||
private String registration_access_token;
|
||||
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
<?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-client-registration-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.7.0.Final-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-client-registration-cli</artifactId>
|
||||
<name>Keycloak Client Registration CLI</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-client-registration-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.aesh</groupId>
|
||||
<artifactId>aesh</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package org.keycloak.client.registration.cli;
|
||||
|
||||
import org.jboss.aesh.cl.parser.CommandLineParserException;
|
||||
import org.jboss.aesh.console.AeshConsole;
|
||||
import org.jboss.aesh.console.AeshConsoleBuilder;
|
||||
import org.jboss.aesh.console.Prompt;
|
||||
import org.jboss.aesh.console.command.Command;
|
||||
import org.jboss.aesh.console.command.CommandNotFoundException;
|
||||
import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
|
||||
import org.jboss.aesh.console.settings.Settings;
|
||||
import org.jboss.aesh.console.settings.SettingsBuilder;
|
||||
import org.jboss.aesh.terminal.Color;
|
||||
import org.jboss.aesh.terminal.TerminalColor;
|
||||
import org.jboss.aesh.terminal.TerminalString;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.client.registration.cli.commands.CreateCommand;
|
||||
import org.keycloak.client.registration.cli.commands.ExitCommand;
|
||||
import org.keycloak.client.registration.cli.commands.SetupCommand;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientRegistrationCLI {
|
||||
|
||||
private static ClientRegistration reg;
|
||||
|
||||
public static void main(String[] args) throws CommandLineParserException, CommandNotFoundException {
|
||||
reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/master").build();
|
||||
reg.auth(Auth.token("..."));
|
||||
|
||||
Context context = new Context();
|
||||
|
||||
List<Command> commands = new LinkedList<>();
|
||||
commands.add(new SetupCommand(context));
|
||||
commands.add(new CreateCommand(context));
|
||||
commands.add(new ExitCommand(context));
|
||||
|
||||
SettingsBuilder builder = new SettingsBuilder().logging(true);
|
||||
builder.enableMan(true).readInputrc(false);
|
||||
|
||||
Settings settings = builder.create();
|
||||
|
||||
AeshCommandRegistryBuilder commandRegistryBuilder = new AeshCommandRegistryBuilder();
|
||||
for (Command c : commands) {
|
||||
commandRegistryBuilder.command(c);
|
||||
}
|
||||
|
||||
AeshConsole aeshConsole = new AeshConsoleBuilder()
|
||||
.commandRegistry(commandRegistryBuilder.create())
|
||||
.settings(settings)
|
||||
.prompt(new Prompt(new TerminalString("[clientreg]$ ",
|
||||
new TerminalColor(Color.GREEN, Color.DEFAULT, Color.Intensity.BRIGHT))))
|
||||
.create();
|
||||
|
||||
aeshConsole.start();
|
||||
|
||||
|
||||
/*
|
||||
if (args.length > 0) {
|
||||
CommandContainer command = registry.getCommand(args[0], null);
|
||||
ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
|
||||
}*/
|
||||
|
||||
//commandInvocation.getCommandRegistry().getAllCommandNames()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.keycloak.client.registration.cli;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
||||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Context {
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
|
||||
static {
|
||||
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
|
||||
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
|
||||
}
|
||||
|
||||
private ClientRegistration reg;
|
||||
|
||||
public ClientRegistration getReg() {
|
||||
return reg;
|
||||
}
|
||||
|
||||
public void setReg(ClientRegistration reg) {
|
||||
this.reg = reg;
|
||||
}
|
||||
|
||||
public static <T> T readJson(InputStream bytes, Class<T> type) throws IOException {
|
||||
return mapper.readValue(bytes, type);
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.keycloak.client.registration.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.Arguments;
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
import org.jboss.aesh.console.command.Command;
|
||||
import org.jboss.aesh.console.command.CommandResult;
|
||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
||||
import org.jboss.aesh.io.Resource;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.cli.Context;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@CommandDefinition(name="create", description = "[OPTIONS] FILE")
|
||||
public class CreateCommand implements Command {
|
||||
|
||||
@Option(shortName = 'h', hasValue = false, description = "display this help and exit")
|
||||
private boolean help;
|
||||
|
||||
@Arguments(description = "files or directories thats listed")
|
||||
private List<Resource> arguments;
|
||||
|
||||
private Context context;
|
||||
|
||||
public CreateCommand(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
||||
System.out.println(help);
|
||||
|
||||
|
||||
if(help) {
|
||||
commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
|
||||
}
|
||||
else {
|
||||
|
||||
if(arguments != null) {
|
||||
for(Resource f : arguments) {
|
||||
System.out.println(f.getAbsolutePath());
|
||||
ClientRepresentation rep = JsonSerialization.readValue(f.read(), ClientRepresentation.class);
|
||||
try {
|
||||
context.getReg().create(rep);
|
||||
} catch (ClientRegistrationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// reg.create();
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package org.keycloak.client.registration.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.console.command.Command;
|
||||
import org.jboss.aesh.console.command.CommandResult;
|
||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
||||
import org.keycloak.client.registration.cli.Context;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@CommandDefinition(name="exit", description = "Exit the program")
|
||||
public class ExitCommand implements Command {
|
||||
|
||||
private Context context;
|
||||
|
||||
public ExitCommand(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
||||
commandInvocation.stop();
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.keycloak.client.registration.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
import org.jboss.aesh.console.command.Command;
|
||||
import org.jboss.aesh.console.command.CommandResult;
|
||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
||||
import org.jboss.aesh.io.Resource;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.cli.Context;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@CommandDefinition(name="setup", description = "")
|
||||
public class SetupCommand implements Command {
|
||||
|
||||
@Option(shortName = 'h', hasValue = false, description = "display this help and exit")
|
||||
private boolean help;
|
||||
|
||||
private Context context;
|
||||
|
||||
public SetupCommand(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
||||
System.out.println(help);
|
||||
|
||||
if(help) {
|
||||
commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
private String promptForUsername(CommandInvocation invocation) throws InterruptedException {
|
||||
invocation.print("username: ");
|
||||
return invocation.getInputLine();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<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.8.0.CR1</version>
|
||||
</parent>
|
||||
|
||||
<name>Keycloak Client Registration Parent</name>
|
||||
<description/>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-client-registration-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>api</module>
|
||||
<!--<module>cli</module>-->
|
||||
</modules>
|
||||
</project>
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -12,6 +12,8 @@ import java.util.Properties;
|
||||
*/
|
||||
public class Version {
|
||||
public static final String UNKNOWN = "UNKNOWN";
|
||||
public static String NAME;
|
||||
public static String NAME_HTML;
|
||||
public static String VERSION;
|
||||
public static String RESOURCES_VERSION;
|
||||
public static String BUILD_TIME;
|
||||
@@ -21,6 +23,8 @@ public class Version {
|
||||
InputStream is = Version.class.getResourceAsStream("/keycloak-version.properties");
|
||||
try {
|
||||
props.load(is);
|
||||
Version.NAME = props.getProperty("name");
|
||||
Version.NAME_HTML = props.getProperty("name-html");
|
||||
Version.VERSION = props.getProperty("version");
|
||||
Version.BUILD_TIME = props.getProperty("build-time");
|
||||
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
||||
|
||||
@@ -23,4 +23,19 @@ public class CollectionUtil {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Return true if all items from col1 are in col2 and viceversa. Order is not taken into account
|
||||
public static <T> boolean collectionEquals(Collection<T> col1, Collection<T> col2) {
|
||||
if (col1.size() != col2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (T item : col1) {
|
||||
if (!col2.contains(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -341,6 +341,17 @@ public class KeycloakUriBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fragment, but not encode it. It assumes that given fragment was already properly encoded
|
||||
*
|
||||
* @param fragment
|
||||
* @return
|
||||
*/
|
||||
public KeycloakUriBuilder encodedFragment(String fragment) {
|
||||
this.fragment = fragment;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only replace path params in path of URI. This changes state of URIBuilder.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,7 @@ public class ObjectUtil {
|
||||
* @param str2
|
||||
* @return true if both strings are null or equal
|
||||
*/
|
||||
public static boolean isEqualOrNull(Object str1, Object str2) {
|
||||
public static boolean isEqualOrBothNull(Object str1, Object str2) {
|
||||
if (str1 == null && str2 == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -24,4 +24,8 @@ public class ObjectUtil {
|
||||
|
||||
return str1.equals(str2);
|
||||
}
|
||||
|
||||
public static String capitalize(String str) {
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
name=${product.name}
|
||||
name-html=${product.name-html}
|
||||
version=${product.version}
|
||||
build-time=${product.build-time}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Provides the InMemoryModel and notifies the factory to save it when
|
||||
* the session is done.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class DefaultFileConnectionProvider implements FileConnectionProvider {
|
||||
|
||||
private final DefaultFileConnectionProviderFactory factory;
|
||||
private final KeycloakSession session;
|
||||
private final InMemoryModel inMemoryModel;
|
||||
|
||||
private boolean isRollbackOnly = false;
|
||||
|
||||
public DefaultFileConnectionProvider(DefaultFileConnectionProviderFactory factory,
|
||||
KeycloakSession session,
|
||||
InMemoryModel inMemoryModel) {
|
||||
this.factory = factory;
|
||||
this.session = session;
|
||||
this.inMemoryModel = inMemoryModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InMemoryModel getModel() {
|
||||
return inMemoryModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClosed(KeycloakSession session) {
|
||||
factory.sessionClosed(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
factory.commit(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
factory.rollback(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
isRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return isRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return factory.isActive(session);
|
||||
}
|
||||
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.jackson.JsonToken;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import org.keycloak.exportimport.util.ExportUtils;
|
||||
import org.keycloak.exportimport.util.ImportUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* This class dispenses a FileConnectionProvider to Keycloak sessions. It
|
||||
* makes sure that only one InMemoryModel is provided for each session and it
|
||||
* handles thread contention for the file where the model is read or saved.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class DefaultFileConnectionProviderFactory implements FileConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultFileConnectionProviderFactory.class);
|
||||
|
||||
private File kcdata;
|
||||
private final Map<KeycloakSession, FileConnectionProvider> allProviders = new HashMap<KeycloakSession, FileConnectionProvider>();
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
String fileName = config.get("fileName");
|
||||
if (fileName == null) {
|
||||
fileName = "keycloak-model.json";
|
||||
}
|
||||
|
||||
String directory = config.get("directory");
|
||||
if (directory == null) {
|
||||
directory = System.getProperty("jboss.server.data.dir");
|
||||
}
|
||||
if (directory == null) {
|
||||
directory = ".";
|
||||
}
|
||||
|
||||
kcdata = new File(directory, fileName);
|
||||
}
|
||||
|
||||
public void sessionClosed(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
allProviders.remove(session);
|
||||
//logger.info("Removed session " + session.hashCode());
|
||||
//logger.info("sessionClosed: Session count=" + allModels.size());
|
||||
}
|
||||
}
|
||||
|
||||
void readModelFile(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
if (!kcdata.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(kcdata);
|
||||
Model model = JsonSerialization.readValue(fis, Model.class);
|
||||
ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING);
|
||||
session.realms().getMigrationModel().setStoredVersion(model.getModelVersion());
|
||||
|
||||
ImportUtils.importRealms(session, model.getRealms(), Strategy.IGNORE_EXISTING);
|
||||
} catch (IOException ioe) {
|
||||
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
|
||||
} finally {
|
||||
//logger.info("Read model file for session=" + session.hashCode());
|
||||
try {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to close output stream.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeModelFile(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
FileOutputStream outStream = null;
|
||||
|
||||
try {
|
||||
outStream = new FileOutputStream(kcdata);
|
||||
exportModel(session, outStream);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e);
|
||||
} finally {
|
||||
//logger.info("Wrote model file for session=" + session.hashCode());
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to close output stream.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException {
|
||||
List<RealmModel> realms = session.realms().getRealms();
|
||||
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
|
||||
for (RealmModel realm : realms) {
|
||||
reps.add(ExportUtils.exportRealm(session, realm, true));
|
||||
}
|
||||
Model model = new Model();
|
||||
model.setRealms(reps);
|
||||
model.setModelVersion(session.realms().getMigrationModel().getStoredVersion());
|
||||
JsonSerialization.prettyMapper.writeValue(outStream, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileConnectionProvider create(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
FileConnectionProvider fcProvider = allProviders.get(session);
|
||||
if (fcProvider == null) {
|
||||
InMemoryModel model = new InMemoryModel();
|
||||
fcProvider = new DefaultFileConnectionProvider(this, session, model);
|
||||
allProviders.put(session, fcProvider);
|
||||
session.getTransaction().enlist(fcProvider);
|
||||
readModelFile(session);
|
||||
//logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size());
|
||||
}
|
||||
|
||||
return fcProvider;
|
||||
}
|
||||
}
|
||||
|
||||
// commitCount is used for debugging. This allows you to easily run a test
|
||||
// to a particular point and then examine the JSON file.
|
||||
//private static int commitCount = 0;
|
||||
void commit(KeycloakSession session) {
|
||||
//commitCount++;
|
||||
synchronized (allProviders) {
|
||||
// in case commit was somehow called twice on the same session
|
||||
if (!allProviders.containsKey(session)) return;
|
||||
|
||||
try {
|
||||
writeModelFile(session);
|
||||
} finally {
|
||||
allProviders.remove(session);
|
||||
//logger.info("Removed session " + session.hashCode());
|
||||
//logger.info("*** commitCount=" + commitCount);
|
||||
//logger.info("commit(): Session count=" + allModels.size());
|
||||
}
|
||||
|
||||
// if (commitCount == 16) {Thread.dumpStack();System.exit(0);}
|
||||
}
|
||||
}
|
||||
|
||||
void rollback(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
allProviders.remove(session);
|
||||
//logger.info("rollback(): Session count=" + allModels.size());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isActive(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
return allProviders.containsKey(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* This class provides an in-memory copy of the entire model for each
|
||||
* Keycloak session. At the start of the session, the model is read
|
||||
* from JSON. When the session's transaction ends, the model is written back
|
||||
* out.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class InMemoryModel {
|
||||
private final Map<String, RealmModel> allRealms = new HashMap<String, RealmModel>();
|
||||
|
||||
// realmId, userId, userModel
|
||||
private final Map<String, Map<String,UserModel>> allUsers = new HashMap<String, Map<String,UserModel>>();
|
||||
|
||||
private String modelVersion;
|
||||
|
||||
public InMemoryModel() {
|
||||
}
|
||||
|
||||
public void putRealm(String id, RealmModel realm) {
|
||||
allRealms.put(id, realm);
|
||||
allUsers.put(id, new HashMap<String, UserModel>());
|
||||
}
|
||||
|
||||
public String getModelVersion() {
|
||||
return modelVersion;
|
||||
}
|
||||
|
||||
public void setModelVersion(String modelVersion) {
|
||||
this.modelVersion = modelVersion;
|
||||
}
|
||||
|
||||
public RealmModel getRealm(String id) {
|
||||
return allRealms.get(id);
|
||||
}
|
||||
|
||||
public Collection<RealmModel> getRealms() {
|
||||
return allRealms.values();
|
||||
}
|
||||
|
||||
public RealmModel getRealmByName(String name) {
|
||||
for (RealmModel realm : getRealms()) {
|
||||
if (realm.getName().equals(name)) return realm;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean removeRealm(String id) {
|
||||
allUsers.remove(id);
|
||||
return (allRealms.remove(id) != null);
|
||||
}
|
||||
|
||||
protected Map<String, UserModel> realmUsers(String realmId) {
|
||||
Map<String, UserModel> realmUsers = allUsers.get(realmId);
|
||||
if (realmUsers == null) throw new NullPointerException("Realm users not found for id=" + realmId);
|
||||
return realmUsers;
|
||||
}
|
||||
|
||||
public void putUser(String realmId, String userId, UserModel user) {
|
||||
realmUsers(realmId).put(userId, user);
|
||||
}
|
||||
|
||||
public UserModel getUser(String realmId, String userId) {
|
||||
return realmUsers(realmId).get(userId);
|
||||
}
|
||||
|
||||
public boolean hasUserWithUsername(String realmId, String username) {
|
||||
for (UserModel user : getUsers(realmId)) {
|
||||
if (user.getUsername().equals(username)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Collection<UserModel> getUsers(String realmId) {
|
||||
return realmUsers(realmId).values();
|
||||
}
|
||||
|
||||
public boolean removeUser(String realmId, String userId) {
|
||||
return (realmUsers(realmId).remove(userId) != null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class Model {
|
||||
private String modelVersion;
|
||||
private List<RealmRepresentation> realms;
|
||||
|
||||
public String getModelVersion() {
|
||||
return modelVersion;
|
||||
}
|
||||
|
||||
public void setModelVersion(String modelVersion) {
|
||||
this.modelVersion = modelVersion;
|
||||
}
|
||||
|
||||
public List<RealmRepresentation> getRealms() {
|
||||
return realms;
|
||||
}
|
||||
|
||||
public void setRealms(List<RealmRepresentation> realms) {
|
||||
this.realms = realms;
|
||||
}
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
|
||||
@@ -1 +0,0 @@
|
||||
org.keycloak.connections.file.FileConnectionSpi
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -22,6 +22,10 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-truststore</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
|
||||
+60
-38
@@ -10,6 +10,7 @@ import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.truststore.TruststoreProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.common.util.EnvUtil;
|
||||
@@ -28,9 +29,12 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||
private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
|
||||
|
||||
private volatile CloseableHttpClient httpClient;
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public HttpClientProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
|
||||
return new HttpClientProvider() {
|
||||
@Override
|
||||
public HttpClient getHttpClient() {
|
||||
@@ -74,7 +78,9 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
httpClient.close();
|
||||
if (httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
@@ -87,46 +93,62 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
long socketTimeout = config.getLong("socket-timeout-millis", -1L);
|
||||
long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
|
||||
int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
|
||||
int connectionPoolSize = config.getInt("connection-pool-size", 200);
|
||||
boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
|
||||
boolean disableCookies = config.getBoolean("disable-cookies", true);
|
||||
String hostnameVerificationPolicy = config.get("hostname-verification-policy", "WILDCARD");
|
||||
HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = HttpClientBuilder.HostnameVerificationPolicy.valueOf(hostnameVerificationPolicy);
|
||||
String truststore = config.get("truststore");
|
||||
String truststorePassword = config.get("truststore-password");
|
||||
String clientKeystore = config.get("client-keystore");
|
||||
String clientKeystorePassword = config.get("client-keystore-password");
|
||||
String clientPrivateKeyPassword = config.get("client-key-password");
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
HttpClientBuilder builder = new HttpClientBuilder();
|
||||
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
||||
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
|
||||
.maxPooledPerRoute(maxPooledPerRoute)
|
||||
.connectionPoolSize(connectionPoolSize)
|
||||
.hostnameVerification(hostnamePolicy)
|
||||
.disableCookies(disableCookies);
|
||||
if (disableTrustManager) builder.disableTrustManager();
|
||||
if (truststore != null) {
|
||||
truststore = EnvUtil.replace(truststore);
|
||||
try {
|
||||
builder.trustStore(KeystoreUtil.loadKeyStore(truststore, truststorePassword));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load truststore", e);
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (httpClient == null) {
|
||||
synchronized(this) {
|
||||
if (httpClient == null) {
|
||||
long socketTimeout = config.getLong("socket-timeout-millis", -1L);
|
||||
long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
|
||||
int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
|
||||
int connectionPoolSize = config.getInt("connection-pool-size", 200);
|
||||
boolean disableCookies = config.getBoolean("disable-cookies", true);
|
||||
String clientKeystore = config.get("client-keystore");
|
||||
String clientKeystorePassword = config.get("client-keystore-password");
|
||||
String clientPrivateKeyPassword = config.get("client-key-password");
|
||||
|
||||
TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
|
||||
boolean disableTrustManager = truststoreProvider == null || truststoreProvider.getTruststore() == null;
|
||||
if (disableTrustManager) {
|
||||
logger.warn("Truststore is disabled");
|
||||
}
|
||||
HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = disableTrustManager ? null
|
||||
: HttpClientBuilder.HostnameVerificationPolicy.valueOf(truststoreProvider.getPolicy().name());
|
||||
|
||||
HttpClientBuilder builder = new HttpClientBuilder();
|
||||
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
||||
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
|
||||
.maxPooledPerRoute(maxPooledPerRoute)
|
||||
.connectionPoolSize(connectionPoolSize)
|
||||
.disableCookies(disableCookies);
|
||||
|
||||
if (disableTrustManager) {
|
||||
// TODO: is it ok to do away with disabling trust manager?
|
||||
//builder.disableTrustManager();
|
||||
} else {
|
||||
builder.hostnameVerification(hostnamePolicy);
|
||||
try {
|
||||
builder.trustStore(truststoreProvider.getTruststore());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load truststore", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (clientKeystore != null) {
|
||||
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||
try {
|
||||
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
||||
builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load keystore", e);
|
||||
}
|
||||
}
|
||||
httpClient = builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clientKeystore != null) {
|
||||
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||
try {
|
||||
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
||||
builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load keystore", e);
|
||||
}
|
||||
}
|
||||
httpClient = builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ public class HttpClientBuilder {
|
||||
* Disable cookie management.
|
||||
*/
|
||||
public HttpClientBuilder disableCookies(boolean disable) {
|
||||
this.disableTrustManager = disable;
|
||||
this.disableCookies = disable;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-13
@@ -66,19 +66,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +126,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||
|
||||
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="bburke@redhat.com" id="1.7.0">
|
||||
<createTable tableName="KEYCLOAK_GROUP">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="PARENT_GROUP" type="VARCHAR(36)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="GROUP_ROLE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="GROUP_ATTRIBUTE">
|
||||
<column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_GROUP_MEMBERSHIP">
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_DEFAULT_GROUPS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addColumn tableName="IDENTITY_PROVIDER">
|
||||
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<addColumn tableName="REALM">
|
||||
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
|
||||
</addColumn>
|
||||
|
||||
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD" />
|
||||
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP_ATTRIBUTE_PK" tableName="GROUP_ATTRIBUTE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ATTRIBUTE" constraintName="FK_GROUP_ATTRIBUTE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
||||
|
||||
<addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTRAINT_USER_GROUP" tableName="USER_GROUP_MEMBERSHIP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_GROUP_MEMBERSHIP" constraintName="FK_USER_GROUP_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
|
||||
<addPrimaryKey columnNames="ROLE_ID, GROUP_ID" constraintName="CONSTRAINT_GROUP_ROLE" tableName="GROUP_ROLE_MAPPING"/>
|
||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
|
||||
<addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
||||
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
|
||||
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<update tableName="CLIENT">
|
||||
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
|
||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" valueBoolean="true"/>
|
||||
<where>DIRECT_GRANTS_ONLY = :value</where>
|
||||
<whereParams>
|
||||
<param valueBoolean="true" />
|
||||
</whereParams>
|
||||
</update>
|
||||
|
||||
<dropDefaultValue tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY" />
|
||||
<dropColumn tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY"/>
|
||||
|
||||
<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
|
||||
|
||||
<!-- Sybase specific hacks -->
|
||||
<modifySql dbms="sybase">
|
||||
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
|
||||
</modifySql>
|
||||
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||
<changeSet author="mposolda@redhat.com" id="1.8.0">
|
||||
|
||||
<addColumn tableName="IDENTITY_PROVIDER">
|
||||
<column name="POST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<createTable tableName="CLIENT_TEMPLATE">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
||||
<column name="PROTOCOL" type="VARCHAR(255)"/>
|
||||
<column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_TEMPLATE_ATTRIBUTES">
|
||||
<column name="TEMPLATE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(2048)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="TEMPLATE_SCOPE_MAPPING">
|
||||
<column name="TEMPLATE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
|
||||
|
||||
<dropNotNullConstraint tableName="PROTOCOL_MAPPER" columnName="CLIENT_ID" columnDataType="VARCHAR(36)"/>
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
<column name="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<addColumn tableName="PROTOCOL_MAPPER">
|
||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<createTable tableName="REALM_CLIENT_TEMPLATE">
|
||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="PK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_TEMPLATE" constraintName="FK_REALM_CLI_TMPLT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_CLI_TMPLT_MAPPER" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="CLIENT" constraintName="FK_CLI_TMPLT_CLIENT" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
||||
<addPrimaryKey columnNames="TEMPLATE_ID, ROLE_ID" constraintName="PK_TEMPLATE_SCOPE" tableName="TEMPLATE_SCOPE_MAPPING"/>
|
||||
<addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addPrimaryKey columnNames="TEMPLATE_ID, NAME" constraintName="PK_CL_TMPL_ATTR" tableName="CLIENT_TEMPLATE_ATTRIBUTES"/>
|
||||
<addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="CLIENT_TEMPLATE_ATTRIBUTES" constraintName="FK_CL_TEMPL_ATTR_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
||||
|
||||
<update tableName="CREDENTIAL">
|
||||
<column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
|
||||
<where>TYPE in ('password-history', 'password') AND ALGORITHM is NULL</where>
|
||||
</update>
|
||||
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -10,4 +10,6 @@
|
||||
<include file="META-INF/jpa-changelog-1.4.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.5.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.6.1.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.7.0.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.8.0.xml"/>
|
||||
</databaseChangeLog>
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+16
-14
@@ -1,18 +1,5 @@
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.DriverManager;
|
||||
@@ -22,10 +9,25 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
|
||||
public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
|
||||
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
|
||||
|
||||
public String FIRST_VERSION = "1.0.0.Final";
|
||||
|
||||
public String LAST_VERSION = "1.6.1";
|
||||
public String LAST_VERSION = "1.8.0";
|
||||
|
||||
public String getCurrentVersionSql(String defaultSchema);
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class>
|
||||
|
||||
<!-- JpaAuditProviders -->
|
||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+3
-1
@@ -28,7 +28,9 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||
Update1_2_0_Beta1.class,
|
||||
Update1_2_0_CR1.class,
|
||||
Update1_3_0.class,
|
||||
Update1_4_0.class
|
||||
Update1_4_0.class,
|
||||
Update1_7_0.class,
|
||||
Update1_8_0.class
|
||||
};
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public abstract class Update {
|
||||
o.append(f, 1);
|
||||
}
|
||||
|
||||
col.ensureIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
||||
col.createIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
||||
log.debugv("Created index {0}, fields={1}, unique={2}, sparse={3}", name, Arrays.toString(fields), unique, sparse);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ public class Update1_0_0_Final extends Update {
|
||||
@Override
|
||||
public void update(KeycloakSession session) throws ClassNotFoundException {
|
||||
DBCollection realmsCollection = db.getCollection("realms");
|
||||
realmsCollection.ensureIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
||||
realmsCollection.createIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
||||
|
||||
DefaultMongoUpdaterProvider.log.debugv("Created collection {0}", "realms");
|
||||
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.keycloak.connections.mongo.updater.impl.updates;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class Update1_7_0 extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.7.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session) throws ClassNotFoundException {
|
||||
DBCollection clients = db.getCollection("clients");
|
||||
DBCursor clientsCursor = clients.find();
|
||||
|
||||
try {
|
||||
while (clientsCursor.hasNext()) {
|
||||
BasicDBObject client = (BasicDBObject) clientsCursor.next();
|
||||
|
||||
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
|
||||
client.append("standardFlowEnabled", !directGrantsOnly);
|
||||
client.append("implicitFlowEnabled", false);
|
||||
client.append("directAccessGrantsEnabled", directGrantsOnly);
|
||||
client.removeField("directGrantsOnly");
|
||||
|
||||
clients.save(client);
|
||||
}
|
||||
} finally {
|
||||
clientsCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package org.keycloak.connections.mongo.updater.impl.updates;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.WriteResult;
|
||||
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class Update1_8_0 extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.8.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session) {
|
||||
BasicDBList orArgs = new BasicDBList();
|
||||
orArgs.add(new BasicDBObject("type", UserCredentialModel.PASSWORD));
|
||||
orArgs.add(new BasicDBObject("type", UserCredentialModel.PASSWORD_HISTORY));
|
||||
|
||||
BasicDBObject elemMatch = new BasicDBObject("$or", orArgs);
|
||||
elemMatch.put("algorithm", new BasicDBObject("$exists", false));
|
||||
|
||||
BasicDBObject query = new BasicDBObject("credentials", new BasicDBObject("$elemMatch", elemMatch));
|
||||
|
||||
BasicDBObject update = new BasicDBObject("$set", new BasicDBObject("credentials.$.algorithm", Pbkdf2PasswordHashProvider.ID));
|
||||
|
||||
DBCollection users = db.getCollection("users");
|
||||
|
||||
// Not sure how to do in single query
|
||||
int countModified = 1;
|
||||
while (countModified > 0) {
|
||||
WriteResult wr = users.update(query, update, false, true);
|
||||
countModified = wr.getN();
|
||||
log.debugf("%d credentials modified in current iteration during upgrade to 1.8", countModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+17
-18
@@ -1,11 +1,12 @@
|
||||
package org.keycloak.connections.mongo;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
@@ -15,25 +16,28 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
|
||||
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {
|
||||
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
||||
|
||||
// TODO Make it dynamic
|
||||
private String[] entities = new String[]{
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoClientTemplateEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
||||
"org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity",
|
||||
@@ -170,7 +174,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
operationalInfo.put("mongoHosts", hosts);
|
||||
operationalInfo.put("mongoDatabaseName", dbName);
|
||||
operationalInfo.put("mongoUser", uri.getUsername());
|
||||
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||
|
||||
logger.debugv("Initialized mongo model. host(s): %s, db: %s", uri.getHosts(), dbName);
|
||||
return client;
|
||||
@@ -194,7 +197,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
||||
operationalInfo.put("mongoDatabaseName", dbName);
|
||||
operationalInfo.put("mongoUser", user);
|
||||
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||
|
||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||
return client;
|
||||
@@ -210,9 +212,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
||||
checkIntOption("socketTimeout", builder);
|
||||
checkBooleanOption("socketKeepAlive", builder);
|
||||
checkBooleanOption("autoConnectRetry", builder);
|
||||
if (config.getLong("maxAutoConnectRetryTime") != null) {
|
||||
builder.maxAutoConnectRetryTime(config.getLong("maxAutoConnectRetryTime"));
|
||||
}
|
||||
if(config.getBoolean("ssl", false)) {
|
||||
builder.socketFactory(SSLSocketFactory.getDefault());
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package org.keycloak.connections.mongo;
|
||||
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
|
||||
public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
|
||||
}
|
||||
+2
-9
@@ -1,13 +1,6 @@
|
||||
package org.keycloak.connections.mongo.impl;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.WriteResult;
|
||||
import com.mongodb.*;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoEntity;
|
||||
@@ -133,7 +126,7 @@ public class MongoStoreImpl implements MongoStore {
|
||||
}
|
||||
|
||||
public static ModelException convertException(MongoException e) {
|
||||
if (e instanceof MongoException.DuplicateKey) {
|
||||
if (e instanceof DuplicateKeyException) {
|
||||
return new ModelDuplicateException(e);
|
||||
} else {
|
||||
return new ModelException(e);
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
</parent>
|
||||
<name>Connections Parent</name>
|
||||
<description/>
|
||||
@@ -16,10 +16,10 @@
|
||||
<module>jpa</module>
|
||||
<module>jpa-liquibase</module>
|
||||
<module>infinispan</module>
|
||||
<module>mongo</module>
|
||||
<module>file</module>
|
||||
<module>mongo-update</module>
|
||||
<!--<module>mongo</module>-->
|
||||
<!--<module>mongo-update</module>-->
|
||||
<module>http-client</module>
|
||||
<module>truststore</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,23 +4,19 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-file</artifactId>
|
||||
<name>Keycloak Connections File</name>
|
||||
<artifactId>keycloak-connections-truststore</artifactId>
|
||||
<name>Keycloak Truststore</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-export-import-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-export-import-single-file</artifactId>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
@@ -30,14 +26,10 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class FileTruststoreProvider implements TruststoreProvider {
|
||||
|
||||
private final HostnameVerificationPolicy policy;
|
||||
private final KeyStore truststore;
|
||||
|
||||
FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy) {
|
||||
this.policy = policy;
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostnameVerificationPolicy getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getTruststore() {
|
||||
return truststore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class FileTruststoreProviderFactory implements TruststoreProviderFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(FileTruststoreProviderFactory.class);
|
||||
|
||||
private TruststoreProvider provider;
|
||||
|
||||
@Override
|
||||
public TruststoreProvider create(KeycloakSession session) {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
String storepath = config.get("file");
|
||||
String pass = config.get("password");
|
||||
String policy = config.get("hostname-verification-policy");
|
||||
Boolean disabled = config.getBoolean("disabled", null);
|
||||
|
||||
// if "truststore" . "file" is not configured then it is disabled
|
||||
if (storepath == null && pass == null && policy == null && disabled == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if explicitly disabled
|
||||
if (disabled != null && disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
HostnameVerificationPolicy verificationPolicy = null;
|
||||
KeyStore truststore = null;
|
||||
|
||||
if (storepath == null) {
|
||||
throw new RuntimeException("Attribute 'file' missing in 'truststore':'file' configuration");
|
||||
}
|
||||
if (pass == null) {
|
||||
throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration");
|
||||
}
|
||||
|
||||
try {
|
||||
truststore = loadStore(storepath, pass == null ? null :pass.toCharArray());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath(), e);
|
||||
}
|
||||
if (policy == null) {
|
||||
verificationPolicy = HostnameVerificationPolicy.WILDCARD;
|
||||
} else {
|
||||
try {
|
||||
verificationPolicy = HostnameVerificationPolicy.valueOf(policy);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Invalid value for 'hostname-verification-policy': " + policy + " (must be one of: ANY, WILDCARD, STRICT)");
|
||||
}
|
||||
}
|
||||
|
||||
provider = new FileTruststoreProvider(truststore, verificationPolicy);
|
||||
TruststoreProviderSingleton.set(provider);
|
||||
log.debug("File trustore provider initialized: " + new File(storepath).getAbsolutePath());
|
||||
}
|
||||
|
||||
private KeyStore loadStore(String path, char[] password) throws Exception {
|
||||
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
InputStream is = new FileInputStream(path);
|
||||
try {
|
||||
ks.load(is, password);
|
||||
return ks;
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "file";
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
public enum HostnameVerificationPolicy {
|
||||
|
||||
/**
|
||||
* Hostname verification is not done on the server's certificate
|
||||
*/
|
||||
ANY,
|
||||
|
||||
/**
|
||||
* Allows wildcards in subdomain names i.e. *.foo.com
|
||||
*/
|
||||
WILDCARD,
|
||||
|
||||
/**
|
||||
* CN must match hostname connecting to
|
||||
*/
|
||||
STRICT
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class JSSETruststoreConfigurator {
|
||||
|
||||
private TruststoreProvider provider;
|
||||
private volatile javax.net.ssl.SSLSocketFactory sslFactory;
|
||||
private volatile TrustManager[] tm;
|
||||
|
||||
public JSSETruststoreConfigurator(KeycloakSession session) {
|
||||
KeycloakSessionFactory factory = session.getKeycloakSessionFactory();
|
||||
TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file");
|
||||
|
||||
provider = truststoreFactory.create(session);
|
||||
if (provider != null && provider.getTruststore() == null) {
|
||||
provider = null;
|
||||
}
|
||||
}
|
||||
|
||||
public JSSETruststoreConfigurator(TruststoreProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public javax.net.ssl.SSLSocketFactory getSSLSocketFactory() {
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sslFactory == null) {
|
||||
synchronized(this) {
|
||||
if (sslFactory == null) {
|
||||
try {
|
||||
SSLContext sslctx = SSLContext.getInstance("TLS");
|
||||
sslctx.init(null, getTrustManagers(), null);
|
||||
sslFactory = sslctx.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize SSLContext: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sslFactory;
|
||||
}
|
||||
|
||||
public TrustManager[] getTrustManagers() {
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tm == null) {
|
||||
synchronized (this) {
|
||||
if (tm == null) {
|
||||
TrustManagerFactory tmf = null;
|
||||
try {
|
||||
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(provider.getTruststore());
|
||||
tm = tmf.getTrustManagers();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize TrustManager: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tm;
|
||||
}
|
||||
|
||||
public HostnameVerifier getHostnameVerifier() {
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HostnameVerificationPolicy policy = provider.getPolicy();
|
||||
switch (policy) {
|
||||
case ANY:
|
||||
return new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
case WILDCARD:
|
||||
return new BrowserCompatHostnameVerifier();
|
||||
case STRICT:
|
||||
return new StrictHostnameVerifier();
|
||||
default:
|
||||
throw new IllegalStateException("Unknown policy: " + policy.name());
|
||||
}
|
||||
}
|
||||
|
||||
public TruststoreProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
|
||||
/**
|
||||
* Using this class is ugly, but it is the only way to push our truststore to the default LDAP client implementation.
|
||||
* <p>
|
||||
* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
|
||||
* initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
|
||||
* in keycloak-server.json.
|
||||
* <p>
|
||||
* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault().
|
||||
*
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
|
||||
public class SSLSocketFactory extends javax.net.ssl.SSLSocketFactory {
|
||||
|
||||
private static final Logger log = Logger.getLogger(SSLSocketFactory.class);
|
||||
|
||||
private static SSLSocketFactory instance;
|
||||
|
||||
private final javax.net.ssl.SSLSocketFactory sslsf;
|
||||
|
||||
private SSLSocketFactory() {
|
||||
|
||||
TruststoreProvider provider = TruststoreProviderSingleton.get();
|
||||
javax.net.ssl.SSLSocketFactory sf = null;
|
||||
if (provider != null) {
|
||||
sf = new JSSETruststoreConfigurator(provider).getSSLSocketFactory();
|
||||
}
|
||||
|
||||
if (sf == null) {
|
||||
log.info("No truststore provider found - using default SSLSocketFactory");
|
||||
sf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
||||
}
|
||||
|
||||
sslsf = sf;
|
||||
}
|
||||
|
||||
public static synchronized SSLSocketFactory getDefault() {
|
||||
if (instance == null) {
|
||||
instance = new SSLSocketFactory();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return sslsf.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return sslsf.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
||||
return sslsf.createSocket(socket, host, port, autoClose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return sslsf.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return sslsf.createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return sslsf.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return sslsf.createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public interface TruststoreProvider extends Provider {
|
||||
|
||||
HostnameVerificationPolicy getPolicy();
|
||||
|
||||
KeyStore getTruststore();
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public interface TruststoreProviderFactory extends ProviderFactory<TruststoreProvider> {
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
class TruststoreProviderSingleton {
|
||||
|
||||
static private TruststoreProvider provider;
|
||||
|
||||
static void set(TruststoreProvider tp) {
|
||||
provider = tp;
|
||||
}
|
||||
|
||||
static TruststoreProvider get() {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -1,13 +1,13 @@
|
||||
package org.keycloak.connections.file;
|
||||
package org.keycloak.connections.truststore;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
public class FileConnectionSpi implements Spi {
|
||||
public class TruststoreSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
@@ -16,17 +16,16 @@ public class FileConnectionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsFile";
|
||||
return "truststore";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return FileConnectionProvider.class;
|
||||
return TruststoreProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return FileConnectionProviderFactory.class;
|
||||
return TruststoreProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.truststore.FileTruststoreProviderFactory
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.truststore.TruststoreSpi
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.6.1.Final</version>
|
||||
<version>1.8.0.CR1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -27,6 +27,8 @@ public interface OAuth2Constants {
|
||||
|
||||
String AUTHORIZATION_CODE = "authorization_code";
|
||||
|
||||
String IMPLICIT = "implicit";
|
||||
|
||||
String PASSWORD = "password";
|
||||
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
|
||||
Loaded 100 of 2751 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user