Compare commits
843
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ad464c681 | ||
|
|
88a37067ad | ||
|
|
c6dab26769 | ||
|
|
9bceec9ef5 | ||
|
|
d1e491d57d | ||
|
|
7cfa1cc368 | ||
|
|
b79ee3c7f3 | ||
|
|
6085b21c36 | ||
|
|
b24d2e064a | ||
|
|
4fcb22c540 | ||
|
|
07689c9537 | ||
|
|
e329c7801d | ||
|
|
2de2df3a41 | ||
|
|
194a63cc71 | ||
|
|
6392405413 | ||
|
|
a18a4477e0 | ||
|
|
94ffeda62a | ||
|
|
264e81efb2 | ||
|
|
cea52a6db9 | ||
|
|
b8c7fcf587 | ||
|
|
04228791f3 | ||
|
|
04c4110f76 | ||
|
|
13e92cdb35 | ||
|
|
bd271b2367 | ||
|
|
9fea9f6fe0 | ||
|
|
8717cd0090 | ||
|
|
e487db349c | ||
|
|
29c0fe564c | ||
|
|
b2d1a1a17f | ||
|
|
4868918f94 | ||
|
|
15d0a116ac | ||
|
|
b5212d58ec | ||
|
|
cf1c577840 | ||
|
|
57127f39d0 | ||
|
|
052534de82 | ||
|
|
99e1eac094 | ||
|
|
7410bdb31c | ||
|
|
cc9c8d8ca1 | ||
|
|
f55d18189c | ||
|
|
6097a0c9e1 | ||
|
|
39f8311484 | ||
|
|
fe8e437e74 | ||
|
|
1373708dea | ||
|
|
34605ee3c6 | ||
|
|
3444fb62f1 | ||
|
|
cc788cf44e | ||
|
|
13e4f607ad | ||
|
|
5da491c270 | ||
|
|
99fcc51019 | ||
|
|
350b9550c3 | ||
|
|
f289b281a0 | ||
|
|
9cd860d698 | ||
|
|
536b88790e | ||
|
|
73d3e8afd9 | ||
|
|
aef6bb9789 | ||
|
|
b4f56e92f3 | ||
|
|
8daa2c3703 | ||
|
|
c7f2a0ffdd | ||
|
|
c19360c6f2 | ||
|
|
71da6ab4e9 | ||
|
|
9f33685785 | ||
|
|
df4f1e7129 | ||
|
|
212182ee34 | ||
|
|
86988833e9 | ||
|
|
5a0504b5d9 | ||
|
|
e364680792 | ||
|
|
ef46070410 | ||
|
|
843b4b470b | ||
|
|
dcf6da2a51 | ||
|
|
3d29816ea3 | ||
|
|
2eeb2f52c5 | ||
|
|
32827be8b1 | ||
|
|
23c1f513e7 | ||
|
|
ae40bfb96e | ||
|
|
547f5ecfa6 | ||
|
|
1913f801b9 | ||
|
|
5842f7c837 | ||
|
|
1ef2eb6110 | ||
|
|
178625d3f2 | ||
|
|
9fb46a7b1c | ||
|
|
bea8de8a1a | ||
|
|
d68f6bbc42 | ||
|
|
7de999a7f9 | ||
|
|
ffb688b393 | ||
|
|
6aee6b0c46 | ||
|
|
aa78c9eaf5 | ||
|
|
02eda8943c | ||
|
|
2f7143e72f | ||
|
|
9ad14d991c | ||
|
|
59a6c9f13f | ||
|
|
ae838c12db | ||
|
|
ac9268bd48 | ||
|
|
0b58bebc90 | ||
|
|
93157e49d5 | ||
|
|
3d9f11168e | ||
|
|
c4cce147e2 | ||
|
|
3a2927e12e | ||
|
|
901ff9c3d5 | ||
|
|
8de3b26126 | ||
|
|
1e51ade620 | ||
|
|
89e6f93fa4 | ||
|
|
41630d6962 | ||
|
|
efcd0346fc | ||
|
|
f13deab812 | ||
|
|
f28b3c1269 | ||
|
|
5e1ec459e5 | ||
|
|
9a6f202c46 | ||
|
|
e11957ecf3 | ||
|
|
23c8809598 | ||
|
|
7098daaf72 | ||
|
|
811d532b48 | ||
|
|
7ceef826ac | ||
|
|
fe294dcb58 | ||
|
|
8441bda3da | ||
|
|
227900f288 | ||
|
|
345e0da76d | ||
|
|
139e12fa5f | ||
|
|
4df70c517d | ||
|
|
cbd6f7e1d0 | ||
|
|
426e55664f | ||
|
|
ab0a669662 | ||
|
|
5c03b20bd1 | ||
|
|
7eeebff874 | ||
|
|
a0a7372160 | ||
|
|
9e697d033f | ||
|
|
2ec3eaf68a | ||
|
|
e047872c25 | ||
|
|
f1173f8022 | ||
|
|
420286c103 | ||
|
|
1495f4881e | ||
|
|
452611242c | ||
|
|
d075172fd2 | ||
|
|
c32620b718 | ||
|
|
37b8d1ad10 | ||
|
|
be4f2aff63 | ||
|
|
14669dfbc5 | ||
|
|
5cec0d4bef | ||
|
|
dd74b57d13 | ||
|
|
a09bc6520f | ||
|
|
8a02ef1859 | ||
|
|
9ccd05ff54 | ||
|
|
42a8a0eb15 | ||
|
|
fb374ad99d | ||
|
|
a4f46d3310 | ||
|
|
5bc134ea7b | ||
|
|
28c289cfb8 | ||
|
|
0b5b27ea3a | ||
|
|
f1dbe3c9e2 | ||
|
|
9fb3201c8b | ||
|
|
96f8f99313 | ||
|
|
86c49f5e89 | ||
|
|
017dbb1d9f | ||
|
|
60eda52aba | ||
|
|
377fbced4a | ||
|
|
fb6a8da863 | ||
|
|
0cb5ba0f6e | ||
|
|
eb1b011989 | ||
|
|
72691b2e74 | ||
|
|
f9eeecf836 | ||
|
|
61e7936d79 | ||
|
|
4934fe5a8e | ||
|
|
2035398ef4 | ||
|
|
56160ef12b | ||
|
|
b0644ce18c | ||
|
|
cf5c3aa53b | ||
|
|
f9e87516ae | ||
|
|
ed1a30f67b | ||
|
|
77293b85e0 | ||
|
|
ad9210a7a7 | ||
|
|
15af61a7cc | ||
|
|
2d1417d72b | ||
|
|
41404424ac | ||
|
|
a2a531a747 | ||
|
|
a76c52ef84 | ||
|
|
ed75ef900a | ||
|
|
862502f3ed | ||
|
|
2b57b8371b | ||
|
|
4044b39ab7 | ||
|
|
e0bc56960b | ||
|
|
180352aa09 | ||
|
|
09a61f4706 | ||
|
|
5150251141 | ||
|
|
732d56b0c0 | ||
|
|
08aeb13804 | ||
|
|
1f0469894a | ||
|
|
8a0859fcba | ||
|
|
5212f57b5b | ||
|
|
6ac1301dc2 | ||
|
|
1eb0cde74f | ||
|
|
32f8fd4b9f | ||
|
|
90a5272860 | ||
|
|
40a8f0a243 | ||
|
|
f2ee9df600 | ||
|
|
9ab44b79ce | ||
|
|
50c11e105b | ||
|
|
b7c98ed433 | ||
|
|
d217b23719 | ||
|
|
171c6ab88f | ||
|
|
45411b1199 | ||
|
|
e0bfb13ed9 | ||
|
|
902332c5ae | ||
|
|
08d7211a93 | ||
|
|
1c0e204f50 | ||
|
|
d6e620a266 | ||
|
|
54b3d3abec | ||
|
|
31ed69a970 | ||
|
|
b1d9fd670a | ||
|
|
bf58511d81 | ||
|
|
b5317f3485 | ||
|
|
e805ffd945 | ||
|
|
40b5731198 | ||
|
|
ebd3d522e2 | ||
|
|
04179c5681 | ||
|
|
6c8ebdb456 | ||
|
|
85446c3b46 | ||
|
|
df7a68b709 | ||
|
|
1c65fd86ef | ||
|
|
bb00524f94 | ||
|
|
7f9e6bbf60 | ||
|
|
2ba21485f9 | ||
|
|
c998198aac | ||
|
|
3e8d4e4eb6 | ||
|
|
b15918b745 | ||
|
|
d2b7f18480 | ||
|
|
663b99d884 | ||
|
|
e3e18913f1 | ||
|
|
f98c454775 | ||
|
|
9c2944c090 | ||
|
|
66eb9095c1 | ||
|
|
e7c73518b3 | ||
|
|
172007f59c | ||
|
|
e262f8fb63 | ||
|
|
b6b3c04400 | ||
|
|
f97a3e0745 | ||
|
|
f9bccc83f9 | ||
|
|
761714036e | ||
|
|
d365d9d784 | ||
|
|
96f2985e86 | ||
|
|
7bad8e6ff3 | ||
|
|
55d53214a1 | ||
|
|
aee8398ee8 | ||
|
|
7cd63f2f8b | ||
|
|
8b5aafc4b4 | ||
|
|
f6323d94ec | ||
|
|
eb7ad07e31 | ||
|
|
7c7f87d201 | ||
|
|
18b94a2153 | ||
|
|
0b3e867362 | ||
|
|
3498273601 | ||
|
|
01a8c13ac0 | ||
|
|
194ba6009f | ||
|
|
c9c8acd029 | ||
|
|
7e2c06e301 | ||
|
|
40591cff25 | ||
|
|
5cf5168770 | ||
|
|
cb57dbf58a | ||
|
|
4b6df5d489 | ||
|
|
b6d29ccd30 | ||
|
|
16fb1e2078 | ||
|
|
ac00f7fee2 | ||
|
|
faeff029fa | ||
|
|
c6363aa146 | ||
|
|
2c70e53df6 | ||
|
|
81ab6aeeed | ||
|
|
d1a528eb07 | ||
|
|
c9c9f05e29 | ||
|
|
4e3e9ad5d4 | ||
|
|
3bd3d0285d | ||
|
|
343e678f09 | ||
|
|
b8adfcad87 | ||
|
|
ca3691e650 | ||
|
|
ad11d6e76b | ||
|
|
3e3216fb23 | ||
|
|
93cec9b3ee | ||
|
|
20c5c6b60c | ||
|
|
f29bb7d501 | ||
|
|
c3d9859c6e | ||
|
|
3b3b219a86 | ||
|
|
3f2e9c3592 | ||
|
|
787a3f8fcc | ||
|
|
a4cbf130b4 | ||
|
|
1c0e23db66 | ||
|
|
f33a1e2143 | ||
|
|
63ca9b2fe8 | ||
|
|
34f82fa1b1 | ||
|
|
5453bec1bf | ||
|
|
e65dd4fbc3 | ||
|
|
7b115b4b44 | ||
|
|
4d64f0e160 | ||
|
|
7453e96f5c | ||
|
|
9be9d3f580 | ||
|
|
7d51df4eed | ||
|
|
74fd904bc4 | ||
|
|
3c2a12d019 | ||
|
|
56f9aa41d0 | ||
|
|
e5d744f7d5 | ||
|
|
394676222f | ||
|
|
5464655bc2 | ||
|
|
768c3eb9a7 | ||
|
|
e316037910 | ||
|
|
97a08a1d99 | ||
|
|
db4420e10d | ||
|
|
480d4e6f4f | ||
|
|
0b701b3cf9 | ||
|
|
40216b5e7d | ||
|
|
b1fe07ac3d | ||
|
|
642de06fb5 | ||
|
|
7ad028fcb1 | ||
|
|
76385157fc | ||
|
|
c11f65720b | ||
|
|
5006fe2292 | ||
|
|
90fdb4889c | ||
|
|
c17a57d76c | ||
|
|
efcb749771 | ||
|
|
8c99a13387 | ||
|
|
79c1650c15 | ||
|
|
63ddabe5fb | ||
|
|
5448403345 | ||
|
|
5996149a8d | ||
|
|
62029e8a33 | ||
|
|
588e1711dd | ||
|
|
35c4a379ad | ||
|
|
cf62715499 | ||
|
|
1bfdff0e25 | ||
|
|
ba14b9d942 | ||
|
|
10fc7302eb | ||
|
|
3bea8f4adb | ||
|
|
24a36e6848 | ||
|
|
5d070cbf22 | ||
|
|
5e660977aa | ||
|
|
1f0600044a | ||
|
|
d3e3990d77 | ||
|
|
4a80f1e913 | ||
|
|
0550bdb467 | ||
|
|
5f07fa8057 | ||
|
|
10064fdac9 | ||
|
|
e7f6c780e2 | ||
|
|
e03b72df00 | ||
|
|
7d8b392fd9 | ||
|
|
afa8dc735d | ||
|
|
d49a74b927 | ||
|
|
ae36b3c832 | ||
|
|
e0c62e25b2 | ||
|
|
081958e282 | ||
|
|
379d5e3228 | ||
|
|
9b18601102 | ||
|
|
7868152ffe | ||
|
|
223cc1fb50 | ||
|
|
6ae9d19843 | ||
|
|
cbe4ff324c | ||
|
|
b8c06dd560 | ||
|
|
31d2cde6e5 | ||
|
|
75e2b404c8 | ||
|
|
d163dad3fe | ||
|
|
7271fdaaaa | ||
|
|
03fbb3c853 | ||
|
|
68c8bfa0e1 | ||
|
|
f34b47d29c | ||
|
|
a0b54ebedc | ||
|
|
ddb201db6c | ||
|
|
4069be3ff6 | ||
|
|
15f23eb045 | ||
|
|
77d17de14d | ||
|
|
bab08bf8f0 | ||
|
|
0d110ceb30 | ||
|
|
9cd3034762 | ||
|
|
ba51640c61 | ||
|
|
287593d75f | ||
|
|
3826e933c1 | ||
|
|
6b1020ec34 | ||
|
|
c8b22e71f0 | ||
|
|
41a5598103 | ||
|
|
fc92639d3f | ||
|
|
6587cd2478 | ||
|
|
e55c2ae554 | ||
|
|
693d6c0e5d | ||
|
|
952c1decf0 | ||
|
|
f3dbacabf8 | ||
|
|
819105c3aa | ||
|
|
06affc0700 | ||
|
|
f03d79c7d3 | ||
|
|
b52936bc5b | ||
|
|
3c4114091f | ||
|
|
d354aa1f62 | ||
|
|
45fd114143 | ||
|
|
32da5fea0a | ||
|
|
32c23c2410 | ||
|
|
802085f158 | ||
|
|
773c88cc04 | ||
|
|
197f51e50f | ||
|
|
1c66ce7dd7 | ||
|
|
8610a02d72 | ||
|
|
d7df86d6d0 | ||
|
|
e3d0f8f6e5 | ||
|
|
e8f2527fcd | ||
|
|
305dcb2b1e | ||
|
|
0ab352706b | ||
|
|
eac0bb4b37 | ||
|
|
3bc721720f | ||
|
|
54758b800b | ||
|
|
6cd6edce64 | ||
|
|
69a1d4d192 | ||
|
|
5d4864b82d | ||
|
|
3840aed5f7 | ||
|
|
6a629aec0c | ||
|
|
8b4bb72fb3 | ||
|
|
88d08c4f38 | ||
|
|
e3f5df3894 | ||
|
|
cfbd7d469f | ||
|
|
8fd7091068 | ||
|
|
9c97245c46 | ||
|
|
257aba4331 | ||
|
|
672e1b3575 | ||
|
|
ce50b0ed29 | ||
|
|
e88af874ca | ||
|
|
aed54439c2 | ||
|
|
c8c6e36fd9 | ||
|
|
e6ace84101 | ||
|
|
815b5e5f34 | ||
|
|
c149358028 | ||
|
|
b1265f6d70 | ||
|
|
a09e78873e | ||
|
|
17d8394ab6 | ||
|
|
e6a20aa44b | ||
|
|
458ca8a7ee | ||
|
|
b56e23eded | ||
|
|
d02c9c2150 | ||
|
|
cccb532a21 | ||
|
|
5c2bf7c822 | ||
|
|
e8758a77fb | ||
|
|
b2f0acfe26 | ||
|
|
8842d88058 | ||
|
|
739178d840 | ||
|
|
2454b3b644 | ||
|
|
87b243ed59 | ||
|
|
cec6f5db14 | ||
|
|
e7dd49b453 | ||
|
|
6fdc470b0b | ||
|
|
695aa7bec9 | ||
|
|
3952dba094 | ||
|
|
209f8155d1 | ||
|
|
8621733e17 | ||
|
|
621301862c | ||
|
|
fd4c5a90ed | ||
|
|
bfec073457 | ||
|
|
dc6e869c64 | ||
|
|
bed592460d | ||
|
|
46d3555798 | ||
|
|
74967737ee | ||
|
|
a347cb591d | ||
|
|
1ae60ef8a7 | ||
|
|
f9db687385 | ||
|
|
a38544796f | ||
|
|
5ecc8d1c71 | ||
|
|
c9cf7f6564 | ||
|
|
ba406d5747 | ||
|
|
c72ceadfce | ||
|
|
1e7f1b1e54 | ||
|
|
7c0f6f6e6e | ||
|
|
3f32cb154b | ||
|
|
6fa504489f | ||
|
|
433f373f60 | ||
|
|
b771b84f56 | ||
|
|
45abaec86e | ||
|
|
d0a96d463d | ||
|
|
12f1aedcd1 | ||
|
|
f0874bf585 | ||
|
|
1a749dc7b7 | ||
|
|
ec49d10007 | ||
|
|
294dc6fbd0 | ||
|
|
9e50a45b4c | ||
|
|
72ffb9dbb9 | ||
|
|
a677ab64e3 | ||
|
|
b33afcd47c | ||
|
|
659892edcb | ||
|
|
803fde6c1d | ||
|
|
83063a5740 | ||
|
|
7efa3a3ddf | ||
|
|
c1d93e251d | ||
|
|
8f3256675a | ||
|
|
9711ac2ecb | ||
|
|
833c56e17b | ||
|
|
9bb59c0f94 | ||
|
|
629ec6383e | ||
|
|
a6b6ecf4ba | ||
|
|
e5ce080fd3 | ||
|
|
80c4b2aa31 | ||
|
|
0b8f9edb9e | ||
|
|
63458a7de7 | ||
|
|
f6a080729a | ||
|
|
1dacddb7e3 | ||
|
|
334aa37b7f | ||
|
|
725f42e185 | ||
|
|
69ce1e05f0 | ||
|
|
8d3136188d | ||
|
|
cb4f856ab6 | ||
|
|
65b269cd54 | ||
|
|
df93244373 | ||
|
|
a8de125e26 | ||
|
|
7db06b8afb | ||
|
|
7c6032cc84 | ||
|
|
939da455d7 | ||
|
|
817dbce391 | ||
|
|
0f447fadd4 | ||
|
|
79825dfa1d | ||
|
|
b640e9fe39 | ||
|
|
9dd4cbb40e | ||
|
|
82423db239 | ||
|
|
0262b64062 | ||
|
|
bd920b8ba9 | ||
|
|
57da86c598 | ||
|
|
cd92cc504b | ||
|
|
e82e584b81 | ||
|
|
ccbd8e8c70 | ||
|
|
e396261399 | ||
|
|
aae368f1e7 | ||
|
|
5ac4a852e1 | ||
|
|
fad33bc50f | ||
|
|
d5925b8ccf | ||
|
|
5e97a46afc | ||
|
|
8ee2134e05 | ||
|
|
2da9986717 | ||
|
|
c3b6b07e3a | ||
|
|
045d6ef1d0 | ||
|
|
ecba291ea0 | ||
|
|
328ab1cc27 | ||
|
|
884f84d5ed | ||
|
|
b2d48c86a6 | ||
|
|
3c5a0a3394 | ||
|
|
6ec82865d3 | ||
|
|
dab2225a39 | ||
|
|
0e93603aee | ||
|
|
d44f347b08 | ||
|
|
99f12ff720 | ||
|
|
a3cb3730b4 | ||
|
|
2cca3bf570 | ||
|
|
33cd261d83 | ||
|
|
e515d7bbbe | ||
|
|
0c05dc093f | ||
|
|
68701900d8 | ||
|
|
ee18d062ab | ||
|
|
695213dd31 | ||
|
|
50fc083d97 | ||
|
|
798fd84698 | ||
|
|
19575b2c8f | ||
|
|
a52f7f9baa | ||
|
|
fc84ffbd65 | ||
|
|
27e5d9672a | ||
|
|
8f31425daf | ||
|
|
7c4591617b | ||
|
|
c21ab943bb | ||
|
|
d8c8afe070 | ||
|
|
d1a35a5acc | ||
|
|
2d4cda7eda | ||
|
|
5e731d9557 | ||
|
|
6df7a80352 | ||
|
|
d261bbef88 | ||
|
|
4fa903a02f | ||
|
|
f0fd9e6137 | ||
|
|
a45ffce118 | ||
|
|
c558e7e0b1 | ||
|
|
bfec70481b | ||
|
|
a7c8fe4375 | ||
|
|
da52a5c9cf | ||
|
|
639cf1e038 | ||
|
|
76bfbad2c4 | ||
|
|
cca352fa9f | ||
|
|
7043ecc21b | ||
|
|
c384b8a81f | ||
|
|
747738c0fc | ||
|
|
3e71aeddf3 | ||
|
|
a2e00e4c3b | ||
|
|
2c6e9be8e5 | ||
|
|
fce41c0ab8 | ||
|
|
ca5c806f58 | ||
|
|
1e8f5f1a51 | ||
|
|
b434c2b9cf | ||
|
|
60afd280c3 | ||
|
|
9b2ef96b22 | ||
|
|
44ee53b0d8 | ||
|
|
cc9131441e | ||
|
|
5e496fb9bb | ||
|
|
5c089e45d4 | ||
|
|
a27be0cee7 | ||
|
|
a0ae8c126e | ||
|
|
55556fc63c | ||
|
|
8fc5b00d91 | ||
|
|
475c6403cc | ||
|
|
cf17687b8b | ||
|
|
50339f6f0e | ||
|
|
26b1541f4a | ||
|
|
1c3a475d1e | ||
|
|
65136fabdd | ||
|
|
70d1ac7162 | ||
|
|
b1f35ff974 | ||
|
|
f5d4554b39 | ||
|
|
8a0cb507c5 | ||
|
|
89a43b8771 | ||
|
|
83571dba5d | ||
|
|
a6d59faf98 | ||
|
|
9512180406 | ||
|
|
0f0f20716f | ||
|
|
b4f072ed81 | ||
|
|
394a9daa64 | ||
|
|
a3beef754c | ||
|
|
94afba91a0 | ||
|
|
8794416241 | ||
|
|
f471ad46c9 | ||
|
|
989a701e95 | ||
|
|
37c1c84e04 | ||
|
|
39ffd3d756 | ||
|
|
b920c7b2c4 | ||
|
|
a2c54b03c8 | ||
|
|
7666387277 | ||
|
|
cc0eb47814 | ||
|
|
c280634bfa | ||
|
|
fb1cd9d27d | ||
|
|
b612415a88 | ||
|
|
223041bc6b | ||
|
|
ab0514041e | ||
|
|
750e942267 | ||
|
|
17c13043d0 | ||
|
|
750075d0dd | ||
|
|
9df43e3bd2 | ||
|
|
d1eaace4cf | ||
|
|
39f40bc005 | ||
|
|
6c64494620 | ||
|
|
c9e3fe9a92 | ||
|
|
4ce055cede | ||
|
|
1e65174867 | ||
|
|
1052c71124 | ||
|
|
a86b5988b5 | ||
|
|
088f0ea630 | ||
|
|
08dbdeb57f | ||
|
|
8a5f817030 | ||
|
|
675faee593 | ||
|
|
7e33f4a7d1 | ||
|
|
71c88a8dfc | ||
|
|
52a4509ada | ||
|
|
1ee6df412e | ||
|
|
94076a3b24 | ||
|
|
43002f7a8a | ||
|
|
8816b55843 | ||
|
|
9c724b616d | ||
|
|
025cf5ebaf | ||
|
|
6f1b8e1fee | ||
|
|
80b071024f | ||
|
|
4880c0443c | ||
|
|
c05057748f | ||
|
|
de7006a048 | ||
|
|
2b6811a366 | ||
|
|
10f9800327 | ||
|
|
292777259e | ||
|
|
01c42f9359 | ||
|
|
ef48594d85 | ||
|
|
c9a58fad15 | ||
|
|
5b54375490 | ||
|
|
db4f3561a5 | ||
|
|
f138eecc27 | ||
|
|
5a86623c88 | ||
|
|
14dc0ff92f | ||
|
|
4302b440ee | ||
|
|
c75dcb90c2 | ||
|
|
3efd103712 | ||
|
|
f922164a88 | ||
|
|
36257863db | ||
|
|
14f96fdb4b | ||
|
|
570d71c07b | ||
|
|
8ae1b1740d | ||
|
|
4f9e35c0a1 | ||
|
|
67bb9aef3d | ||
|
|
1ae268ec6f | ||
|
|
10deac0b06 | ||
|
|
057cc37b60 | ||
|
|
70a8255eae | ||
|
|
d5c3bde0af | ||
|
|
5d840500af | ||
|
|
904a5c3ca5 | ||
|
|
66698f60d4 | ||
|
|
3035cbc5db | ||
|
|
6c18b300a4 | ||
|
|
6baf9b89fe | ||
|
|
fd5f7eb165 | ||
|
|
76c37de1e8 | ||
|
|
c3f3c5c6a5 | ||
|
|
f2b28d8215 | ||
|
|
672f5d879b | ||
|
|
20a6795f74 | ||
|
|
c1ef4067bc | ||
|
|
c1fb71c031 | ||
|
|
136f3bc5f9 | ||
|
|
2052a0dff1 | ||
|
|
ee8c986e8e | ||
|
|
b58bfe56d6 | ||
|
|
6c5d1b9214 | ||
|
|
97be09a3bc | ||
|
|
6605ba1362 | ||
|
|
ac18b6c71e | ||
|
|
d9069e0955 | ||
|
|
3f98f23422 | ||
|
|
706c1e2660 | ||
|
|
95d2130405 | ||
|
|
683428a2c2 | ||
|
|
a315919dfc | ||
|
|
1b89472451 | ||
|
|
ae2a1e929f | ||
|
|
ad995934b0 | ||
|
|
c5b6507830 | ||
|
|
d7c5f991c8 | ||
|
|
cef1973ee4 | ||
|
|
d98c375495 | ||
|
|
da516a78b3 | ||
|
|
a0f359c93c | ||
|
|
4bf5da8fac | ||
|
|
44977207e3 | ||
|
|
facdd586a3 | ||
|
|
893963f339 | ||
|
|
a5e5f4cf9c | ||
|
|
fdb7924d3f | ||
|
|
782620655f | ||
|
|
446b57b827 | ||
|
|
2c287af977 | ||
|
|
4bd567e610 | ||
|
|
35b0872ed9 | ||
|
|
89348af59e | ||
|
|
ccaac40863 | ||
|
|
33bd7ca45f | ||
|
|
408850e7bd | ||
|
|
f4a77c3d06 | ||
|
|
3ea555bae6 | ||
|
|
c543035f5a | ||
|
|
b6b567f948 | ||
|
|
479295cfd2 | ||
|
|
a78cfa4b2c | ||
|
|
c6caeb3bec | ||
|
|
5f58c96258 | ||
|
|
bb1d255c40 | ||
|
|
a9d47287ee | ||
|
|
3d46b4c425 | ||
|
|
db428dad1d | ||
|
|
4cc44bc174 | ||
|
|
3e5f490882 | ||
|
|
7579606ac4 | ||
|
|
ee4712b2e9 | ||
|
|
2d1a458f5d | ||
|
|
e958bd254a | ||
|
|
0c6b47b9f2 | ||
|
|
4cea1919f6 | ||
|
|
91da6a47d7 | ||
|
|
f13d759a61 | ||
|
|
21e423ad5f | ||
|
|
c6ac3266f0 | ||
|
|
621d234adc | ||
|
|
ab72b2b141 | ||
|
|
e8dd05619b | ||
|
|
25909b5e79 | ||
|
|
5925a99800 | ||
|
|
6e773c84e8 | ||
|
|
73e3f2a89b | ||
|
|
69dddfa73a | ||
|
|
3b8972e027 | ||
|
|
50b181e804 | ||
|
|
21459d0f43 | ||
|
|
fbe1fd8f98 | ||
|
|
a8c844828d | ||
|
|
c0959fc562 | ||
|
|
6a86bb8ebe | ||
|
|
76e2598c5c | ||
|
|
bf5e724f00 | ||
|
|
112538a220 | ||
|
|
23ea1f8939 | ||
|
|
93f9def3e8 | ||
|
|
8a7703b0ef | ||
|
|
2578df1a82 | ||
|
|
8dca101069 | ||
|
|
4ee759d9ec | ||
|
|
3129e392b0 | ||
|
|
0d0d614b99 | ||
|
|
3fec344fa3 | ||
|
|
f8a78d5565 | ||
|
|
68e853b4bd | ||
|
|
2cc4a920a0 | ||
|
|
b67cb0e97a | ||
|
|
1b2f6b763a | ||
|
|
1c2f49ab4e | ||
|
|
3c237760b6 | ||
|
|
859c5cbe1e | ||
|
|
28f31ce57f | ||
|
|
317749a7fd | ||
|
|
94f33473a8 | ||
|
|
04f2b9ac01 | ||
|
|
7882a6513c | ||
|
|
7e6c49fc74 | ||
|
|
765c3a7b0d | ||
|
|
f3df185bb5 | ||
|
|
8c42167302 | ||
|
|
4b27e66714 | ||
|
|
3e28ac1e46 | ||
|
|
a119a46495 | ||
|
|
3ca836ffdc | ||
|
|
bc718471da | ||
|
|
e24b27c79e | ||
|
|
dcaac76b4b | ||
|
|
d741349830 | ||
|
|
cd3f9ee27c | ||
|
|
3320d34c2b | ||
|
|
036407fd90 | ||
|
|
8bca84c0e7 | ||
|
|
f7a6d39b01 | ||
|
|
c79378ca5f | ||
|
|
4d47f758fc | ||
|
|
e390cffa63 | ||
|
|
c615674cbb | ||
|
|
1a4f9e656d | ||
|
|
e1a214351f | ||
|
|
9801f09a93 | ||
|
|
9f8fb98083 | ||
|
|
fd86f3bda8 | ||
|
|
390becb935 | ||
|
|
7d8c79b17b | ||
|
|
772a1580ff | ||
|
|
5a00aaefa8 | ||
|
|
1bf24d26a4 | ||
|
|
284f49e584 | ||
|
|
e7ab53dd5f | ||
|
|
139158e614 | ||
|
|
c4ad84945c | ||
|
|
94d4afa11c | ||
|
|
d7d91cfbc0 | ||
|
|
757d5c16df | ||
|
|
36c2422fa4 | ||
|
|
ec0f222f7e | ||
|
|
969b44768a | ||
|
|
db0024d857 | ||
|
|
d59f6e397f | ||
|
|
072ccb5c61 | ||
|
|
8ee566f5fa | ||
|
|
8e9db1be96 | ||
|
|
115f95d90e | ||
|
|
2bba6af6d9 | ||
|
|
0d3a50411e | ||
|
|
3bed84d712 | ||
|
|
0485f5c43d |
No files matched your search
@@ -14,6 +14,7 @@
|
|||||||
nbactions.xml
|
nbactions.xml
|
||||||
nb-configuration.xml
|
nb-configuration.xml
|
||||||
catalog.xml
|
catalog.xml
|
||||||
|
nbproject
|
||||||
|
|
||||||
# Compiled source #
|
# Compiled source #
|
||||||
###################
|
###################
|
||||||
|
|||||||
+8
-1
@@ -11,6 +11,13 @@ env:
|
|||||||
global:
|
global:
|
||||||
- MAVEN_SKIP_RC=true
|
- MAVEN_SKIP_RC=true
|
||||||
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
||||||
|
matrix:
|
||||||
|
- TESTS=group1
|
||||||
|
- TESTS=group2
|
||||||
|
- TESTS=group3
|
||||||
|
- TESTS=group4
|
||||||
|
- TESTS=adapter
|
||||||
|
- TESTS=old
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
@@ -22,6 +29,6 @@ install:
|
|||||||
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- mvn test -B
|
- ./travis-run-tests.sh $TESTS
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
@@ -9,7 +9,7 @@ For more information about Keycloak visit [Keycloak homepage](http://keycloak.or
|
|||||||
Building
|
Building
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
|
Ensure you have JDK 8 (or newer), Maven 3.1.1 (or newer) and Git installed
|
||||||
|
|
||||||
java -version
|
java -version
|
||||||
mvn -version
|
mvn -version
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+16
@@ -342,6 +342,12 @@ public class AdapterDeploymentContext {
|
|||||||
return delegate.getNotBefore();
|
return delegate.getNotBefore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateNotBefore(int notBefore) {
|
||||||
|
delegate.setNotBefore(notBefore);
|
||||||
|
getPublicKeyLocator().reset(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setExposeToken(boolean exposeToken) {
|
public void setExposeToken(boolean exposeToken) {
|
||||||
delegate.setExposeToken(exposeToken);
|
delegate.setExposeToken(exposeToken);
|
||||||
@@ -446,6 +452,16 @@ public class AdapterDeploymentContext {
|
|||||||
public int getMinTimeBetweenJwksRequests() {
|
public int getMinTimeBetweenJwksRequests() {
|
||||||
return delegate.getMinTimeBetweenJwksRequests();
|
return delegate.getMinTimeBetweenJwksRequests();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPublicKeyCacheTtl() {
|
||||||
|
return delegate.getPublicKeyCacheTtl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
||||||
|
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||||
|
|||||||
+6
-2
@@ -142,9 +142,13 @@ public class AuthenticatedActionsHandler {
|
|||||||
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
||||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
||||||
|
|
||||||
session.setAuthorizationContext(authorizationContext);
|
if (session != null) {
|
||||||
|
session.setAuthorizationContext(authorizationContext);
|
||||||
|
|
||||||
return authorizationContext.isGranted();
|
return authorizationContext.isGranted();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-8
@@ -46,14 +46,7 @@ public class HttpAdapterUtils {
|
|||||||
}
|
}
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
try {
|
try {
|
||||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
return JsonSerialization.readValue(is, clazz);
|
||||||
int c;
|
|
||||||
while ((c = is.read()) != -1) {
|
|
||||||
os.write(c);
|
|
||||||
}
|
|
||||||
byte[] bytes = os.toByteArray();
|
|
||||||
String json = new String(bytes);
|
|
||||||
return JsonSerialization.readValue(json, clazz);
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
is.close();
|
is.close();
|
||||||
|
|||||||
+4
-4
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
|
|||||||
import org.apache.http.params.HttpConnectionParams;
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
import org.keycloak.common.util.EnvUtil;
|
import org.keycloak.common.util.EnvUtil;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient build(AdapterConfig adapterConfig) {
|
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
|
||||||
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
|
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
|
||||||
|
|
||||||
String truststorePath = adapterConfig.getTruststore();
|
String truststorePath = adapterConfig.getTruststore();
|
||||||
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
|
|||||||
/**
|
/**
|
||||||
* Configures a the proxy to use for auth-server requests if provided.
|
* Configures a the proxy to use for auth-server requests if provided.
|
||||||
* <p>
|
* <p>
|
||||||
* If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
|
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
|
||||||
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param adapterConfig
|
* @param adapterConfig
|
||||||
*/
|
*/
|
||||||
private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
|
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
|
||||||
|
|
||||||
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
+23
@@ -57,6 +57,7 @@ public class KeycloakDeployment {
|
|||||||
|
|
||||||
protected String resourceName;
|
protected String resourceName;
|
||||||
protected boolean bearerOnly;
|
protected boolean bearerOnly;
|
||||||
|
protected boolean autodetectBearerOnly;
|
||||||
protected boolean enableBasicAuth;
|
protected boolean enableBasicAuth;
|
||||||
protected boolean publicClient;
|
protected boolean publicClient;
|
||||||
protected Map<String, Object> resourceCredentials = new HashMap<>();
|
protected Map<String, Object> resourceCredentials = new HashMap<>();
|
||||||
@@ -81,6 +82,7 @@ public class KeycloakDeployment {
|
|||||||
protected volatile int notBefore;
|
protected volatile int notBefore;
|
||||||
protected int tokenMinimumTimeToLive;
|
protected int tokenMinimumTimeToLive;
|
||||||
protected int minTimeBetweenJwksRequests;
|
protected int minTimeBetweenJwksRequests;
|
||||||
|
protected int publicKeyCacheTtl;
|
||||||
private PolicyEnforcer policyEnforcer;
|
private PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
public KeycloakDeployment() {
|
public KeycloakDeployment() {
|
||||||
@@ -200,6 +202,14 @@ public class KeycloakDeployment {
|
|||||||
this.bearerOnly = bearerOnly;
|
this.bearerOnly = bearerOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAutodetectBearerOnly() {
|
||||||
|
return autodetectBearerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
||||||
|
this.autodetectBearerOnly = autodetectBearerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnableBasicAuth() {
|
public boolean isEnableBasicAuth() {
|
||||||
return enableBasicAuth;
|
return enableBasicAuth;
|
||||||
}
|
}
|
||||||
@@ -328,6 +338,11 @@ public class KeycloakDeployment {
|
|||||||
this.notBefore = notBefore;
|
this.notBefore = notBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateNotBefore(int notBefore) {
|
||||||
|
this.notBefore = notBefore;
|
||||||
|
getPublicKeyLocator().reset(this);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isAlwaysRefreshToken() {
|
public boolean isAlwaysRefreshToken() {
|
||||||
return alwaysRefreshToken;
|
return alwaysRefreshToken;
|
||||||
}
|
}
|
||||||
@@ -384,6 +399,14 @@ public class KeycloakDeployment {
|
|||||||
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPublicKeyCacheTtl() {
|
||||||
|
return publicKeyCacheTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
||||||
|
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||||
|
}
|
||||||
|
|
||||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
this.policyEnforcer = policyEnforcer;
|
this.policyEnforcer = policyEnforcer;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -99,12 +99,14 @@ public class KeycloakDeploymentBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deployment.setBearerOnly(adapterConfig.isBearerOnly());
|
deployment.setBearerOnly(adapterConfig.isBearerOnly());
|
||||||
|
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
|
||||||
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
||||||
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
||||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||||
|
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
||||||
|
|
||||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||||
|
|||||||
+5
-6
@@ -25,6 +25,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
|
|||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.Encode;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
@@ -169,7 +170,7 @@ public class OAuthRequestAuthenticator {
|
|||||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, url)
|
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
.queryParam("login", "true");
|
.queryParam("login", "true");
|
||||||
if(loginHint != null && loginHint.length() > 0){
|
if(loginHint != null && loginHint.length() > 0){
|
||||||
@@ -195,15 +196,13 @@ public class OAuthRequestAuthenticator {
|
|||||||
return sslRedirectPort;
|
return sslRedirectPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final AtomicLong counter = new AtomicLong();
|
|
||||||
|
|
||||||
protected String getStateCode() {
|
protected String getStateCode() {
|
||||||
return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
|
return AdapterUtils.generateId();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthChallenge loginRedirect() {
|
protected AuthChallenge loginRedirect() {
|
||||||
final String state = getStateCode();
|
final String state = getStateCode();
|
||||||
final String redirect = getRedirectUri(state);
|
final String redirect = getRedirectUri(state);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
|
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
|
||||||
}
|
}
|
||||||
@@ -357,7 +356,7 @@ public class OAuthRequestAuthenticator {
|
|||||||
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
|
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
|
||||||
}
|
}
|
||||||
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
|
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||||
deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
|
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy());
|
||||||
}
|
}
|
||||||
if (token.getIssuedAt() < deployment.getNotBefore()) {
|
if (token.getIssuedAt() < deployment.getNotBefore()) {
|
||||||
log.error("Stale token");
|
log.error("Stale token");
|
||||||
|
|||||||
+2
-2
@@ -155,7 +155,7 @@ public class PreAuthActionsHandler {
|
|||||||
} else {
|
} else {
|
||||||
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
||||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
if (action.getNotBefore() > deployment.getNotBefore()) {
|
||||||
deployment.setNotBefore(action.getNotBefore());
|
deployment.updateNotBefore(action.getNotBefore());
|
||||||
}
|
}
|
||||||
userSessionManagement.logoutAll();
|
userSessionManagement.logoutAll();
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ public class PreAuthActionsHandler {
|
|||||||
}
|
}
|
||||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
||||||
if (!validateAction(action)) return;
|
if (!validateAction(action)) return;
|
||||||
deployment.setNotBefore(action.getNotBefore());
|
deployment.updateNotBefore(action.getNotBefore());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -144,7 +144,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||||
deployment.setNotBefore(response.getNotBeforePolicy());
|
deployment.updateNotBefore(response.getNotBeforePolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
|||||||
+38
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.adapters.spi.AuthChallenge;
|
import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
@@ -116,6 +118,12 @@ public abstract class RequestAuthenticator {
|
|||||||
return AuthOutcome.NOT_ATTEMPTED;
|
return AuthOutcome.NOT_ATTEMPTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAutodetectedBearerOnly(facade.getRequest())) {
|
||||||
|
challenge = bearer.getChallenge();
|
||||||
|
log.debug("NOT_ATTEMPTED: Treating as bearer only");
|
||||||
|
return AuthOutcome.NOT_ATTEMPTED;
|
||||||
|
}
|
||||||
|
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try oauth");
|
log.trace("try oauth");
|
||||||
}
|
}
|
||||||
@@ -158,6 +166,36 @@ public abstract class RequestAuthenticator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
|
||||||
|
if (!deployment.isAutodetectBearerOnly()) return false;
|
||||||
|
|
||||||
|
String headerValue = facade.getRequest().getHeader("X-Requested-With");
|
||||||
|
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValue = facade.getRequest().getHeader("Faces-Request");
|
||||||
|
if (headerValue != null && headerValue.startsWith("partial/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValue = facade.getRequest().getHeader("SOAPAction");
|
||||||
|
if (headerValue != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> accepts = facade.getRequest().getHeaders("Accept");
|
||||||
|
if (accepts == null) accepts = Collections.emptyList();
|
||||||
|
|
||||||
|
for (String accept : accepts) {
|
||||||
|
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
|
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
|
||||||
|
|
||||||
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
||||||
|
|||||||
+82
-54
@@ -17,8 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.adapters.authorization;
|
package org.keycloak.adapters.authorization;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.AuthorizationContext;
|
import org.keycloak.AuthorizationContext;
|
||||||
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||||
@@ -31,13 +39,6 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.Enforce
|
|||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
@@ -47,7 +48,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
private final PolicyEnforcerConfig enforcerConfig;
|
private final PolicyEnforcerConfig enforcerConfig;
|
||||||
private final PolicyEnforcer policyEnforcer;
|
private final PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
private List<PathConfig> paths;
|
private Map<String, PathConfig> paths;
|
||||||
private AuthzClient authzClient;
|
private AuthzClient authzClient;
|
||||||
private PathMatcher pathMatcher;
|
private PathMatcher pathMatcher;
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||||
this.authzClient = policyEnforcer.getClient();
|
this.authzClient = policyEnforcer.getClient();
|
||||||
this.pathMatcher = new PathMatcher();
|
this.pathMatcher = new PathMatcher();
|
||||||
this.paths = new ArrayList<>(policyEnforcer.getPaths());
|
this.paths = policyEnforcer.getPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||||
@@ -66,40 +67,50 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
return createEmptyAuthorizationContext(true);
|
return createEmptyAuthorizationContext(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessToken accessToken = httpFacade.getSecurityContext().getToken();
|
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||||
Request request = httpFacade.getRequest();
|
|
||||||
Response response = httpFacade.getResponse();
|
|
||||||
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
|
||||||
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
|
||||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
|
||||||
|
|
||||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
if (securityContext != null) {
|
||||||
|
AccessToken accessToken = securityContext.getToken();
|
||||||
|
|
||||||
if (pathConfig == null) {
|
if (accessToken != null) {
|
||||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
Request request = httpFacade.getRequest();
|
||||||
return createAuthorizationContext(accessToken);
|
Response response = httpFacade.getResponse();
|
||||||
|
String path = getPath(request);
|
||||||
|
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||||
|
|
||||||
|
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||||
|
|
||||||
|
if (pathConfig == null) {
|
||||||
|
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||||
|
return createAuthorizationContext(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||||
|
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||||
|
|
||||||
|
return createEmptyAuthorizationContext(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||||
|
return createEmptyAuthorizationContext(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||||
|
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||||
|
|
||||||
|
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||||
|
try {
|
||||||
|
return createAuthorizationContext(accessToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||||
|
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||||
|
response.sendError(403, "Authorization failed.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
|
||||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
|
||||||
|
|
||||||
return createEmptyAuthorizationContext(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
|
||||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
|
||||||
|
|
||||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
|
||||||
try {
|
|
||||||
return createAuthorizationContext(accessToken);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
|
||||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
|
||||||
response.sendError(403, "Authorization failed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return createEmptyAuthorizationContext(false);
|
return createEmptyAuthorizationContext(false);
|
||||||
@@ -110,12 +121,9 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||||
Request request = httpFacade.getRequest();
|
Request request = httpFacade.getRequest();
|
||||||
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
||||||
String accessDeniedPath = enforcerConfig.getAccessDeniedPath();
|
|
||||||
|
|
||||||
if (accessDeniedPath != null) {
|
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||||
if (request.getURI().contains(accessDeniedPath)) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||||
@@ -125,14 +133,17 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Permission> permissions = authorization.getPermissions();
|
List<Permission> permissions = authorization.getPermissions();
|
||||||
|
boolean hasPermission = false;
|
||||||
|
|
||||||
for (Permission permission : permissions) {
|
for (Permission permission : permissions) {
|
||||||
if (permission.getResourceSetId() != null) {
|
if (permission.getResourceSetId() != null) {
|
||||||
if (isResourcePermission(actualPathConfig, permission)) {
|
if (isResourcePermission(actualPathConfig, permission)) {
|
||||||
|
hasPermission = true;
|
||||||
|
|
||||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||||
@@ -143,16 +154,32 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||||
|
hasPermission = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
||||||
|
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||||
|
|
||||||
|
if (accessDeniedPath != null) {
|
||||||
|
if (request.getURI().contains(accessDeniedPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
||||||
Set<String> allowedScopes = permission.getScopes();
|
Set<String> allowedScopes = permission.getScopes();
|
||||||
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
||||||
@@ -200,26 +227,23 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
||||||
|
String path = getPath(request);
|
||||||
|
|
||||||
if (originalConfig.hasPattern()) {
|
if (originalConfig.hasPattern()) {
|
||||||
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
|
||||||
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
|
||||||
ProtectedResource resource = this.authzClient.protection().resource();
|
ProtectedResource resource = this.authzClient.protection().resource();
|
||||||
Set<String> search = resource.findByFilter("uri=" + path);
|
Set<String> search = resource.findByFilter("uri=" + path);
|
||||||
|
|
||||||
if (!search.isEmpty()) {
|
if (!search.isEmpty()) {
|
||||||
// resource does exist on the server, cache it
|
// resource does exist on the server, cache it
|
||||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||||
PathConfig config = new PathConfig();
|
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||||
|
|
||||||
config.setId(targetResource.getId());
|
|
||||||
config.setName(targetResource.getName());
|
|
||||||
config.setType(targetResource.getType());
|
|
||||||
config.setPath(targetResource.getUri());
|
|
||||||
config.setScopes(originalConfig.getScopes());
|
config.setScopes(originalConfig.getScopes());
|
||||||
config.setMethods(originalConfig.getMethods());
|
config.setMethods(originalConfig.getMethods());
|
||||||
config.setParentConfig(originalConfig);
|
config.setParentConfig(originalConfig);
|
||||||
|
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||||
|
|
||||||
this.paths.add(config);
|
this.policyEnforcer.addPath(config);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@@ -228,6 +252,10 @@ public abstract class AbstractPolicyEnforcer {
|
|||||||
return originalConfig;
|
return originalConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPath(Request request) {
|
||||||
|
return request.getRelativePath();
|
||||||
|
}
|
||||||
|
|
||||||
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
|
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
|
||||||
Set<String> requiredScopes = new HashSet<>();
|
Set<String> requiredScopes = new HashSet<>();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||||
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
|
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||||
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
|
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
|
||||||
} else {
|
} else {
|
||||||
challengeEntitlementAuthentication(facade);
|
challengeEntitlementAuthentication(facade);
|
||||||
|
|||||||
+33
-36
@@ -17,6 +17,10 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.adapters.authorization;
|
package org.keycloak.adapters.authorization;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
@@ -34,10 +38,6 @@ import org.keycloak.representations.AccessToken;
|
|||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
import org.keycloak.representations.idm.authorization.Permission;
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
*/
|
*/
|
||||||
@@ -51,44 +51,39 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||||
int retry = 2;
|
|
||||||
AccessToken original = accessToken;
|
AccessToken original = accessToken;
|
||||||
|
|
||||||
while (retry > 0) {
|
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||||
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
|
|
||||||
|
|
||||||
if (accessToken == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Authorization authorization = original.getAuthorization();
|
|
||||||
|
|
||||||
if (authorization == null) {
|
|
||||||
authorization = new AccessToken.Authorization();
|
|
||||||
authorization.setPermissions(new ArrayList<Permission>());
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
|
||||||
|
|
||||||
if (newAuthorization != null) {
|
|
||||||
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
|
||||||
}
|
|
||||||
|
|
||||||
original.setAuthorization(authorization);
|
|
||||||
|
|
||||||
retry--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
|
||||||
|
|
||||||
|
if (accessToken == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Authorization authorization = original.getAuthorization();
|
||||||
|
|
||||||
|
if (authorization == null) {
|
||||||
|
authorization = new AccessToken.Authorization();
|
||||||
|
authorization.setPermissions(new ArrayList<Permission>());
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||||
|
|
||||||
|
if (newAuthorization != null) {
|
||||||
|
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
original.setAuthorization(authorization);
|
||||||
|
|
||||||
|
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||||
String accessDeniedPath = getEnforcerConfig().getAccessDeniedPath();
|
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||||
HttpFacade.Response response = facade.getResponse();
|
HttpFacade.Response response = facade.getResponse();
|
||||||
|
|
||||||
if (accessDeniedPath != null) {
|
if (accessDeniedPath != null) {
|
||||||
@@ -107,8 +102,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
AuthzClient authzClient = getAuthzClient();
|
AuthzClient authzClient = getAuthzClient();
|
||||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||||
|
|
||||||
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
|
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||||
PermissionRequest permissionRequest = new PermissionRequest();
|
PermissionRequest permissionRequest = new PermissionRequest();
|
||||||
|
|
||||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||||
@@ -136,12 +131,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
|||||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||||
|
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
||||||
request.addPermission(permissionRequest);
|
request.addPermission(permissionRequest);
|
||||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AuthorizationDeniedException e) {
|
} catch (AuthorizationDeniedException e) {
|
||||||
|
LOGGER.debug("Authorization denied", e);
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Unexpected error during authorization request.", e);
|
throw new RuntimeException("Unexpected error during authorization request.", e);
|
||||||
|
|||||||
+9
-3
@@ -19,7 +19,7 @@ package org.keycloak.adapters.authorization;
|
|||||||
|
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
@@ -28,10 +28,16 @@ class PathMatcher {
|
|||||||
|
|
||||||
private static final String ANY_RESOURCE_PATTERN = "/*";
|
private static final String ANY_RESOURCE_PATTERN = "/*";
|
||||||
|
|
||||||
PathConfig matches(final String requestedUri, List<PathConfig> paths) {
|
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
|
||||||
|
PathConfig pathConfig = paths.get(requestedUri);
|
||||||
|
|
||||||
|
if (pathConfig != null) {
|
||||||
|
return pathConfig;
|
||||||
|
}
|
||||||
|
|
||||||
PathConfig actualConfig = null;
|
PathConfig actualConfig = null;
|
||||||
|
|
||||||
for (PathConfig entry : paths) {
|
for (PathConfig entry : paths.values()) {
|
||||||
String protectedUri = entry.getPath();
|
String protectedUri = entry.getPath();
|
||||||
String selectedUri = null;
|
String selectedUri = null;
|
||||||
|
|
||||||
|
|||||||
+30
-14
@@ -34,8 +34,10 @@ import org.keycloak.representations.idm.authorization.Permission;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +50,7 @@ public class PolicyEnforcer {
|
|||||||
private final KeycloakDeployment deployment;
|
private final KeycloakDeployment deployment;
|
||||||
private final AuthzClient authzClient;
|
private final AuthzClient authzClient;
|
||||||
private final PolicyEnforcerConfig enforcerConfig;
|
private final PolicyEnforcerConfig enforcerConfig;
|
||||||
private final List<PathConfig> paths;
|
private final Map<String, PathConfig> paths;
|
||||||
|
|
||||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||||
this.deployment = deployment;
|
this.deployment = deployment;
|
||||||
@@ -58,7 +60,7 @@ public class PolicyEnforcer {
|
|||||||
|
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("Initialization complete. Path configurations:");
|
LOGGER.debug("Initialization complete. Path configurations:");
|
||||||
for (PathConfig pathConfig : this.paths) {
|
for (PathConfig pathConfig : this.paths.values()) {
|
||||||
LOGGER.debug(pathConfig);
|
LOGGER.debug(pathConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,16 +98,29 @@ public class PolicyEnforcer {
|
|||||||
return authzClient;
|
return authzClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PathConfig> getPaths() {
|
public Map<String, PathConfig> getPaths() {
|
||||||
return Collections.unmodifiableList(paths);
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPath(PathConfig pathConfig) {
|
||||||
|
paths.put(pathConfig.getPath(), pathConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeycloakDeployment getDeployment() {
|
KeycloakDeployment getDeployment() {
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||||
if (enforcerConfig.getPaths().isEmpty()) {
|
boolean loadPathsFromServer = true;
|
||||||
|
|
||||||
|
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||||
|
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||||
|
loadPathsFromServer = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadPathsFromServer) {
|
||||||
LOGGER.info("No path provided in configuration.");
|
LOGGER.info("No path provided in configuration.");
|
||||||
return configureAllPathsForResourceServer(protectedResource);
|
return configureAllPathsForResourceServer(protectedResource);
|
||||||
} else {
|
} else {
|
||||||
@@ -114,8 +129,8 @@ public class PolicyEnforcer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||||
List<PathConfig> paths = new ArrayList<>();
|
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||||
|
|
||||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||||
Set<String> search;
|
Set<String> search;
|
||||||
@@ -163,7 +178,7 @@ public class PolicyEnforcer {
|
|||||||
|
|
||||||
PathConfig existingPath = null;
|
PathConfig existingPath = null;
|
||||||
|
|
||||||
for (PathConfig current : paths) {
|
for (PathConfig current : paths.values()) {
|
||||||
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
||||||
existingPath = current;
|
existingPath = current;
|
||||||
break;
|
break;
|
||||||
@@ -171,7 +186,7 @@ public class PolicyEnforcer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (existingPath == null) {
|
if (existingPath == null) {
|
||||||
paths.add(pathConfig);
|
paths.put(pathConfig.getPath(), pathConfig);
|
||||||
} else {
|
} else {
|
||||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||||
@@ -181,23 +196,24 @@ public class PolicyEnforcer {
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||||
List<PathConfig> paths = new ArrayList<>();
|
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||||
|
|
||||||
for (String id : protectedResource.findAll()) {
|
for (String id : protectedResource.findAll()) {
|
||||||
RegistrationResponse response = protectedResource.findById(id);
|
RegistrationResponse response = protectedResource.findById(id);
|
||||||
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
||||||
|
|
||||||
if (resourceDescription.getUri() != null) {
|
if (resourceDescription.getUri() != null) {
|
||||||
paths.add(createPathConfig(resourceDescription));
|
PathConfig pathConfig = createPathConfig(resourceDescription);
|
||||||
|
paths.put(pathConfig.getPath(), pathConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
||||||
PathConfig pathConfig = new PathConfig();
|
PathConfig pathConfig = new PathConfig();
|
||||||
|
|
||||||
pathConfig.setId(resourceDescription.getId());
|
pathConfig.setId(resourceDescription.getId());
|
||||||
|
|||||||
+5
@@ -37,4 +37,9 @@ public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
|||||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(KeycloakDeployment deployment) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+30
-16
@@ -25,7 +25,6 @@ import org.keycloak.adapters.KeycloakDeployment;
|
|||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.jose.jwk.JWK;
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
|
||||||
import org.keycloak.util.JWKSUtils;
|
import org.keycloak.util.JWKSUtils;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@@ -48,36 +47,51 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
|
|||||||
@Override
|
@Override
|
||||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||||
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
||||||
|
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
|
||||||
|
int currentTime = Time.currentTime();
|
||||||
|
|
||||||
// Check if key is in cache.
|
// Check if key is in cache.
|
||||||
PublicKey publicKey = currentKeys.get(kid);
|
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
||||||
if (publicKey != null) {
|
if (publicKey != null) {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentTime = Time.currentTime();
|
|
||||||
|
|
||||||
// Check if we are allowed to send request
|
// Check if we are allowed to send request
|
||||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
synchronized (this) {
|
||||||
synchronized (this) {
|
currentTime = Time.currentTime();
|
||||||
currentTime = Time.currentTime();
|
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
sendRequest(deployment);
|
||||||
sendRequest(deployment);
|
lastRequestTime = currentTime;
|
||||||
lastRequestTime = currentTime;
|
} else {
|
||||||
} else {
|
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime);
|
||||||
log.debugf("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return currentKeys.get(kid);
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset(KeycloakDeployment deployment) {
|
||||||
|
synchronized (this) {
|
||||||
|
sendRequest(deployment);
|
||||||
|
lastRequestTime = Time.currentTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
|
||||||
|
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
|
||||||
|
return currentKeys.get(kid);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void sendRequest(KeycloakDeployment deployment) {
|
private void sendRequest(KeycloakDeployment deployment) {
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.tracef("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
|
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName());
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
||||||
@@ -87,7 +101,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
|
|||||||
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debugf("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
|
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update current keys
|
// Update current keys
|
||||||
|
|||||||
+7
@@ -34,4 +34,11 @@ public interface PublicKeyLocator {
|
|||||||
*/
|
*/
|
||||||
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the state of locator (eg. clear the cached keys)
|
||||||
|
*
|
||||||
|
* @param deployment
|
||||||
|
*/
|
||||||
|
void reset(KeycloakDeployment deployment);
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
@@ -69,6 +69,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
assertEquals("email", deployment.getPrincipalAttribute());
|
assertEquals("email", deployment.getPrincipalAttribute());
|
||||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||||
|
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -78,6 +79,7 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
|
|
||||||
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
||||||
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
||||||
|
assertEquals(86400, deployment.getPublicKeyCacheTtl());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -30,5 +30,6 @@
|
|||||||
"token-store": "cookie",
|
"token-store": "cookie",
|
||||||
"principal-attribute": "email",
|
"principal-attribute": "email",
|
||||||
"token-minimum-time-to-live": 10,
|
"token-minimum-time-to-live": 10,
|
||||||
"min-time-between-jwks-requests": 20
|
"min-time-between-jwks-requests": 20,
|
||||||
|
"public-key-cache-ttl": 120
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -86,7 +86,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
|
|
||||||
list.add(addRealm);
|
list.add(addRealm);
|
||||||
@@ -117,7 +117,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
* TODO need to check realm-ref first.
|
* TODO need to check realm-ref first.
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
||||||
//TODO: externalize message
|
//TODO: externalize message
|
||||||
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
|
|||||||
return requestContext.getUriInfo().getRequestUri().toString();
|
return requestContext.getUriInfo().getRequestUri().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRelativePath() {
|
||||||
|
return requestContext.getUriInfo().getPath();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecure() {
|
public boolean isSecure() {
|
||||||
return securityContext.isSecure();
|
return securityContext.isSecure();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Jetty Integration</name>
|
<name>Keycloak Jetty Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -118,12 +118,11 @@
|
|||||||
/**
|
/**
|
||||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||||
*/
|
*/
|
||||||
this.entitlement = function (resourceSeververId) {
|
this.entitlement = function (resourceSeververId, entitlementRequest ) {
|
||||||
this.then = function (onGrant, onDeny, onError) {
|
this.then = function (onGrant, onDeny, onError) {
|
||||||
var request = new XMLHttpRequest();
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
|
||||||
|
|
||||||
request.onreadystatechange = function () {
|
request.onreadystatechange = function () {
|
||||||
if (request.readyState == 4) {
|
if (request.readyState == 4) {
|
||||||
@@ -149,7 +148,19 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.send(null);
|
var erJson = null
|
||||||
|
|
||||||
|
if(entitlementRequest) {
|
||||||
|
request.open('POST', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||||
|
request.setRequestHeader("Content-type", "application/json");
|
||||||
|
erJson = JSON.stringify(entitlementRequest)
|
||||||
|
} else {
|
||||||
|
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||||
|
request.send(erJson);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@
|
|||||||
processCallback(callback, initPromise);
|
processCallback(callback, initPromise);
|
||||||
return;
|
return;
|
||||||
} else if (initOptions) {
|
} else if (initOptions) {
|
||||||
if (initOptions.token || initOptions.refreshToken) {
|
if (initOptions.refreshToken) {
|
||||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||||
|
|
||||||
if (loginIframe.enable) {
|
if (loginIframe.enable) {
|
||||||
@@ -210,11 +210,18 @@
|
|||||||
var nonce = createUUID();
|
var nonce = createUUID();
|
||||||
|
|
||||||
var redirectUri = adapter.redirectUri(options);
|
var redirectUri = adapter.redirectUri(options);
|
||||||
if (options && options.prompt) {
|
|
||||||
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
|
var callbackState = {
|
||||||
|
state: state,
|
||||||
|
nonce: nonce,
|
||||||
|
redirectUri: encodeURIComponent(redirectUri),
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackStorage.add({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
|
if (options && options.prompt) {
|
||||||
|
callbackState.prompt = options.prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbackStorage.add(callbackState);
|
||||||
|
|
||||||
var action = 'auth';
|
var action = 'auth';
|
||||||
if (options && options.action == 'register') {
|
if (options && options.action == 'register') {
|
||||||
@@ -372,7 +379,7 @@
|
|||||||
kc.updateToken = function(minValidity) {
|
kc.updateToken = function(minValidity) {
|
||||||
var promise = createPromise();
|
var promise = createPromise();
|
||||||
|
|
||||||
if (!kc.tokenParsed || !kc.refreshToken) {
|
if (!kc.refreshToken) {
|
||||||
promise.setError();
|
promise.setError();
|
||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
@@ -381,14 +388,10 @@
|
|||||||
|
|
||||||
var exec = function() {
|
var exec = function() {
|
||||||
var refreshToken = false;
|
var refreshToken = false;
|
||||||
if (kc.timeSkew == -1) {
|
if (minValidity == -1) {
|
||||||
console.info('Skew ' + kc.timeSkew);
|
|
||||||
refreshToken = true;
|
|
||||||
console.info('[KEYCLOAK] Refreshing token: time skew not set');
|
|
||||||
} else if (minValidity == -1) {
|
|
||||||
refreshToken = true;
|
refreshToken = true;
|
||||||
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
||||||
} else if (kc.isTokenExpired(minValidity)) {
|
} else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
|
||||||
refreshToken = true;
|
refreshToken = true;
|
||||||
console.info('[KEYCLOAK] Refreshing token: token expired');
|
console.info('[KEYCLOAK] Refreshing token: token expired');
|
||||||
}
|
}
|
||||||
@@ -631,49 +634,6 @@
|
|||||||
kc.tokenTimeoutHandle = null;
|
kc.tokenTimeoutHandle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
|
||||||
kc.token = token;
|
|
||||||
kc.tokenParsed = decodeToken(token);
|
|
||||||
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
|
|
||||||
if (kc.tokenParsed.session_state) {
|
|
||||||
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
|
|
||||||
}
|
|
||||||
kc.sessionId = sessionId;
|
|
||||||
kc.authenticated = true;
|
|
||||||
kc.subject = kc.tokenParsed.sub;
|
|
||||||
kc.realmAccess = kc.tokenParsed.realm_access;
|
|
||||||
kc.resourceAccess = kc.tokenParsed.resource_access;
|
|
||||||
|
|
||||||
if (timeLocal) {
|
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
|
||||||
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
|
||||||
} else {
|
|
||||||
kc.timeSkew = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kc.onTokenExpired) {
|
|
||||||
if (kc.timeSkew == -1) {
|
|
||||||
kc.onTokenExpired();
|
|
||||||
} else {
|
|
||||||
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
|
||||||
if (expiresIn <= 0) {
|
|
||||||
kc.onTokenExpired();
|
|
||||||
} else {
|
|
||||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
delete kc.token;
|
|
||||||
delete kc.tokenParsed;
|
|
||||||
delete kc.subject;
|
|
||||||
delete kc.realmAccess;
|
|
||||||
delete kc.resourceAccess;
|
|
||||||
|
|
||||||
kc.authenticated = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshToken) {
|
if (refreshToken) {
|
||||||
kc.refreshToken = refreshToken;
|
kc.refreshToken = refreshToken;
|
||||||
kc.refreshTokenParsed = decodeToken(refreshToken);
|
kc.refreshTokenParsed = decodeToken(refreshToken);
|
||||||
@@ -689,6 +649,48 @@
|
|||||||
delete kc.idToken;
|
delete kc.idToken;
|
||||||
delete kc.idTokenParsed;
|
delete kc.idTokenParsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
kc.token = token;
|
||||||
|
kc.tokenParsed = decodeToken(token);
|
||||||
|
|
||||||
|
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
|
||||||
|
if (kc.tokenParsed.session_state) {
|
||||||
|
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
|
||||||
|
}
|
||||||
|
kc.sessionId = sessionId;
|
||||||
|
kc.authenticated = true;
|
||||||
|
kc.subject = kc.tokenParsed.sub;
|
||||||
|
kc.realmAccess = kc.tokenParsed.realm_access;
|
||||||
|
kc.resourceAccess = kc.tokenParsed.resource_access;
|
||||||
|
|
||||||
|
if (timeLocal) {
|
||||||
|
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||||
|
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
||||||
|
|
||||||
|
if (kc.onTokenExpired) {
|
||||||
|
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
||||||
|
console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
|
||||||
|
if (expiresIn <= 0) {
|
||||||
|
kc.onTokenExpired();
|
||||||
|
} else {
|
||||||
|
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
kc.updateToken(-1);
|
||||||
|
}
|
||||||
|
} else if (refreshToken) {
|
||||||
|
kc.updateToken(-1);
|
||||||
|
} else {
|
||||||
|
delete kc.token;
|
||||||
|
delete kc.tokenParsed;
|
||||||
|
delete kc.subject;
|
||||||
|
delete kc.realmAccess;
|
||||||
|
delete kc.resourceAccess;
|
||||||
|
|
||||||
|
kc.authenticated = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeToken(str) {
|
function decodeToken(str) {
|
||||||
@@ -747,6 +749,7 @@
|
|||||||
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
|
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
|
||||||
oauth.redirectUri = oauthState.redirectUri;
|
oauth.redirectUri = oauthState.redirectUri;
|
||||||
oauth.storedNonce = oauthState.nonce;
|
oauth.storedNonce = oauthState.nonce;
|
||||||
|
oauth.prompt = oauthState.prompt;
|
||||||
|
|
||||||
if (oauth.fragment) {
|
if (oauth.fragment) {
|
||||||
oauth.newUrl += '#' + oauth.fragment;
|
oauth.newUrl += '#' + oauth.fragment;
|
||||||
@@ -838,14 +841,15 @@
|
|||||||
kc.clearToken();
|
kc.clearToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = loginIframe.callbackList.length - 1; i >= 0; --i) {
|
var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
|
||||||
var promise = loginIframe.callbackList[i];
|
|
||||||
|
for (var i = callbacks.length - 1; i >= 0; --i) {
|
||||||
|
var promise = callbacks[i];
|
||||||
if (event.data == "unchanged") {
|
if (event.data == "unchanged") {
|
||||||
promise.setSuccess();
|
promise.setSuccess();
|
||||||
} else {
|
} else {
|
||||||
promise.setError();
|
promise.setError();
|
||||||
}
|
}
|
||||||
loginIframe.callbackList.splice(i, 1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1041,7 +1045,7 @@
|
|||||||
|
|
||||||
function clearExpired() {
|
function clearExpired() {
|
||||||
var time = new Date().getTime();
|
var time = new Date().getTime();
|
||||||
for (var i = 1; i <= localStorage.length; i++) {
|
for (var i = 0; i < localStorage.length; i++) {
|
||||||
var key = localStorage.key(i);
|
var key = localStorage.key(i);
|
||||||
if (key && key.indexOf('kc-callback-') == 0) {
|
if (key && key.indexOf('kc-callback-') == 0) {
|
||||||
var value = localStorage.getItem(key);
|
var value = localStorage.getItem(key);
|
||||||
@@ -1218,9 +1222,6 @@
|
|||||||
case 'redirect_fragment':
|
case 'redirect_fragment':
|
||||||
oauth.fragment = queryParams[param];
|
oauth.fragment = queryParams[param];
|
||||||
break;
|
break;
|
||||||
case 'prompt':
|
|
||||||
oauth.prompt = queryParams[param];
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
|
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
|
||||||
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
|
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
~ and other contributors as indicated by the @author tags.
|
||||||
|
~
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getCookie(cname)
|
||||||
|
{
|
||||||
|
var name = cname + "=";
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for(var i=0; i<ca.length; i++)
|
||||||
|
{
|
||||||
|
var c = ca[i].trim();
|
||||||
|
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function receiveMessage(event)
|
||||||
|
{
|
||||||
|
if (event.origin !== "ORIGIN") {
|
||||||
|
console.log(event.origin + " does not match built origin");
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
data.loggedIn = false;
|
||||||
|
var cookie = getCookie('KEYCLOAK_SESSION');
|
||||||
|
if (cookie) {
|
||||||
|
data.loggedIn = true;
|
||||||
|
data.session = cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.source.postMessage(JSON.stringify(data),
|
||||||
|
event.origin);
|
||||||
|
}
|
||||||
|
window.addEventListener("message", receiveMessage, false);
|
||||||
|
</script>
|
||||||
@@ -21,25 +21,28 @@
|
|||||||
var init;
|
var init;
|
||||||
|
|
||||||
function checkState(clientId, origin, sessionState, callback) {
|
function checkState(clientId, origin, sessionState, callback) {
|
||||||
if (!init) {
|
var cookie = getCookie();
|
||||||
|
|
||||||
|
if (!cookie) {
|
||||||
|
callback('changed');
|
||||||
|
} else if (!init) {
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
|
|
||||||
var url = location.href + "/init";
|
var url = location.href + "/init";
|
||||||
url += "?client_id=" + encodeURIComponent(clientId);
|
url += "?client_id=" + encodeURIComponent(clientId);
|
||||||
url += "&origin=" + encodeURIComponent(origin);
|
url += "&origin=" + encodeURIComponent(origin);
|
||||||
url += "&session_state=" + encodeURIComponent(sessionState);
|
|
||||||
|
|
||||||
req.open('GET', url, true);
|
req.open('GET', url, true);
|
||||||
|
|
||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState === 4) {
|
||||||
if (req.status == 204) {
|
if (req.status === 204) {
|
||||||
init = {
|
init = {
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
origin: origin
|
origin: origin
|
||||||
}
|
}
|
||||||
callback('unchanged');
|
callback('unchanged');
|
||||||
} else if (req.status = 404) {
|
} else if (req.status === 404) {
|
||||||
callback('changed');
|
callback('changed');
|
||||||
} else {
|
} else {
|
||||||
callback('error');
|
callback('error');
|
||||||
@@ -49,9 +52,8 @@
|
|||||||
|
|
||||||
req.send();
|
req.send();
|
||||||
} else {
|
} else {
|
||||||
if (clientId == init.clientId && origin == init.origin) {
|
if (clientId === init.clientId && origin === init.origin) {
|
||||||
var cookie = getCookie();
|
if (sessionState === cookie) {
|
||||||
if (sessionState == cookie) {
|
|
||||||
callback('unchanged');
|
callback('unchanged');
|
||||||
} else {
|
} else {
|
||||||
callback('changed');
|
callback('changed');
|
||||||
@@ -69,7 +71,7 @@
|
|||||||
for(var i=0; i<ca.length; i++)
|
for(var i=0; i<ca.length; i++)
|
||||||
{
|
{
|
||||||
var c = ca[i].trim();
|
var c = ca[i].trim();
|
||||||
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
if (c.indexOf(name)===0) return c.substring(name.length,c.length);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -93,4 +95,4 @@
|
|||||||
window.addEventListener("message", receiveMessage, false);
|
window.addEventListener("message", receiveMessage, false);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak OIDC Client Adapter Modules</name>
|
<name>Keycloak OIDC Client Adapter Modules</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+5
@@ -218,6 +218,11 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||||||
return servletRequest.getRequestURL().toString();
|
return servletRequest.getRequestURL().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRelativePath() {
|
||||||
|
return servletRequest.getServletPath();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecure() {
|
public boolean isSecure() {
|
||||||
return servletRequest.isSecure();
|
return servletRequest.isSecure();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -215,7 +215,7 @@ public class KeycloakSpringBootConfiguration {
|
|||||||
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||||
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
||||||
|
|
||||||
webAppContext.setHandler(securityHandler);
|
webAppContext.setSecurityHandler(securityHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+12
-2
@@ -30,6 +30,12 @@ import javax.servlet.ServletException;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
|
||||||
|
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a
|
* Provides a Keycloak {@link AuthenticationEntryPoint authentication entry point}. Uses a
|
||||||
@@ -56,12 +62,15 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
|
|||||||
private final RequestMatcher apiRequestMatcher;
|
private final RequestMatcher apiRequestMatcher;
|
||||||
private String loginUri = DEFAULT_LOGIN_URI;
|
private String loginUri = DEFAULT_LOGIN_URI;
|
||||||
private String realm = DEFAULT_REALM;
|
private String realm = DEFAULT_REALM;
|
||||||
|
|
||||||
|
private AdapterDeploymentContext adapterDeploymentContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Keycloak authentication entry point.
|
* Creates a new Keycloak authentication entry point.
|
||||||
*/
|
*/
|
||||||
public KeycloakAuthenticationEntryPoint() {
|
public KeycloakAuthenticationEntryPoint(AdapterDeploymentContext adapterDeploymentContext) {
|
||||||
this(DEFAULT_API_REQUEST_MATCHER);
|
this(DEFAULT_API_REQUEST_MATCHER);
|
||||||
|
this.adapterDeploymentContext = adapterDeploymentContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,7 +88,8 @@ public class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPoin
|
|||||||
@Override
|
@Override
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
if (apiRequestMatcher.matches(request)) {
|
HttpFacade facade = new SimpleHttpFacade(request, response);
|
||||||
|
if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
|
||||||
commenceUnauthorizedResponse(request, response);
|
commenceUnauthorizedResponse(request, response);
|
||||||
} else {
|
} else {
|
||||||
commenceLoginRedirect(request, response);
|
commenceLoginRedirect(request, response);
|
||||||
|
|||||||
+2
-2
@@ -71,8 +71,8 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
|
|||||||
return factoryBean.getObject();
|
return factoryBean.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthenticationEntryPoint authenticationEntryPoint() {
|
protected AuthenticationEntryPoint authenticationEntryPoint() throws Exception {
|
||||||
return new KeycloakAuthenticationEntryPoint();
|
return new KeycloakAuthenticationEntryPoint(adapterDeploymentContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
|
protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
|
||||||
|
|||||||
+5
@@ -70,6 +70,11 @@ class WrappedHttpServletRequest implements Request {
|
|||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRelativePath() {
|
||||||
|
return request.getServletPath();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecure() {
|
public boolean isSecure() {
|
||||||
return request.isSecure();
|
return request.isSecure();
|
||||||
|
|||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.springsecurity.filter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
|
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||||
|
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||||
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.web.filter.GenericFilterBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes a Keycloak adapter {@link AuthenticatedActionsHandler} as a Spring Security filter.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class KeycloakAuthenticatedActionsFilter extends GenericFilterBean implements ApplicationContextAware {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticatedActionsFilter.class);
|
||||||
|
|
||||||
|
private final NodesRegistrationManagement management = new NodesRegistrationManagement();
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
private AdapterDeploymentContext deploymentContext;
|
||||||
|
|
||||||
|
|
||||||
|
public KeycloakAuthenticatedActionsFilter() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initFilterBean() throws ServletException {
|
||||||
|
deploymentContext = applicationContext.getBean(AdapterDeploymentContext.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
log.debug("Unregistering deployment");
|
||||||
|
management.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
|
||||||
|
AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deploymentContext.resolveDeployment(facade), (OIDCHttpFacade)facade);
|
||||||
|
boolean handled = handler.handledRequest();
|
||||||
|
if (handled) {
|
||||||
|
log.debug("Authenticated filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
|
||||||
|
} else {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -142,6 +142,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
|||||||
if (AuthOutcome.FAILED.equals(result)) {
|
if (AuthOutcome.FAILED.equals(result)) {
|
||||||
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
||||||
|
|||||||
+22
-1
@@ -26,6 +26,15 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keycloak authentication entry point tests.
|
* Keycloak authentication entry point tests.
|
||||||
@@ -35,12 +44,24 @@ public class KeycloakAuthenticationEntryPointTest {
|
|||||||
private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
|
private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
|
||||||
private MockHttpServletRequest request;
|
private MockHttpServletRequest request;
|
||||||
private MockHttpServletResponse response;
|
private MockHttpServletResponse response;
|
||||||
|
@Mock
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private AdapterDeploymentContext adapterDeploymentContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private KeycloakDeployment keycloakDeployment;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
|
MockitoAnnotations.initMocks(this);
|
||||||
|
authenticationEntryPoint = new KeycloakAuthenticationEntryPoint(adapterDeploymentContext);
|
||||||
request = new MockHttpServletRequest();
|
request = new MockHttpServletRequest();
|
||||||
response = new MockHttpServletResponse();
|
response = new MockHttpServletResponse();
|
||||||
|
when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
|
||||||
|
when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
|
||||||
|
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+1
@@ -154,6 +154,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
|||||||
when(keycloakDeployment.getResourceName()).thenReturn("resource-name");
|
when(keycloakDeployment.getResourceName()).thenReturn("resource-name");
|
||||||
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
||||||
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
||||||
|
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
||||||
filter.attemptAuthentication(request, response);
|
filter.attemptAuthentication(request, response);
|
||||||
|
|
||||||
verify(response).setStatus(302);
|
verify(response).setStatus(302);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Tomcat Integration</name>
|
<name>Keycloak Tomcat Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+3
-1
@@ -127,7 +127,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||||||
InputStream configInputStream = getConfigInputStream(context);
|
InputStream configInputStream = getConfigInputStream(context);
|
||||||
KeycloakDeployment kd;
|
KeycloakDeployment kd;
|
||||||
if (configInputStream == null) {
|
if (configInputStream == null) {
|
||||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
log.warning("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||||
kd = new KeycloakDeployment();
|
kd = new KeycloakDeployment();
|
||||||
} else {
|
} else {
|
||||||
kd = KeycloakDeploymentBuilder.build(configInputStream);
|
kd = KeycloakDeploymentBuilder.build(configInputStream);
|
||||||
@@ -196,6 +196,8 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||||||
CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
|
CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||||
if (deployment == null || !deployment.isConfigured()) {
|
if (deployment == null || !deployment.isConfigured()) {
|
||||||
|
//needed for the EAP6/AS7 adapter relying on the tomcat core adapter
|
||||||
|
facade.getResponse().sendError(401);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
|
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+11
@@ -39,9 +39,20 @@ import java.util.List;
|
|||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
|
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called by Tomcat < 8.5.5
|
||||||
|
*/
|
||||||
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
|
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
|
||||||
return authenticateInternal(request, response, request.getContext().getLoginConfig());
|
return authenticateInternal(request, response, request.getContext().getLoginConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called by Tomcat >= 8.5.5
|
||||||
|
*/
|
||||||
|
protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
|
||||||
|
return this.authenticate(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
|
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -92,7 +92,7 @@ public abstract class AbstractUndertowKeycloakAuthMech implements Authentication
|
|||||||
UndertowHttpFacade facade = createFacade(exchange);
|
UndertowHttpFacade facade = createFacade(exchange);
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||||
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
|
KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
|
||||||
if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
|
if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
|
||||||
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
|
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
|
||||||
}
|
}
|
||||||
AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
|
AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
|
||||||
|
|||||||
+9
-4
@@ -173,10 +173,15 @@ public class KeycloakServletExtension implements ServletExtension {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
|
ServletSessionConfig cookieConfig = deploymentInfo.getServletSessionConfig();
|
||||||
ServletSessionConfig cookieConfig = new ServletSessionConfig();
|
if (cookieConfig == null) {
|
||||||
cookieConfig.setPath(deploymentInfo.getContextPath());
|
cookieConfig = new ServletSessionConfig();
|
||||||
deploymentInfo.setServletSessionConfig(cookieConfig);
|
}
|
||||||
|
if (cookieConfig.getPath() == null) {
|
||||||
|
log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
|
||||||
|
cookieConfig.setPath(deploymentInfo.getContextPath());
|
||||||
|
deploymentInfo.setServletSessionConfig(cookieConfig);
|
||||||
|
}
|
||||||
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
ChangeSessionId.turnOffChangeSessionIdOnLogin(deploymentInfo);
|
||||||
deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() {
|
deploymentInfo.addListener(new ListenerInfo(UndertowNodesRegistrationManagementWrapper.class, new InstanceFactory<UndertowNodesRegistrationManagementWrapper>() {
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak WildFly Integration</name>
|
<name>Keycloak WildFly Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -87,7 +87,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
|
|
||||||
list.add(addRealm);
|
list.add(addRealm);
|
||||||
@@ -118,7 +118,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
|
|||||||
* TODO need to check realm-ref first.
|
* TODO need to check realm-ref first.
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
|
||||||
//TODO: externalize the message
|
//TODO: externalize the message
|
||||||
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
|
|||||||
|
|
||||||
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
|
||||||
//TODO: externalize message
|
//TODO: externalize message
|
||||||
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
|
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -20,10 +20,10 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Integration</name>
|
<name>Keycloak Adapters</name>
|
||||||
<description/>
|
<description/>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak SAML EAP Integration</name>
|
<name>Keycloak SAML EAP Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -105,7 +105,7 @@ public class SamlPrincipal implements Serializable, Principal {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public List<String> getFriendlyAttributes(String friendlyName) {
|
public List<String> getFriendlyAttributes(String friendlyName) {
|
||||||
List<String> list = friendlyAttributes.get(name);
|
List<String> list = friendlyAttributes.get(friendlyName);
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
return Collections.unmodifiableList(list);
|
return Collections.unmodifiableList(list);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Whitespace-only changes.
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>2.3.0-SNAPSHOT</version>
|
<version>2.5.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<timestamp>${maven.build.timestamp}</timestamp>
|
<timestamp>${maven.build.timestamp}</timestamp>
|
||||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
@@ -70,6 +71,11 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.cloned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options relevant for configuring http client that can be used by adapter.
|
||||||
|
*
|
||||||
|
* NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
|
||||||
|
*
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public interface AdapterHttpClientConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns truststore filename.
|
||||||
|
*/
|
||||||
|
public String getTruststore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns truststore password.
|
||||||
|
*/
|
||||||
|
public String getTruststorePassword();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns keystore with client keys.
|
||||||
|
*/
|
||||||
|
public String getClientKeystore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns keystore password.
|
||||||
|
*/
|
||||||
|
public String getClientKeystorePassword();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns boolean flag whether any hostname verification is done on the server's
|
||||||
|
* certificate, {@code true} means that verification is not done.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isAllowAnyHostname();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns boolean flag whether any trust management and hostname verification is done.
|
||||||
|
* <p>
|
||||||
|
* <i>NOTE</i> Disabling trust manager is a security hole, so only set this option
|
||||||
|
* if you cannot or do not want to verify the identity of the
|
||||||
|
* host you are communicating with.
|
||||||
|
*/
|
||||||
|
public boolean isDisableTrustManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns size of connection pool.
|
||||||
|
*/
|
||||||
|
public int getConnectionPoolSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns URL of HTTP proxy.
|
||||||
|
*/
|
||||||
|
public String getProxyUrl();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.cloned;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
|
||||||
|
*/
|
||||||
|
public class HttpAdapterUtils {
|
||||||
|
|
||||||
|
public static MultivaluedHashMap<String, KeyInfo> downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
|
||||||
|
try {
|
||||||
|
HttpGet httpRequest = new HttpGet(descriptorUrl);
|
||||||
|
HttpResponse response = client.execute(httpRequest);
|
||||||
|
int status = response.getStatusLine().getStatusCode();
|
||||||
|
if (status != HttpStatus.SC_OK) {
|
||||||
|
EntityUtils.consumeQuietly(response.getEntity());
|
||||||
|
throw new HttpClientAdapterException("Unexpected status = " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
if (entity == null) {
|
||||||
|
throw new HttpClientAdapterException("There was no entity.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MultivaluedHashMap<String, KeyInfo> res;
|
||||||
|
try (InputStream is = entity.getContent()) {
|
||||||
|
res = extractKeysFromSamlDescriptor(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityUtils.consumeQuietly(entity);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (IOException | ParsingException e) {
|
||||||
|
throw new HttpClientAdapterException("IO error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses SAML descriptor and extracts keys from it.
|
||||||
|
* @param xmlStream
|
||||||
|
* @return List of KeyInfo objects containing keys from the descriptor.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static MultivaluedHashMap<String, KeyInfo> extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
|
||||||
|
Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
|
||||||
|
return (MultivaluedHashMap<String, KeyInfo>) res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+9
-8
@@ -15,17 +15,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.adapters.cloned;
|
||||||
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper interface used just because RealmManager is in keycloak-services and not accessible for ImportUtils
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public interface RealmImporter {
|
public class HttpClientAdapterException extends Exception {
|
||||||
|
|
||||||
RealmModel importRealm(RealmRepresentation rep);
|
public HttpClientAdapterException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientAdapterException(String message, Throwable t) {
|
||||||
|
super(message, t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.cloned;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.client.CookieStore;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
|
import org.apache.http.conn.params.ConnRoutePNames;
|
||||||
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
import org.apache.http.conn.scheme.Scheme;
|
||||||
|
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||||
|
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||||
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||||
|
import org.apache.http.cookie.Cookie;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.apache.http.impl.conn.SingleClientConnManager;
|
||||||
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
|
import org.apache.http.params.BasicHttpParams;
|
||||||
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
|
import org.keycloak.common.util.EnvUtil;
|
||||||
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for creating HttpClients. Allows SSL configuration.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class HttpClientBuilder {
|
||||||
|
public static 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
private static class PassthroughTrustManager implements X509TrustManager {
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain,
|
||||||
|
String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain,
|
||||||
|
String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KeyStore truststore;
|
||||||
|
protected KeyStore clientKeyStore;
|
||||||
|
protected String clientPrivateKeyPassword;
|
||||||
|
protected boolean disableTrustManager;
|
||||||
|
protected boolean disableCookieCache = true;
|
||||||
|
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
|
||||||
|
protected SSLContext sslContext;
|
||||||
|
protected int connectionPoolSize = 100;
|
||||||
|
protected int maxPooledPerRoute = 0;
|
||||||
|
protected long connectionTTL = -1;
|
||||||
|
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
|
||||||
|
protected HostnameVerifier verifier = null;
|
||||||
|
protected long socketTimeout = -1;
|
||||||
|
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
|
protected long establishConnectionTimeout = -1;
|
||||||
|
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
|
protected HttpHost proxyHost;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket inactivity timeout
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
|
||||||
|
this.socketTimeout = timeout;
|
||||||
|
this.socketTimeoutUnits = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When trying to make an initial socket connection, what is the timeout?
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
|
||||||
|
this.establishConnectionTimeout = timeout;
|
||||||
|
this.establishConnectionTimeoutUnits = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
|
||||||
|
this.connectionTTL = ttl;
|
||||||
|
this.connectionTTLUnit = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
|
||||||
|
this.maxPooledPerRoute = maxPooledPerRoute;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
|
||||||
|
this.connectionPoolSize = connectionPoolSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
|
||||||
|
* hole, so only set this option if you cannot or do not want to verify the identity of the
|
||||||
|
* host you are communicating with.
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder disableTrustManager() {
|
||||||
|
this.disableTrustManager = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder disableCookieCache() {
|
||||||
|
this.disableCookieCache = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL policy used to verify hostnames
|
||||||
|
*
|
||||||
|
* @param policy
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HttpClientBuilder sslContext(SSLContext sslContext) {
|
||||||
|
this.sslContext = sslContext;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder trustStore(KeyStore truststore) {
|
||||||
|
this.truststore = truststore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
|
||||||
|
this.clientKeyStore = keyStore;
|
||||||
|
this.clientPrivateKeyPassword = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
|
||||||
|
this.clientKeyStore = keyStore;
|
||||||
|
this.clientPrivateKeyPassword = new String(password);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class VerifierWrapper implements X509HostnameVerifier {
|
||||||
|
protected HostnameVerifier verifier;
|
||||||
|
|
||||||
|
VerifierWrapper(HostnameVerifier verifier) {
|
||||||
|
this.verifier = verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, SSLSocket ssl) throws IOException {
|
||||||
|
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, X509Certificate cert) throws SSLException {
|
||||||
|
throw new SSLException("This verification path not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
|
||||||
|
throw new SSLException("This verification path not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(String s, SSLSession sslSession) {
|
||||||
|
return verifier.verify(s, sslSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient build() {
|
||||||
|
X509HostnameVerifier verifier = null;
|
||||||
|
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
|
||||||
|
else {
|
||||||
|
switch (policy) {
|
||||||
|
case ANY:
|
||||||
|
verifier = new AllowAllHostnameVerifier();
|
||||||
|
break;
|
||||||
|
case WILDCARD:
|
||||||
|
verifier = new BrowserCompatHostnameVerifier();
|
||||||
|
break;
|
||||||
|
case STRICT:
|
||||||
|
verifier = new StrictHostnameVerifier();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SSLSocketFactory sslsf = null;
|
||||||
|
SSLContext theContext = sslContext;
|
||||||
|
if (disableTrustManager) {
|
||||||
|
theContext = SSLContext.getInstance("SSL");
|
||||||
|
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
||||||
|
new SecureRandom());
|
||||||
|
verifier = new AllowAllHostnameVerifier();
|
||||||
|
sslsf = new SniSSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (theContext != null) {
|
||||||
|
sslsf = new SniSSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (clientKeyStore != null || truststore != null) {
|
||||||
|
sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
|
||||||
|
} else {
|
||||||
|
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
|
||||||
|
tlsContext.init(null, null, null);
|
||||||
|
sslsf = new SniSSLSocketFactory(tlsContext, verifier);
|
||||||
|
}
|
||||||
|
SchemeRegistry registry = new SchemeRegistry();
|
||||||
|
registry.register(
|
||||||
|
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
Scheme httpsScheme = new Scheme("https", 443, sslsf);
|
||||||
|
registry.register(httpsScheme);
|
||||||
|
ClientConnectionManager cm = null;
|
||||||
|
if (connectionPoolSize > 0) {
|
||||||
|
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
|
||||||
|
tcm.setMaxTotal(connectionPoolSize);
|
||||||
|
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
|
||||||
|
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
|
||||||
|
cm = tcm;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cm = new SingleClientConnManager(registry);
|
||||||
|
}
|
||||||
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
|
|
||||||
|
if (proxyHost != null) {
|
||||||
|
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketTimeout > -1) {
|
||||||
|
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||||
|
|
||||||
|
}
|
||||||
|
if (establishConnectionTimeout > -1) {
|
||||||
|
HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||||
|
}
|
||||||
|
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
||||||
|
|
||||||
|
if (disableCookieCache) {
|
||||||
|
client.setCookieStore(new CookieStore() {
|
||||||
|
@Override
|
||||||
|
public void addCookie(Cookie cookie) {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Cookie> getCookies() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clearExpired(Date date) {
|
||||||
|
return false; //To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
//To change body of implemented methods use File | Settings | File Templates.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
|
||||||
|
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
|
||||||
|
|
||||||
|
String truststorePath = adapterConfig.getTruststore();
|
||||||
|
if (truststorePath != null) {
|
||||||
|
truststorePath = EnvUtil.replace(truststorePath);
|
||||||
|
String truststorePassword = adapterConfig.getTruststorePassword();
|
||||||
|
try {
|
||||||
|
this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load truststore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String clientKeystore = adapterConfig.getClientKeystore();
|
||||||
|
if (clientKeystore != null) {
|
||||||
|
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||||
|
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
|
||||||
|
try {
|
||||||
|
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
||||||
|
keyStore(clientCertKeystore, clientKeystorePassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load keystore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int size = 10;
|
||||||
|
if (adapterConfig.getConnectionPoolSize() > 0)
|
||||||
|
size = adapterConfig.getConnectionPoolSize();
|
||||||
|
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||||
|
if (adapterConfig.isAllowAnyHostname())
|
||||||
|
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
|
||||||
|
connectionPoolSize(size);
|
||||||
|
hostnameVerification(policy);
|
||||||
|
if (adapterConfig.isDisableTrustManager()) {
|
||||||
|
disableTrustManager();
|
||||||
|
} else {
|
||||||
|
trustStore(truststore);
|
||||||
|
}
|
||||||
|
|
||||||
|
configureProxyForAuthServerIfProvided(adapterConfig);
|
||||||
|
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a the proxy to use for auth-server requests if provided.
|
||||||
|
* <p>
|
||||||
|
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
|
||||||
|
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param adapterConfig
|
||||||
|
*/
|
||||||
|
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
|
||||||
|
|
||||||
|
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
URI uri = URI.create(adapterConfig.getProxyUrl());
|
||||||
|
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.cloned;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
|
import org.apache.http.conn.scheme.HostNameResolver;
|
||||||
|
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||||
|
import org.apache.http.conn.ssl.TrustStrategy;
|
||||||
|
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||||
|
import org.apache.http.protocol.HttpContext;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.AccessController;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.UnrecoverableKeyException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Originally copied from <b>keycloak-adapter-core</b> project.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
|
||||||
|
*/
|
||||||
|
public class SniSSLSocketFactory extends SSLSocketFactory {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(SniSSLSocketFactory.class.getName());
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(keystore, keystorePassword, truststore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(keystore, keystorePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(truststore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(trustStrategy, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
|
||||||
|
super(trustStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(SSLContext sslContext) {
|
||||||
|
super(sslContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
|
||||||
|
super(sslContext, nameResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
|
||||||
|
super(sslContext, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
|
||||||
|
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
|
||||||
|
super(socketfactory, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
|
||||||
|
super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
|
||||||
|
return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
|
||||||
|
return super.createLayeredSocket(applySNI(socket, target), target, port, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Socket applySNI(final Socket socket, String hostname) {
|
||||||
|
if (socket instanceof SSLSocket) {
|
||||||
|
try {
|
||||||
|
Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
|
||||||
|
@Override
|
||||||
|
public Method run() throws NoSuchMethodException {
|
||||||
|
return socket.getClass().getMethod("setHost", String.class);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setHostMethod.invoke(socket, hostname);
|
||||||
|
LOG.log(Level.FINEST, "Applied SNI to socket for host {0}", hostname);
|
||||||
|
} catch (PrivilegedActionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -79,7 +79,9 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
|
|||||||
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.signWith(keypair);
|
binding.signWith(null, keypair);
|
||||||
|
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
|
||||||
|
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
|
||||||
binding.signDocument();
|
binding.signDocument();
|
||||||
}
|
}
|
||||||
return binding;
|
return binding;
|
||||||
|
|||||||
+51
-5
@@ -23,7 +23,14 @@ import org.keycloak.saml.SignatureAlgorithm;
|
|||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
|
||||||
|
import org.keycloak.rotation.CompositeKeyLocator;
|
||||||
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
|
import org.keycloak.rotation.KeyLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -179,10 +186,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
|||||||
|
|
||||||
public static class DefaultIDP implements IDP {
|
public static class DefaultIDP implements IDP {
|
||||||
|
|
||||||
|
private static final int DEFAULT_CACHE_TTL = 24 * 60 * 60;
|
||||||
|
|
||||||
private String entityID;
|
private String entityID;
|
||||||
private PublicKey signatureValidationKey;
|
private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
|
||||||
private SingleSignOnService singleSignOnService;
|
private SingleSignOnService singleSignOnService;
|
||||||
private SingleLogoutService singleLogoutService;
|
private SingleLogoutService singleLogoutService;
|
||||||
|
private final List<PublicKey> signatureValidationKeys = new LinkedList<>();
|
||||||
|
private int minTimeBetweenDescriptorRequests;
|
||||||
|
private HttpClient client;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getEntityID() {
|
public String getEntityID() {
|
||||||
@@ -200,16 +212,25 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PublicKey getSignatureValidationKey() {
|
public KeyLocator getSignatureValidationKeyLocator() {
|
||||||
return signatureValidationKey;
|
return this.signatureValidationKeyLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinTimeBetweenDescriptorRequests() {
|
||||||
|
return minTimeBetweenDescriptorRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinTimeBetweenDescriptorRequests(int minTimeBetweenDescriptorRequests) {
|
||||||
|
this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEntityID(String entityID) {
|
public void setEntityID(String entityID) {
|
||||||
this.entityID = entityID;
|
this.entityID = entityID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSignatureValidationKey(PublicKey signatureValidationKey) {
|
public void addSignatureValidationKey(PublicKey signatureValidationKey) {
|
||||||
this.signatureValidationKey = signatureValidationKey;
|
this.signatureValidationKeys.add(signatureValidationKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
|
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
|
||||||
@@ -219,6 +240,31 @@ public class DefaultSamlDeployment implements SamlDeployment {
|
|||||||
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
|
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
|
||||||
this.singleLogoutService = singleLogoutService;
|
this.singleLogoutService = singleLogoutService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refreshKeyLocatorConfiguration() {
|
||||||
|
this.signatureValidationKeyLocator.clear();
|
||||||
|
|
||||||
|
// When key is set, use that (and only that), otherwise configure dynamic key locator
|
||||||
|
if (! this.signatureValidationKeys.isEmpty()) {
|
||||||
|
this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
|
||||||
|
} else if (this.singleSignOnService != null) {
|
||||||
|
String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
|
||||||
|
HttpClient httpClient = getClient();
|
||||||
|
SamlDescriptorPublicKeyLocator samlDescriptorPublicKeyLocator =
|
||||||
|
new SamlDescriptorPublicKeyLocator(
|
||||||
|
samlDescriptorUrl, this.minTimeBetweenDescriptorRequests, DEFAULT_CACHE_TTL, httpClient);
|
||||||
|
this.signatureValidationKeyLocator.add(samlDescriptorPublicKeyLocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClient getClient() {
|
||||||
|
return this.client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(HttpClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IDP idp;
|
private IDP idp;
|
||||||
|
|||||||
@@ -22,14 +22,18 @@ import org.keycloak.saml.SignatureAlgorithm;
|
|||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.keycloak.rotation.KeyLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Represents SAML deployment configuration.
|
||||||
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface SamlDeployment {
|
public interface SamlDeployment {
|
||||||
|
|
||||||
enum Binding {
|
enum Binding {
|
||||||
POST,
|
POST,
|
||||||
REDIRECT;
|
REDIRECT;
|
||||||
@@ -41,20 +45,68 @@ public interface SamlDeployment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface IDP {
|
public interface IDP {
|
||||||
|
/**
|
||||||
|
* Returns entity identifier of this IdP.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
String getEntityID();
|
String getEntityID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Single sign on service configuration for this IdP.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
SingleSignOnService getSingleSignOnService();
|
SingleSignOnService getSingleSignOnService();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Single logout service configuration for this IdP.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
SingleLogoutService getSingleLogoutService();
|
SingleLogoutService getSingleLogoutService();
|
||||||
PublicKey getSignatureValidationKey();
|
|
||||||
|
/**
|
||||||
|
* Returns {@link KeyLocator} looking up public keys used for validation of IdP signatures.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
|
KeyLocator getSignatureValidationKeyLocator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns minimum time (in seconds) between issuing requests to IdP SAML descriptor.
|
||||||
|
* Used e.g. by {@link KeyLocator} looking up public keys for validation of IdP signatures
|
||||||
|
* to prevent too frequent requests.
|
||||||
|
*
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
|
int getMinTimeBetweenDescriptorRequests();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@link HttpClient} instance that will be used for http communication with this IdP.
|
||||||
|
* @return see description
|
||||||
|
*/
|
||||||
|
HttpClient getClient();
|
||||||
|
|
||||||
public interface SingleSignOnService {
|
public interface SingleSignOnService {
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the requests to IdP need to be signed by SP key.
|
||||||
|
* @return see dscription
|
||||||
|
*/
|
||||||
boolean signRequest();
|
boolean signRequest();
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the complete response message from IdP should
|
||||||
|
* be checked for valid signature.
|
||||||
|
* @return see dscription
|
||||||
|
*/
|
||||||
boolean validateResponseSignature();
|
boolean validateResponseSignature();
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if individual assertions in response from IdP should
|
||||||
|
* be checked for valid signature.
|
||||||
|
* @return see dscription
|
||||||
|
*/
|
||||||
boolean validateAssertionSignature();
|
boolean validateAssertionSignature();
|
||||||
Binding getRequestBinding();
|
Binding getRequestBinding();
|
||||||
Binding getResponseBinding();
|
Binding getResponseBinding();
|
||||||
String getRequestBindingUrl();
|
String getRequestBindingUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SingleLogoutService {
|
public interface SingleLogoutService {
|
||||||
boolean validateRequestSignature();
|
boolean validateRequestSignature();
|
||||||
boolean validateResponseSignature();
|
boolean validateResponseSignature();
|
||||||
@@ -67,10 +119,19 @@ public interface SamlDeployment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Identity Provider configuration for this SAML deployment.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
public IDP getIDP();
|
public IDP getIDP();
|
||||||
|
|
||||||
public boolean isConfigured();
|
public boolean isConfigured();
|
||||||
SslRequired getSslRequired();
|
SslRequired getSslRequired();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns entity identifier of this SP.
|
||||||
|
* @return see description.
|
||||||
|
*/
|
||||||
String getEntityID();
|
String getEntityID();
|
||||||
String getNameIDPolicyFormat();
|
String getNameIDPolicyFormat();
|
||||||
boolean isForceAuthentication();
|
boolean isForceAuthentication();
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class SamlUtil {
|
|||||||
httpFacade.getResponse().setHeader("Content-Type", "text/html");
|
httpFacade.getResponse().setHeader("Content-Type", "text/html");
|
||||||
httpFacade.getResponse().setHeader("Pragma", "no-cache");
|
httpFacade.getResponse().setHeader("Pragma", "no-cache");
|
||||||
httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
|
httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
|
||||||
httpFacade.getResponse().getOutputStream().write(html.getBytes());
|
httpFacade.getResponse().getOutputStream().write(html.getBytes(GeneralConstants.SAML_CHARSET));
|
||||||
httpFacade.getResponse().end();
|
httpFacade.getResponse().end();
|
||||||
} else {
|
} else {
|
||||||
String uri = asRequest ? binding.redirectBinding(document).requestURI(actionUrl).toString() : binding.redirectBinding(document).responseURI(actionUrl).toString();
|
String uri = asRequest ? binding.redirectBinding(document).requestURI(actionUrl).toString() : binding.redirectBinding(document).responseURI(actionUrl).toString();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.keycloak.adapters.saml.config;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.keycloak.adapters.cloned.AdapterHttpClientConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -157,12 +158,97 @@ public class IDP implements Serializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class HttpClientConfig implements AdapterHttpClientConfig {
|
||||||
|
|
||||||
|
private String truststore;
|
||||||
|
private String truststorePassword;
|
||||||
|
private String clientKeystore;
|
||||||
|
private String clientKeystorePassword;
|
||||||
|
private boolean allowAnyHostname;
|
||||||
|
private boolean disableTrustManager;
|
||||||
|
private int connectionPoolSize;
|
||||||
|
private String proxyUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTruststore() {
|
||||||
|
return truststore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTruststore(String truststore) {
|
||||||
|
this.truststore = truststore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTruststorePassword() {
|
||||||
|
return truststorePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTruststorePassword(String truststorePassword) {
|
||||||
|
this.truststorePassword = truststorePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientKeystore() {
|
||||||
|
return clientKeystore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientKeystore(String clientKeystore) {
|
||||||
|
this.clientKeystore = clientKeystore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClientKeystorePassword() {
|
||||||
|
return clientKeystorePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientKeystorePassword(String clientKeystorePassword) {
|
||||||
|
this.clientKeystorePassword = clientKeystorePassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowAnyHostname() {
|
||||||
|
return allowAnyHostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowAnyHostname(boolean allowAnyHostname) {
|
||||||
|
this.allowAnyHostname = allowAnyHostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisableTrustManager() {
|
||||||
|
return disableTrustManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisableTrustManager(boolean disableTrustManager) {
|
||||||
|
this.disableTrustManager = disableTrustManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getConnectionPoolSize() {
|
||||||
|
return connectionPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionPoolSize(int connectionPoolSize) {
|
||||||
|
this.connectionPoolSize = connectionPoolSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProxyUrl() {
|
||||||
|
return proxyUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProxyUrl(String proxyUrl) {
|
||||||
|
this.proxyUrl = proxyUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String entityID;
|
private String entityID;
|
||||||
private String signatureAlgorithm;
|
private String signatureAlgorithm;
|
||||||
private String signatureCanonicalizationMethod;
|
private String signatureCanonicalizationMethod;
|
||||||
private SingleSignOnService singleSignOnService;
|
private SingleSignOnService singleSignOnService;
|
||||||
private SingleLogoutService singleLogoutService;
|
private SingleLogoutService singleLogoutService;
|
||||||
private List<Key> keys;
|
private List<Key> keys;
|
||||||
|
private AdapterHttpClientConfig httpClientConfig = new HttpClientConfig();
|
||||||
|
|
||||||
public String getEntityID() {
|
public String getEntityID() {
|
||||||
return entityID;
|
return entityID;
|
||||||
@@ -212,4 +298,12 @@ public class IDP implements Serializable {
|
|||||||
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
|
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AdapterHttpClientConfig getHttpClientConfig() {
|
||||||
|
return httpClientConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHttpClientConfig(AdapterHttpClientConfig httpClientConfig) {
|
||||||
|
this.httpClientConfig = httpClientConfig;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+11
@@ -72,4 +72,15 @@ public class ConfigXmlConstants {
|
|||||||
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
|
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
|
||||||
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
|
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
|
||||||
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
|
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
|
||||||
|
|
||||||
|
public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
|
||||||
|
public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
|
||||||
|
public static final String CLIENT_KEYSTORE_ATTR = "clientKeystore";
|
||||||
|
public static final String CLIENT_KEYSTORE_PASSWORD_ATTR = "clientKeystorePassword";
|
||||||
|
public static final String CONNECTION_POOL_SIZE_ATTR = "connectionPoolSize";
|
||||||
|
public static final String DISABLE_TRUST_MANAGER_ATTR = "disableTrustManager";
|
||||||
|
public static final String PROXY_URL_ATTR = "proxyUrl";
|
||||||
|
public static final String TRUSTSTORE_ATTR = "truststore";
|
||||||
|
public static final String TRUSTSTORE_PASSWORD_ATTR = "truststorePassword";
|
||||||
|
|
||||||
}
|
}
|
||||||
+26
-21
@@ -40,6 +40,7 @@ import java.security.PublicKey;
|
|||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.keycloak.adapters.cloned.HttpClientBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -178,35 +179,39 @@ public class DeploymentBuilder {
|
|||||||
if (sp.getIdp().getKeys() != null) {
|
if (sp.getIdp().getKeys() != null) {
|
||||||
for (Key key : sp.getIdp().getKeys()) {
|
for (Key key : sp.getIdp().getKeys()) {
|
||||||
if (key.isSigning()) {
|
if (key.isSigning()) {
|
||||||
if (key.getKeystore() != null) {
|
processSigningKey(idp, key, resourceLoader);
|
||||||
KeyStore keyStore = loadKeystore(resourceLoader, key);
|
|
||||||
Certificate cert = null;
|
|
||||||
try {
|
|
||||||
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
|
|
||||||
} catch (KeyStoreException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
idp.setSignatureValidationKey(cert.getPublicKey());
|
|
||||||
} else {
|
|
||||||
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
|
|
||||||
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
PublicKey publicKey = getPublicKeyFromPem(key);
|
|
||||||
idp.setSignatureValidationKey(publicKey);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idp.setClient(new HttpClientBuilder().build(sp.getIdp().getHttpClientConfig()));
|
||||||
|
idp.refreshKeyLocatorConfiguration();
|
||||||
|
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
|
private void processSigningKey(DefaultSamlDeployment.DefaultIDP idp, Key key, ResourceLoader resourceLoader) throws RuntimeException {
|
||||||
|
PublicKey publicKey;
|
||||||
|
if (key.getKeystore() != null) {
|
||||||
|
KeyStore keyStore = loadKeystore(resourceLoader, key);
|
||||||
|
Certificate cert = null;
|
||||||
|
try {
|
||||||
|
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
publicKey = cert.getPublicKey();
|
||||||
|
} else {
|
||||||
|
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
|
||||||
|
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
|
||||||
|
}
|
||||||
|
publicKey = getPublicKeyFromPem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
idp.addSignatureValidationKey(publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static PublicKey getPublicKeyFromPem(Key key) {
|
||||||
PublicKey publicKey;
|
PublicKey publicKey;
|
||||||
if (key.getPublicKeyPem() != null) {
|
if (key.getPublicKeyPem() != null) {
|
||||||
publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
|
publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
|
||||||
|
|||||||
+60
-18
@@ -29,6 +29,10 @@ import javax.xml.stream.events.EndElement;
|
|||||||
import javax.xml.stream.events.StartElement;
|
import javax.xml.stream.events.StartElement;
|
||||||
import javax.xml.stream.events.XMLEvent;
|
import javax.xml.stream.events.XMLEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.keycloak.adapters.saml.config.IDP.HttpClientConfig;
|
||||||
|
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getAttributeValue;
|
||||||
|
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getBooleanAttributeValue;
|
||||||
|
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getIntegerAttributeValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -41,16 +45,16 @@ public class IDPXmlParser extends AbstractParser {
|
|||||||
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
|
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
|
||||||
StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
|
StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
|
||||||
IDP idp = new IDP();
|
IDP idp = new IDP();
|
||||||
String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
|
String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
|
||||||
if (entityID == null) {
|
if (entityID == null) {
|
||||||
throw new ParsingException("entityID must be set on IDP");
|
throw new ParsingException("entityID must be set on IDP");
|
||||||
|
|
||||||
}
|
}
|
||||||
idp.setEntityID(entityID);
|
idp.setEntityID(entityID);
|
||||||
|
|
||||||
boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
|
boolean signaturesRequired = getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
|
||||||
idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
|
idp.setSignatureCanonicalizationMethod(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
|
||||||
idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
|
idp.setSignatureAlgorithm(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
|
||||||
while (xmlEventReader.hasNext()) {
|
while (xmlEventReader.hasNext()) {
|
||||||
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
|
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
|
||||||
if (xmlEvent == null)
|
if (xmlEvent == null)
|
||||||
@@ -75,6 +79,10 @@ public class IDPXmlParser extends AbstractParser {
|
|||||||
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
|
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
|
||||||
idp.setSingleLogoutService(slo);
|
idp.setSingleLogoutService(slo);
|
||||||
|
|
||||||
|
} else if (tag.equals(ConfigXmlConstants.HTTP_CLIENT_ELEMENT)) {
|
||||||
|
HttpClientConfig config = parseHttpClientElement(xmlEventReader);
|
||||||
|
idp.setHttpClientConfig(config);
|
||||||
|
|
||||||
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
|
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
|
||||||
KeysXmlParser parser = new KeysXmlParser();
|
KeysXmlParser parser = new KeysXmlParser();
|
||||||
List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
|
List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
|
||||||
@@ -90,29 +98,63 @@ public class IDPXmlParser extends AbstractParser {
|
|||||||
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
|
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
|
||||||
IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
|
IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
|
||||||
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
|
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
|
||||||
slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
|
slo.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
|
||||||
slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
|
slo.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
|
||||||
slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
|
slo.setValidateRequestSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
|
||||||
slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
|
slo.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
|
||||||
slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
|
slo.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
|
||||||
slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
|
slo.setSignResponse(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
|
||||||
slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
|
slo.setPostBindingUrl(getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
|
||||||
slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
|
slo.setRedirectBindingUrl(getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
|
||||||
return slo;
|
return slo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
|
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
|
||||||
IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
|
IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
|
||||||
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
|
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
|
||||||
sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
|
sso.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
|
||||||
sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
|
sso.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
|
||||||
sso.setValidateAssertionSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
|
sso.setValidateAssertionSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
|
||||||
sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
|
sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
|
||||||
sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
|
sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
|
||||||
sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
|
sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
|
||||||
return sso;
|
return sso;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HttpClientConfig parseHttpClientElement(XMLEventReader xmlEventReader) throws ParsingException {
|
||||||
|
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
|
||||||
|
StaxParserUtil.validate(startElement, ConfigXmlConstants.HTTP_CLIENT_ELEMENT);
|
||||||
|
HttpClientConfig config = new HttpClientConfig();
|
||||||
|
|
||||||
|
config.setAllowAnyHostname(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
|
||||||
|
config.setClientKeystore(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_ATTR));
|
||||||
|
config.setClientKeystorePassword(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_PASSWORD_ATTR));
|
||||||
|
config.setConnectionPoolSize(getIntegerAttributeValue(startElement, ConfigXmlConstants.CONNECTION_POOL_SIZE_ATTR, 0));
|
||||||
|
config.setDisableTrustManager(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
|
||||||
|
config.setProxyUrl(getAttributeValue(startElement, ConfigXmlConstants.PROXY_URL_ATTR));
|
||||||
|
config.setTruststore(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_ATTR));
|
||||||
|
config.setTruststorePassword(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_PASSWORD_ATTR));
|
||||||
|
|
||||||
|
while (xmlEventReader.hasNext()) {
|
||||||
|
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
|
||||||
|
if (xmlEvent == null)
|
||||||
|
break;
|
||||||
|
if (xmlEvent instanceof EndElement) {
|
||||||
|
EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
|
||||||
|
String endElementName = StaxParserUtil.getEndElementName(endElement);
|
||||||
|
if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String tag = StaxParserUtil.getStartElementName(startElement);
|
||||||
|
StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(QName qname) {
|
public boolean supports(QName qname) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+7
@@ -48,6 +48,13 @@ public class SPXmlParser extends AbstractParser {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getIntegerAttributeValue(StartElement startElement, String tag, int defaultValue) {
|
||||||
|
String result = getAttributeValue(startElement, tag);
|
||||||
|
if (result == null)
|
||||||
|
return defaultValue;
|
||||||
|
return Integer.valueOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
|
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
|
||||||
String result = getAttributeValue(startElement, tag);
|
String result = getAttributeValue(startElement, tag);
|
||||||
if (result == null)
|
if (result == null)
|
||||||
|
|||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.adapters.saml.descriptor.parsers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import javax.xml.crypto.MarshalException;
|
||||||
|
import javax.xml.crypto.dom.DOMStructure;
|
||||||
|
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
|
||||||
|
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.xpath.XPath;
|
||||||
|
import javax.xml.xpath.XPathConstants;
|
||||||
|
import javax.xml.xpath.XPathExpression;
|
||||||
|
import javax.xml.xpath.XPathExpressionException;
|
||||||
|
import javax.xml.xpath.XPathFactory;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
import org.keycloak.saml.processing.core.util.NamespaceContext;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Goes through the given XML file and extracts names, certificates and keys from the KeyInfo elements.
|
||||||
|
* @author hmlnarik
|
||||||
|
*/
|
||||||
|
public class SamlDescriptorIDPKeysExtractor {
|
||||||
|
|
||||||
|
private static final NamespaceContext NS_CONTEXT = new NamespaceContext();
|
||||||
|
static {
|
||||||
|
NS_CONTEXT.addNsUriPair("m", JBossSAMLURIConstants.METADATA_NSURI.get());
|
||||||
|
NS_CONTEXT.addNsUriPair("dsig", JBossSAMLURIConstants.XMLDSIG_NSURI.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final KeyInfoFactory kif = KeyInfoFactory.getInstance();
|
||||||
|
|
||||||
|
private final XPathFactory xPathfactory = XPathFactory.newInstance();
|
||||||
|
private final XPath xpath = xPathfactory.newXPath();
|
||||||
|
{
|
||||||
|
xpath.setNamespaceContext(NS_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultivaluedHashMap<String, KeyInfo> parse(InputStream stream) throws ParsingException {
|
||||||
|
MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
factory.setNamespaceAware(true);
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(stream);
|
||||||
|
|
||||||
|
XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
|
||||||
|
NodeList keyDescriptors = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
|
||||||
|
for (int i = 0; i < keyDescriptors.getLength(); i ++) {
|
||||||
|
Node keyDescriptor = keyDescriptors.item(i);
|
||||||
|
Element keyDescriptorEl = (Element) keyDescriptor;
|
||||||
|
KeyInfo ki = processKeyDescriptor(keyDescriptorEl);
|
||||||
|
if (ki != null) {
|
||||||
|
String use = keyDescriptorEl.getAttribute(JBossSAMLConstants.USE.get());
|
||||||
|
res.add(use, ki);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SAXException | IOException | ParserConfigurationException | MarshalException | XPathExpressionException e) {
|
||||||
|
throw new ParsingException("Error parsing SAML descriptor", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyInfo processKeyDescriptor(Element keyDescriptor) throws MarshalException {
|
||||||
|
NodeList childNodes = keyDescriptor.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), JBossSAMLConstants.KEY_INFO.get());
|
||||||
|
|
||||||
|
if (childNodes.getLength() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Node keyInfoNode = childNodes.item(0);
|
||||||
|
return (keyInfoNode == null) ? null : kif.unmarshalKeyInfo(new DOMStructure(keyInfoNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+139
-15
@@ -60,15 +60,22 @@ import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
|||||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||||
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.util.HashSet;
|
import java.security.SignatureException;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Set;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
|
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
|
||||||
|
import org.keycloak.rotation.KeyLocator;
|
||||||
|
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -187,7 +194,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
challenge = new AuthChallenge() {
|
challenge = new AuthChallenge() {
|
||||||
@Override
|
@Override
|
||||||
public boolean challenge(HttpFacade exchange) {
|
public boolean challenge(HttpFacade exchange) {
|
||||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
|
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, statusResponse);
|
||||||
exchange.getRequest().setError(error);
|
exchange.getRequest().setError(error);
|
||||||
exchange.getResponse().sendError(403);
|
exchange.getResponse().sendError(403);
|
||||||
return true;
|
return true;
|
||||||
@@ -257,13 +264,44 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
|
private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
|
||||||
|
KeyLocator signatureValidationKey = deployment.getIDP().getSignatureValidationKeyLocator();
|
||||||
if (postBinding) {
|
if (postBinding) {
|
||||||
verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
|
verifyPostBindingSignature(holder.getSamlDocument(), signatureValidationKey);
|
||||||
} else {
|
} else {
|
||||||
verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
|
String keyId = getMessageSigningKeyId(holder.getSamlObject());
|
||||||
|
verifyRedirectBindingSignature(paramKey, signatureValidationKey, keyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getMessageSigningKeyId(SAML2Object doc) {
|
||||||
|
final ExtensionsType extensions;
|
||||||
|
if (doc instanceof RequestAbstractType) {
|
||||||
|
extensions = ((RequestAbstractType) doc).getExtensions();
|
||||||
|
} else if (doc instanceof StatusResponseType) {
|
||||||
|
extensions = ((StatusResponseType) doc).getExtensions();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensions == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object ext : extensions.getAny()) {
|
||||||
|
if (! (ext instanceof Element)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
|
||||||
|
|
||||||
|
if (res != null) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
|
private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
|
||||||
if(statusCode != null && statusCode.getValue()!=null){
|
if(statusCode != null && statusCode.getValue()!=null){
|
||||||
String v = statusCode.getValue().toString();
|
String v = statusCode.getValue().toString();
|
||||||
@@ -272,8 +310,25 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AuthOutcome handleLoginResponse(ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
|
protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
|
||||||
AssertionType assertion = null;
|
AssertionType assertion = null;
|
||||||
|
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
||||||
|
challenge = new AuthChallenge() {
|
||||||
|
@Override
|
||||||
|
public boolean challenge(HttpFacade exchange) {
|
||||||
|
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.ERROR_STATUS, responseType);
|
||||||
|
exchange.getRequest().setError(error);
|
||||||
|
exchange.getResponse().sendError(403);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getResponseCode() {
|
||||||
|
return 403;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return AuthOutcome.FAILED;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
|
assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey());
|
||||||
if (AssertionUtil.hasExpired(assertion)) {
|
if (AssertionUtil.hasExpired(assertion)) {
|
||||||
@@ -295,6 +350,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
return 403;
|
return 403;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return AuthOutcome.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
|
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
|
||||||
@@ -306,7 +362,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
challenge = new AuthChallenge() {
|
challenge = new AuthChallenge() {
|
||||||
@Override
|
@Override
|
||||||
public boolean challenge(HttpFacade exchange) {
|
public boolean challenge(HttpFacade exchange) {
|
||||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE);
|
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||||
exchange.getRequest().setError(error);
|
exchange.getRequest().setError(error);
|
||||||
exchange.getResponse().sendError(403);
|
exchange.getResponse().sendError(403);
|
||||||
return true;
|
return true;
|
||||||
@@ -409,6 +465,14 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
return AuthOutcome.AUTHENTICATED;
|
return AuthOutcome.AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSuccessfulSamlResponse(ResponseType responseType) {
|
||||||
|
return responseType != null
|
||||||
|
&& responseType.getStatus() != null
|
||||||
|
&& responseType.getStatus().getStatusCode() != null
|
||||||
|
&& responseType.getStatus().getStatusCode().getValue() != null
|
||||||
|
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
||||||
|
}
|
||||||
|
|
||||||
private String getAttributeValue(Object attrValue) {
|
private String getAttributeValue(Object attrValue) {
|
||||||
String value = null;
|
String value = null;
|
||||||
if (attrValue instanceof String) {
|
if (attrValue instanceof String) {
|
||||||
@@ -473,10 +537,10 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
|
public void verifyPostBindingSignature(Document document, KeyLocator keyLocator) throws VerificationException {
|
||||||
SAML2Signature saml2Signature = new SAML2Signature();
|
SAML2Signature saml2Signature = new SAML2Signature();
|
||||||
try {
|
try {
|
||||||
if (!saml2Signature.validate(document, publicKey)) {
|
if (!saml2Signature.validate(document, keyLocator)) {
|
||||||
throw new VerificationException("Invalid signature on document");
|
throw new VerificationException("Invalid signature on document");
|
||||||
}
|
}
|
||||||
} catch (ProcessingException e) {
|
} catch (ProcessingException e) {
|
||||||
@@ -484,7 +548,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
|
private void verifyRedirectBindingSignature(String paramKey, KeyLocator keyLocator, String keyId) throws VerificationException {
|
||||||
String request = facade.getRequest().getQueryParamValue(paramKey);
|
String request = facade.getRequest().getQueryParamValue(paramKey);
|
||||||
String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
||||||
String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
|
String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
|
||||||
@@ -511,16 +575,76 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||||||
try {
|
try {
|
||||||
//byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
|
//byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
|
||||||
byte[] decodedSignature = Base64.decode(signature);
|
byte[] decodedSignature = Base64.decode(signature);
|
||||||
|
byte[] rawQueryBytes = rawQuery.getBytes("UTF-8");
|
||||||
|
|
||||||
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
|
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
|
||||||
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
|
|
||||||
validator.initVerify(publicKey);
|
if (! validateRedirectBindingSignature(signatureAlgorithm, rawQueryBytes, decodedSignature, keyLocator, keyId)) {
|
||||||
validator.update(rawQuery.getBytes("UTF-8"));
|
|
||||||
if (!validator.verify(decodedSignature)) {
|
|
||||||
throw new VerificationException("Invalid query param signature");
|
throw new VerificationException("Invalid query param signature");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new VerificationException(e);
|
throw new VerificationException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validateRedirectBindingSignature(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, KeyLocator locator, String keyId)
|
||||||
|
throws KeyManagementException, VerificationException {
|
||||||
|
try {
|
||||||
|
Key key;
|
||||||
|
try {
|
||||||
|
key = locator.getKey(keyId);
|
||||||
|
boolean keyLocated = key != null;
|
||||||
|
|
||||||
|
if (keyLocated) {
|
||||||
|
return validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key);
|
||||||
|
}
|
||||||
|
} catch (KeyManagementException ex) {
|
||||||
|
}
|
||||||
|
} catch (SignatureException ex) {
|
||||||
|
log.debug("Verification failed for key %s: %s", keyId, ex);
|
||||||
|
log.trace(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locator instanceof Iterable) {
|
||||||
|
Iterable<Key> availableKeys = (Iterable<Key>) locator;
|
||||||
|
|
||||||
|
log.trace("Trying hard to validate XML signature using all available keys.");
|
||||||
|
|
||||||
|
for (Key key : availableKeys) {
|
||||||
|
try {
|
||||||
|
if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (SignatureException ex) {
|
||||||
|
log.debug("Verification failed: %s", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validateRedirectBindingSignatureForKey(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, Key key)
|
||||||
|
throws SignatureException {
|
||||||
|
if (key == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! (key instanceof PublicKey)) {
|
||||||
|
log.warnf("Unusable key for signature validation: %s", key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Signature signature = sigAlg.createSignature(); // todo plugin signature alg
|
||||||
|
try {
|
||||||
|
signature.initVerify((PublicKey) key);
|
||||||
|
} catch (InvalidKeyException ex) {
|
||||||
|
log.warnf(ex, "Unusable key for signature validation: %s", key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
signature.update(rawQueryBytes);
|
||||||
|
|
||||||
|
return signature.verify(decodedSignature);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+6
-2
@@ -82,8 +82,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
|
|||||||
if (deployment.getSignatureCanonicalizationMethod() != null)
|
if (deployment.getSignatureCanonicalizationMethod() != null)
|
||||||
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
||||||
binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
|
binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
|
||||||
.signWith(deployment.getSigningKeyPair())
|
.signWith(null, deployment.getSigningKeyPair())
|
||||||
.signDocument();
|
.signDocument();
|
||||||
|
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
|
||||||
|
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -113,8 +115,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
|
|||||||
if (deployment.getSignatureCanonicalizationMethod() != null)
|
if (deployment.getSignatureCanonicalizationMethod() != null)
|
||||||
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
|
||||||
binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
|
binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
|
||||||
binding.signWith(deployment.getSigningKeyPair())
|
binding.signWith(null, deployment.getSigningKeyPair())
|
||||||
.signDocument();
|
.signDocument();
|
||||||
|
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
|
||||||
|
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.relayState("logout");
|
binding.relayState("logout");
|
||||||
|
|||||||
Loaded 100 of 2206 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user