Compare commits
880
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfafe65814 | ||
|
|
1b8dc04459 | ||
|
|
4355c89b9d | ||
|
|
1b37394276 | ||
|
|
ded82fff3d | ||
|
|
4fbbaf18aa | ||
|
|
5ad929b9ee | ||
|
|
2b9b1ba45f | ||
|
|
8b122de425 | ||
|
|
7d75377813 | ||
|
|
3c8dddf33e | ||
|
|
311e848460 | ||
|
|
607bb1b995 | ||
|
|
91637120ee | ||
|
|
d90a5d1367 | ||
|
|
d395043fc7 | ||
|
|
c9cd060417 | ||
|
|
abd6e560ac | ||
|
|
6e93ca36af | ||
|
|
2d727fc54c | ||
|
|
6db1f60e27 | ||
|
|
8af1ca8fc3 | ||
|
|
55f90ff09f | ||
|
|
dc06a8cee3 | ||
|
|
f3bf1456ab | ||
|
|
548950ed8e | ||
|
|
f67d6f9660 | ||
|
|
a1d86fbce8 | ||
|
|
3756cf629b | ||
|
|
0793234c19 | ||
|
|
461dae20de | ||
|
|
a3d4612edd | ||
|
|
0533782d90 | ||
|
|
3bdbbf41af | ||
|
|
0b36020bf5 | ||
|
|
85f11873c3 | ||
|
|
39bf08e1b9 | ||
|
|
9f88abb022 | ||
|
|
78b2067f73 | ||
|
|
cf57a1bc4b | ||
|
|
8c650f9f6a | ||
|
|
22d8fb17f5 | ||
|
|
6cee8b126b | ||
|
|
f5ae76d8e3 | ||
|
|
e391328b5d | ||
|
|
ecd476fb10 | ||
|
|
c3778e66db | ||
|
|
6a23eb19f5 | ||
|
|
72b23c1357 | ||
|
|
0cb6053699 | ||
|
|
6564cebc0f | ||
|
|
a957e118e6 | ||
|
|
0897d969b1 | ||
|
|
1b5a83c4f1 | ||
|
|
f090b39e85 | ||
|
|
5b72bd8da6 | ||
|
|
602a6e201d | ||
|
|
086dbae5ca | ||
|
|
4f5b41c65b | ||
|
|
28eb1007fb | ||
|
|
7703d81389 | ||
|
|
cd96d6cc35 | ||
|
|
1ee6fd7130 | ||
|
|
6450a457d0 | ||
|
|
6d04247947 | ||
|
|
29f8187978 | ||
|
|
e7b7307b4f | ||
|
|
5e1d85c9a8 | ||
|
|
560d76b7ee | ||
|
|
bce2aee144 | ||
|
|
94f2ce2eae | ||
|
|
3be2c35561 | ||
|
|
cbe59f03b7 | ||
|
|
36b0d8b80e | ||
|
|
5880efe775 | ||
|
|
e6b9364c39 | ||
|
|
0c6b20e862 | ||
|
|
327991bd73 | ||
|
|
234b7a06a1 | ||
|
|
00cee1d8cf | ||
|
|
d0f75c8766 | ||
|
|
b2aa324ee4 | ||
|
|
76e4253a21 | ||
|
|
ffcd8e09e7 | ||
|
|
cfeb56e18a | ||
|
|
9652748ba9 | ||
|
|
91c4bfa81c | ||
|
|
e843297238 | ||
|
|
f5425f916a | ||
|
|
f6943296c7 | ||
|
|
6bf91a3956 | ||
|
|
4d2300f17e | ||
|
|
f449b8b454 | ||
|
|
9ef4c7fffd | ||
|
|
460cdf4508 | ||
|
|
2af9d002b6 | ||
|
|
7e12b60158 | ||
|
|
a2b13715ed | ||
|
|
f55264499c | ||
|
|
f3796d2eb8 | ||
|
|
ec335629b4 | ||
|
|
c36b577566 | ||
|
|
6f8f8e6a28 | ||
|
|
c536955b70 | ||
|
|
7d195deece | ||
|
|
7d85ce93bb | ||
|
|
7a96911a83 | ||
|
|
096d749411 | ||
|
|
623d985e7f | ||
|
|
68ab08aa56 | ||
|
|
ab8789739f | ||
|
|
60a8267576 | ||
|
|
5f0424fb11 | ||
|
|
393ff50b8d | ||
|
|
68a54abb09 | ||
|
|
8cecbcffb8 | ||
|
|
102628dc59 | ||
|
|
11374a2707 | ||
|
|
4483677cdd | ||
|
|
0e8d79bbfb | ||
|
|
c71f6e2188 | ||
|
|
371bca57aa | ||
|
|
f254675a5e | ||
|
|
066bef744f | ||
|
|
fbe3445c48 | ||
|
|
5b51c000af | ||
|
|
aaa33ad883 | ||
|
|
9be8bef575 | ||
|
|
0a6f43c1a1 | ||
|
|
6b59c2f44c | ||
|
|
fd0ab4a626 | ||
|
|
22d64368a6 | ||
|
|
79ca722b49 | ||
|
|
c3f1bd5a25 | ||
|
|
3ca386f223 | ||
|
|
00f4b54730 | ||
|
|
a8ab1fea58 | ||
|
|
8e57cee30f | ||
|
|
7f1c03a122 | ||
|
|
6fd4a02f95 | ||
|
|
531ee3a1be | ||
|
|
28b6e4dd5b | ||
|
|
f17b5f0f49 | ||
|
|
86a2f28561 | ||
|
|
0d9b1e73b8 | ||
|
|
fe0db7fe44 | ||
|
|
6ebc6c0354 | ||
|
|
a82f0ab657 | ||
|
|
0a6e22ba54 | ||
|
|
211774ccbc | ||
|
|
c41bcddd8d | ||
|
|
2a4cee6044 | ||
|
|
dba513c921 | ||
|
|
b4b3527df7 | ||
|
|
17a1a33987 | ||
|
|
4b9b189016 | ||
|
|
e1ecf94428 | ||
|
|
2da758ac86 | ||
|
|
efe6a38648 | ||
|
|
c3fc9e9815 | ||
|
|
43f5983613 | ||
|
|
723ba42264 | ||
|
|
081e9883e6 | ||
|
|
df311b60b4 | ||
|
|
0ff0dcecec | ||
|
|
eda09d2b32 | ||
|
|
340c8e8426 | ||
|
|
3777dc45d0 | ||
|
|
adf0a19f9d | ||
|
|
b748e269ec | ||
|
|
25b390b2ae | ||
|
|
52b7fd8c45 | ||
|
|
4268dd1777 | ||
|
|
24b7d080af | ||
|
|
39f0e4528a | ||
|
|
6b0bc0b3be | ||
|
|
2bf6d75e57 | ||
|
|
3dd6f9cb85 | ||
|
|
044d153c37 | ||
|
|
609c521c17 | ||
|
|
0b893d5634 | ||
|
|
aaf78297c9 | ||
|
|
64f8fe4987 | ||
|
|
e85a396572 | ||
|
|
99a16dcc1f | ||
|
|
8b6db21e56 | ||
|
|
01051016f5 | ||
|
|
c4a651bcac | ||
|
|
62c1ffcb52 | ||
|
|
00da3e3669 | ||
|
|
d4a5c81034 | ||
|
|
26f257a6ac | ||
|
|
12f3d2115d | ||
|
|
24e60747b6 | ||
|
|
5b6036525c | ||
|
|
bd4098191b | ||
|
|
df22c4d613 | ||
|
|
0561d73ae2 | ||
|
|
bf758809ba | ||
|
|
1fb4ca4525 | ||
|
|
e59646aafb | ||
|
|
bd8510f4da | ||
|
|
c56e171f3a | ||
|
|
812e76c39b | ||
|
|
8761819b24 | ||
|
|
47066e1b89 | ||
|
|
6a0a1031a1 | ||
|
|
33efcc6b93 | ||
|
|
2eddfd3ea8 | ||
|
|
bc8763ccf3 | ||
|
|
9f839f001f | ||
|
|
237318dfd3 | ||
|
|
5fe1905e4b | ||
|
|
f0ba8f6591 | ||
|
|
54b5ec206e | ||
|
|
befee75917 | ||
|
|
5ae126b10e | ||
|
|
c0b5c12dee | ||
|
|
214a8e1fed | ||
|
|
f89637bd8f | ||
|
|
bee3894cdf | ||
|
|
df76afb513 | ||
|
|
c266e90a77 | ||
|
|
b70468341e | ||
|
|
21b71e83dd | ||
|
|
ccba07a5c0 | ||
|
|
42553cdc44 | ||
|
|
8a7a545628 | ||
|
|
d37eb5d10b | ||
|
|
36837ae4b6 | ||
|
|
31270e2f52 | ||
|
|
3ccffdb77e | ||
|
|
56c97407d4 | ||
|
|
bf7a173e1e | ||
|
|
2077975b1c | ||
|
|
e4d05a7852 | ||
|
|
6fc99cd749 | ||
|
|
01b0b6b345 | ||
|
|
59082e0b5f | ||
|
|
9e072cb174 | ||
|
|
c0508aded7 | ||
|
|
9882341ecf | ||
|
|
3c2339ba33 | ||
|
|
457c6a9d80 | ||
|
|
1f840b1fbc | ||
|
|
694966b613 | ||
|
|
0a8fca7ec4 | ||
|
|
3e2e0ac91c | ||
|
|
b21e784826 | ||
|
|
dda365e002 | ||
|
|
653d3f4f5d | ||
|
|
3dcc106c59 | ||
|
|
248654a75e | ||
|
|
fdc9882709 | ||
|
|
b606a25684 | ||
|
|
1b88eaf817 | ||
|
|
dc8268b30c | ||
|
|
b80701589c | ||
|
|
d04791243c | ||
|
|
c5f861a522 | ||
|
|
b6d545b332 | ||
|
|
a35ed671e6 | ||
|
|
f0a2f7a675 | ||
|
|
4cb9944067 | ||
|
|
625f613128 | ||
|
|
e406e8f1f0 | ||
|
|
90bafee5eb | ||
|
|
ab77140454 | ||
|
|
d1ae13c208 | ||
|
|
e9604a49e1 | ||
|
|
645a72482b | ||
|
|
730377a843 | ||
|
|
f36e45cb10 | ||
|
|
ed72097862 | ||
|
|
0b95cdacb8 | ||
|
|
1912a8acf4 | ||
|
|
79774d2f07 | ||
|
|
fa8cb004a1 | ||
|
|
3449401ae2 | ||
|
|
02b2a8aab0 | ||
|
|
575851d45c | ||
|
|
060b3b8d0f | ||
|
|
a8a9631d4f | ||
|
|
2efc7ebb48 | ||
|
|
905fd3ae00 | ||
|
|
ee8c35a48e | ||
|
|
fb58214fcc | ||
|
|
4554798cec | ||
|
|
80e5227bcd | ||
|
|
65f51b7b83 | ||
|
|
72750b9882 | ||
|
|
27719565ae | ||
|
|
65030e2c73 | ||
|
|
b5d56e2f3b | ||
|
|
a63676ce93 | ||
|
|
01af40413e | ||
|
|
f6a4ba98de | ||
|
|
263792a4ab | ||
|
|
3380fdc119 | ||
|
|
959cd035ba | ||
|
|
e7e15652cf | ||
|
|
f57cc3a9c0 | ||
|
|
29da7d3d90 | ||
|
|
f99299ee39 | ||
|
|
ae47b7fa80 | ||
|
|
a2afe7c205 | ||
|
|
f32e258a18 | ||
|
|
665bcaebbb | ||
|
|
398f7d950f | ||
|
|
e6ad0d63a1 | ||
|
|
f0808d6882 | ||
|
|
a4fd7e9f7f | ||
|
|
7a978ff3f6 | ||
|
|
9b0930a289 | ||
|
|
959e7b1b01 | ||
|
|
40cc826586 | ||
|
|
6c593bab5a | ||
|
|
9c18d0e628 | ||
|
|
ecd3fcc0af | ||
|
|
38017d3cec | ||
|
|
d88568266f | ||
|
|
f43519a16e | ||
|
|
771d7f1724 | ||
|
|
0844aa8d68 | ||
|
|
d73c4288ae | ||
|
|
9c1a411c6e | ||
|
|
8eabd4ae89 | ||
|
|
524ab44160 | ||
|
|
ea8eaaff07 | ||
|
|
a6e4f4f9aa | ||
|
|
af72c1374a | ||
|
|
7c0ca9aad2 | ||
|
|
b43392bac8 | ||
|
|
c8bc0d6d7b | ||
|
|
c4b375c1fc | ||
|
|
1308a3231d | ||
|
|
2eefd60344 | ||
|
|
5472364265 | ||
|
|
26bf7f251d | ||
|
|
7c14a6a503 | ||
|
|
acc5f5c6d1 | ||
|
|
2cb7ec9432 | ||
|
|
8b6979ac18 | ||
|
|
34407957b9 | ||
|
|
44f5cf3020 | ||
|
|
54fcbf12b0 | ||
|
|
31f6cf9ab5 | ||
|
|
3417b569c0 | ||
|
|
8e20986335 | ||
|
|
385e1c6122 | ||
|
|
3e158c0321 | ||
|
|
c969c4c903 | ||
|
|
8533bc7f00 | ||
|
|
2adff375db | ||
|
|
d959e501ae | ||
|
|
886362ebf2 | ||
|
|
8e221ea597 | ||
|
|
432ea277a7 | ||
|
|
b520dda3ef | ||
|
|
90bfa2bff5 | ||
|
|
3fc6eed1ce | ||
|
|
fbbc670d1f | ||
|
|
f022bc1269 | ||
|
|
5aebc74f8c | ||
|
|
fba2bf0b2f | ||
|
|
55550f2023 | ||
|
|
401a347f6f | ||
|
|
0432a566dd | ||
|
|
4a82979792 | ||
|
|
3918dbed59 | ||
|
|
b80b480769 | ||
|
|
d0a824dde4 | ||
|
|
01da592f77 | ||
|
|
40d129cf54 | ||
|
|
8c66f520af | ||
|
|
64b391cc1b | ||
|
|
8a37395760 | ||
|
|
f5d00ddffb | ||
|
|
dafd567e68 | ||
|
|
d55ccf5312 | ||
|
|
871be4ad87 | ||
|
|
6f3c59e086 | ||
|
|
fc0d510e85 | ||
|
|
742a280f5d | ||
|
|
87126704cd | ||
|
|
1450a7fad4 | ||
|
|
65551159e0 | ||
|
|
d99dca2db3 | ||
|
|
3355399b4e | ||
|
|
dcadc61220 | ||
|
|
3c5027de3c | ||
|
|
a5d155a35a | ||
|
|
05bbd9ace3 | ||
|
|
559a8b2a2c | ||
|
|
c97e7e720e | ||
|
|
f10c47955f | ||
|
|
23db2b852b | ||
|
|
591093f867 | ||
|
|
9039b44f4d | ||
|
|
5f0c86a49f | ||
|
|
8ac7bda52c | ||
|
|
0e2e867e4a | ||
|
|
0d9ccba566 | ||
|
|
30e8071d1d | ||
|
|
39cbf4e9ab | ||
|
|
af47bd5da8 | ||
|
|
0ee5c97b1c | ||
|
|
d07f13eace | ||
|
|
fc3ca33033 | ||
|
|
2fb022e501 | ||
|
|
b478472b35 | ||
|
|
8fdadcc596 | ||
|
|
ca06043b00 | ||
|
|
3c9d3c2c04 | ||
|
|
998227ac53 | ||
|
|
30a066db41 | ||
|
|
d9f79fae79 | ||
|
|
530a710dce | ||
|
|
f5ca4840d6 | ||
|
|
d3a6b6f587 | ||
|
|
f90e0fc14c | ||
|
|
6b968796ce | ||
|
|
1277f8bb3b | ||
|
|
63a6ba53df | ||
|
|
9af51fb664 | ||
|
|
c2012a595b | ||
|
|
2fcfa5cf71 | ||
|
|
e638391182 | ||
|
|
e98fcfe56e | ||
|
|
2f366b0d5d | ||
|
|
e1a0e581b9 | ||
|
|
c43d8b3d85 | ||
|
|
6a2a121d4e | ||
|
|
9dc5709ce7 | ||
|
|
ccb09fbf45 | ||
|
|
fb883254f6 | ||
|
|
5f1f3dff5e | ||
|
|
dd93de75d9 | ||
|
|
d897159560 | ||
|
|
182c975e01 | ||
|
|
f19a324030 | ||
|
|
9cf965a157 | ||
|
|
9e42be09d7 | ||
|
|
7d466be94e | ||
|
|
a5c0cbc3b4 | ||
|
|
bb5dc4c473 | ||
|
|
132386f64d | ||
|
|
b0c89d739b | ||
|
|
6a07a7ed2c | ||
|
|
f293ab86c3 | ||
|
|
db60abc604 | ||
|
|
f55c93a1e4 | ||
|
|
08296bd3ce | ||
|
|
fca6da3a5a | ||
|
|
49407c2e4f | ||
|
|
4d1474afe0 | ||
|
|
aa128d6c07 | ||
|
|
c8c76cc03f | ||
|
|
5a9bfea419 | ||
|
|
dffe70e40a | ||
|
|
1b4d9a6147 | ||
|
|
b7fb3ec9a9 | ||
|
|
7ff18ca14b | ||
|
|
6052b1546d | ||
|
|
5a241392cf | ||
|
|
c586c63533 | ||
|
|
f8919f8baa | ||
|
|
f45ed1613e | ||
|
|
bc665fdbc3 | ||
|
|
6c3e6bc90c | ||
|
|
cb3b7f3cb2 | ||
|
|
5e8db32329 | ||
|
|
65c39763eb | ||
|
|
f429469fc8 | ||
|
|
6339b62f8d | ||
|
|
473e9ab55c | ||
|
|
77e503f5ca | ||
|
|
c64ecb1ab8 | ||
|
|
2e22dcfc47 | ||
|
|
16fd6558a6 | ||
|
|
eb97151476 | ||
|
|
b8adee4004 | ||
|
|
65f5eacdd4 | ||
|
|
2b6597e9f1 | ||
|
|
6a2f73df9c | ||
|
|
60a03d0293 | ||
|
|
dd6735285d | ||
|
|
dbf5c395b0 | ||
|
|
8261ffeeeb | ||
|
|
1e438cdc45 | ||
|
|
d70859ef1b | ||
|
|
3ab8ff2ea1 | ||
|
|
adea414695 | ||
|
|
cace03c3cc | ||
|
|
d55b1d7259 | ||
|
|
78e963688c | ||
|
|
b2df872ad4 | ||
|
|
fea4201f46 | ||
|
|
073a2f4d65 | ||
|
|
06df678d33 | ||
|
|
dd65c231f9 | ||
|
|
ae690e0679 | ||
|
|
06f108df3b | ||
|
|
a8319d1131 | ||
|
|
f800290d89 | ||
|
|
e79de47f5e | ||
|
|
08c22416a2 | ||
|
|
dac5d313b3 | ||
|
|
21d139c6c2 | ||
|
|
e5d997a6c0 | ||
|
|
a2e54dc94f | ||
|
|
28040109ac | ||
|
|
39b6bf62ba | ||
|
|
6a19a8fb7e | ||
|
|
e914b6fe8d | ||
|
|
e5a6dbd77a | ||
|
|
343dc05f82 | ||
|
|
76076cdb3c | ||
|
|
487539542a | ||
|
|
1634bef28a | ||
|
|
6882f7a9c0 | ||
|
|
943c8507f7 | ||
|
|
99119562bb | ||
|
|
5cbe595fe3 | ||
|
|
eb8257a52e | ||
|
|
27d8afe4a7 | ||
|
|
3391ec5377 | ||
|
|
88f21eae87 | ||
|
|
1258923a0d | ||
|
|
344286e037 | ||
|
|
3ced81a2c2 | ||
|
|
032c48fb25 | ||
|
|
f6125a2542 | ||
|
|
49a03a86fb | ||
|
|
e84acd9898 | ||
|
|
8022a74565 | ||
|
|
fdc6fc59b8 | ||
|
|
f3340b113f | ||
|
|
20f24bffc4 | ||
|
|
7ebcc69cb9 | ||
|
|
90e5c7f3eb | ||
|
|
81918713dd | ||
|
|
9505925363 | ||
|
|
6e123bcea2 | ||
|
|
ddceaaf3d5 | ||
|
|
a3fb0a5ac5 | ||
|
|
1ef5379648 | ||
|
|
5ec50461ee | ||
|
|
e960642399 | ||
|
|
2bab2acf5b | ||
|
|
5a56a822b0 | ||
|
|
35154db50f | ||
|
|
fe2ae6ec68 | ||
|
|
b249a48dcf | ||
|
|
035ebc881a | ||
|
|
8b5fd2b4ac | ||
|
|
e813fcd9c8 | ||
|
|
634e7170e3 | ||
|
|
b6a0303a4c | ||
|
|
824b900a43 | ||
|
|
49a83db2e9 | ||
|
|
af7e42d640 | ||
|
|
9183cba02e | ||
|
|
c3d297dd05 | ||
|
|
527d6ca4d8 | ||
|
|
5e4d173f1d | ||
|
|
c3c1a0fb4e | ||
|
|
7b8c691402 | ||
|
|
a1e7351072 | ||
|
|
7efa45126c | ||
|
|
c12cd7fd57 | ||
|
|
4ddff9ee16 | ||
|
|
615cb24cce | ||
|
|
3bef6d5066 | ||
|
|
e8a07c9a6c | ||
|
|
3041499cd4 | ||
|
|
03590f117e | ||
|
|
c1311e4619 | ||
|
|
9a94004fc9 | ||
|
|
095fec95e5 | ||
|
|
b78f0aa9db | ||
|
|
fac3118b0a | ||
|
|
cf056b3464 | ||
|
|
9ed221b168 | ||
|
|
e12752f9ac | ||
|
|
70b3f6bd92 | ||
|
|
5022015e4a | ||
|
|
f41a7000de | ||
|
|
a67da7bc59 | ||
|
|
943bd9e48e | ||
|
|
2b14469a58 | ||
|
|
2be6dec238 | ||
|
|
e1f5245145 | ||
|
|
b3513e3203 | ||
|
|
ffd9d957f4 | ||
|
|
3cea54e3a6 | ||
|
|
ef4223858b | ||
|
|
5514812a4d | ||
|
|
87abe5e648 | ||
|
|
7e3703b2bf | ||
|
|
f652b13520 | ||
|
|
baf6b96db6 | ||
|
|
701c318b60 | ||
|
|
b3b81d6a76 | ||
|
|
8a5428808e | ||
|
|
a939c45d58 | ||
|
|
0b2fe75828 | ||
|
|
28d0ab9da8 | ||
|
|
103a3c4ca7 | ||
|
|
5c52da80c6 | ||
|
|
8fe10b3331 | ||
|
|
04a72b9608 | ||
|
|
7280234422 | ||
|
|
4078e84fb6 | ||
|
|
06f32a47ec | ||
|
|
f4a5e49b63 | ||
|
|
5d74b776d6 | ||
|
|
5cae1bb134 | ||
|
|
8d3dc790df | ||
|
|
f5bacb79c1 | ||
|
|
4a425c2674 | ||
|
|
c38b6d585e | ||
|
|
ad5f3fefc5 | ||
|
|
707b80f436 | ||
|
|
80feb67fc2 | ||
|
|
aae63de755 | ||
|
|
e9e376419d | ||
|
|
ffeb0420bf | ||
|
|
a63bb44ba2 | ||
|
|
91135c95ae | ||
|
|
bff7831912 | ||
|
|
66186273ca | ||
|
|
07fea02146 | ||
|
|
593f57fd2c | ||
|
|
d40e9bd3c1 | ||
|
|
92aba77cc1 | ||
|
|
e4e14cd9f4 | ||
|
|
327a159a10 | ||
|
|
d57fb445eb | ||
|
|
fe98c30077 | ||
|
|
758ae41999 | ||
|
|
67229912e6 | ||
|
|
51c593b8cd | ||
|
|
39f93dfa33 | ||
|
|
3559c5dc3c | ||
|
|
bb3c76a9d9 | ||
|
|
ca15db81bb | ||
|
|
f000cedcbb | ||
|
|
681e3d751e | ||
|
|
66507cbf91 | ||
|
|
f11c24e359 | ||
|
|
3ade41a76d | ||
|
|
8926837a3e | ||
|
|
35b9fe043c | ||
|
|
82ba2b1b0d | ||
|
|
57f57f5c75 | ||
|
|
08896ee9c9 | ||
|
|
917ba90f2c | ||
|
|
3cf104580e | ||
|
|
4bba11cd94 | ||
|
|
7cfe6addf0 | ||
|
|
e4781b8aa3 | ||
|
|
9de8e79577 | ||
|
|
3826f6fae2 | ||
|
|
711bf244ed | ||
|
|
99d0811412 | ||
|
|
ee2d28d589 | ||
|
|
22e32117e0 | ||
|
|
fed1b62c5d | ||
|
|
5b1e65c23e | ||
|
|
e72756d01a | ||
|
|
871ecf83fb | ||
|
|
daaa35bc37 | ||
|
|
9d10ccef70 | ||
|
|
2aa71d1737 | ||
|
|
b9b1102b74 | ||
|
|
f824582aac | ||
|
|
199f289ee3 | ||
|
|
668b67dcdb | ||
|
|
190771ddf1 | ||
|
|
05dcc6e3af | ||
|
|
6f7200868b | ||
|
|
62b70b561e | ||
|
|
1f13427dee | ||
|
|
6aee573e2e | ||
|
|
c5c285abc3 | ||
|
|
0a4fd79b22 | ||
|
|
b29edc7056 | ||
|
|
97aa3f5f36 | ||
|
|
d66c33a8b9 | ||
|
|
63383f8032 | ||
|
|
9599003bc3 | ||
|
|
a63a01c9ea | ||
|
|
dabf4e8bbd | ||
|
|
35ffdb4745 | ||
|
|
b0d63980e3 | ||
|
|
c27ffbda8b | ||
|
|
502fc62967 | ||
|
|
c06ac70633 | ||
|
|
414d51b02b | ||
|
|
4b6b45cf43 | ||
|
|
986f39838c | ||
|
|
d90affa47d | ||
|
|
d5a075da30 | ||
|
|
1b06194455 | ||
|
|
e2bec73011 | ||
|
|
07613bcca0 | ||
|
|
cc24484eaf | ||
|
|
8549bd70b7 | ||
|
|
9bd2e70376 | ||
|
|
569f26776e | ||
|
|
7f21cdd1f4 | ||
|
|
7b5a0c6dcf | ||
|
|
1e1de85685 | ||
|
|
9b1275f182 | ||
|
|
cb531056a6 | ||
|
|
b0200d462d | ||
|
|
028e78f46b | ||
|
|
9ca7b22cec | ||
|
|
1b45ab2601 | ||
|
|
215bbaac6c | ||
|
|
53f96099fb | ||
|
|
681256a079 | ||
|
|
2a4663c940 | ||
|
|
1d88d061e6 | ||
|
|
123c6dffe0 | ||
|
|
91bdc4bde2 | ||
|
|
190ad06f1a | ||
|
|
f8022a5c2f | ||
|
|
e52380915b | ||
|
|
08bf19d4aa | ||
|
|
a2cc7bd4b9 | ||
|
|
b8637bf1fa | ||
|
|
417c31bc0b | ||
|
|
1f20c03afa | ||
|
|
d70e4740fc | ||
|
|
b1d42381cd | ||
|
|
aa089980ce | ||
|
|
52acd959e0 | ||
|
|
8cf65bc37d | ||
|
|
24132c8f5b | ||
|
|
fe1c447d9a | ||
|
|
bde9210fa3 | ||
|
|
10e31c397c | ||
|
|
e2ad59a74d | ||
|
|
f604449188 | ||
|
|
f3b965d466 | ||
|
|
e7cdb8ad54 | ||
|
|
898347366d | ||
|
|
9ef1f1b73c | ||
|
|
eea6eb6263 | ||
|
|
548ab4f78c | ||
|
|
b464dc15f2 | ||
|
|
8112c5d3f2 | ||
|
|
eb326cd1bb | ||
|
|
208ecbc3f7 | ||
|
|
61d5425fdf | ||
|
|
d590600c12 | ||
|
|
12a2f23101 | ||
|
|
f351db608e | ||
|
|
2c59d948fc | ||
|
|
72199cc0b3 | ||
|
|
fc463ae50b | ||
|
|
86a8addf49 | ||
|
|
b367e94261 | ||
|
|
df68ca8dcf | ||
|
|
9b63cd35f0 | ||
|
|
84ea3f8cb1 | ||
|
|
5d5373454c | ||
|
|
a3d6917f20 | ||
|
|
87ee15a081 | ||
|
|
f44cda2621 | ||
|
|
dd1e5b5c0f | ||
|
|
adb6c7789c | ||
|
|
d6788a0839 | ||
|
|
2b65adc15f | ||
|
|
b91998a0d8 | ||
|
|
505cf5b251 | ||
|
|
5ea4ef9e55 | ||
|
|
e8de4655ac | ||
|
|
76657d5239 | ||
|
|
d63040283f | ||
|
|
04c2ad9430 | ||
|
|
75f040f38e | ||
|
|
0e69dc6885 | ||
|
|
f1e7d3d6da | ||
|
|
c07b60d527 | ||
|
|
2743e4a182 | ||
|
|
972c3dd36b | ||
|
|
9ccef223f6 | ||
|
|
fc52ff65bd | ||
|
|
8325151e16 | ||
|
|
b3766576d7 | ||
|
|
e3153295a9 | ||
|
|
c8c86d2bad | ||
|
|
3420293f36 | ||
|
|
0370cb64e7 | ||
|
|
a50b94be0c | ||
|
|
62a9d4ea91 | ||
|
|
c2fe500eb8 | ||
|
|
340afb2a50 | ||
|
|
3873ab811a | ||
|
|
4ab856a0be | ||
|
|
46ebff2163 | ||
|
|
afa26f7d3c | ||
|
|
9382439a05 | ||
|
|
8495a7c05a | ||
|
|
019c3c9ef9 | ||
|
|
77334af34e | ||
|
|
8f09efab9d | ||
|
|
c34db4cf01 | ||
|
|
6054b69eec | ||
|
|
6b8ec0bb82 | ||
|
|
a0d275c850 | ||
|
|
179e371d0e | ||
|
|
26411a123e | ||
|
|
126dd70efc | ||
|
|
461c62ea10 | ||
|
|
bda57d00df | ||
|
|
bd3eb9d662 | ||
|
|
a571781240 | ||
|
|
a5f675d693 | ||
|
|
4a044fe867 | ||
|
|
0fc7fa557d | ||
|
|
79f9de9de4 | ||
|
|
4bf23cc83a | ||
|
|
82815ff614 | ||
|
|
bdfab04c10 | ||
|
|
2a2f255640 | ||
|
|
1d8e38f0c6 | ||
|
|
dd4c0d448c | ||
|
|
6b84b9b4b6 | ||
|
|
8ac7d1deca | ||
|
|
f0511c5f51 | ||
|
|
502627f590 | ||
|
|
806b554fb2 | ||
|
|
13261b52db | ||
|
|
a21a94078b | ||
|
|
b0fbe5c8ba | ||
|
|
ddad1cb8af | ||
|
|
8a17b61f4e | ||
|
|
7c66f76858 | ||
|
|
3d12bf7d14 | ||
|
|
851d0192ad | ||
|
|
42759be6ff | ||
|
|
0f1644e612 | ||
|
|
64b75d2806 | ||
|
|
4bfb62d7f4 | ||
|
|
6369c26671 | ||
|
|
71e0b00600 | ||
|
|
be65c14a6a | ||
|
|
7b2e72d395 | ||
|
|
a9297df89c | ||
|
|
4ba72e2d2d | ||
|
|
5f49db6c7f | ||
|
|
fc3c07f6de | ||
|
|
c7cba6d5ad | ||
|
|
6f624b29e4 | ||
|
|
f762173eb0 | ||
|
|
35ada9d636 | ||
|
|
81a4ba854c | ||
|
|
5758c7e175 | ||
|
|
ba2f8cdeef | ||
|
|
c2fa2de290 | ||
|
|
932aef887c | ||
|
|
bae4d4c673 | ||
|
|
6bf050e05c | ||
|
|
79349e6307 | ||
|
|
a77be7eb7b | ||
|
|
082714a436 | ||
|
|
06bb6f00e5 | ||
|
|
e4c875eb41 | ||
|
|
f0c1e65b2d | ||
|
|
b5fc6045fd | ||
|
|
d99dcc0d4b | ||
|
|
56750cba83 | ||
|
|
6e3303145f | ||
|
|
d8c0cc447f | ||
|
|
3ee999ae2a | ||
|
|
0bedbb4dd3 |
No files matched your search
+10
-1
@@ -8,6 +8,11 @@
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
# reverting this as e.g. /distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/
|
||||
# should not be ignored
|
||||
#bin/
|
||||
.factorypath
|
||||
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
@@ -54,4 +59,8 @@ target
|
||||
##########
|
||||
# KEYCLOAK-5391: We will re-exclude node_modules when node_modules handling is worked out.
|
||||
# For now, we keep our js libraries checked into GitHub, so we don't ignore.
|
||||
#node_modules
|
||||
#node_modules
|
||||
|
||||
# testsuite #
|
||||
#############
|
||||
*offline-token.txt
|
||||
+17
-2
@@ -1,5 +1,4 @@
|
||||
language: java
|
||||
dist: precise
|
||||
|
||||
cache:
|
||||
cache: false
|
||||
@@ -14,15 +13,31 @@ env:
|
||||
- TESTS=server-group2
|
||||
- TESTS=server-group3
|
||||
- TESTS=server-group4
|
||||
- TESTS=adapter-tests
|
||||
- TESTS=old
|
||||
- TESTS=crossdc
|
||||
- TESTS=crossdc-server
|
||||
- TESTS=crossdc-adapter
|
||||
- TESTS=ssl
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
install: true
|
||||
|
||||
before_install:
|
||||
- "export PHANTOMJS_VERSION=2.1.1"
|
||||
- "phantomjs --version"
|
||||
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
|
||||
- "phantomjs --version"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
|
||||
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
|
||||
- "phantomjs --version"
|
||||
|
||||
script:
|
||||
- ./travis-run-tests.sh $TESTS
|
||||
|
||||
after_success:
|
||||
- .travis/docker-hub-master-trigger.sh
|
||||
|
||||
sudo: false
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$DOCKER_HUB_TOKEN" != "" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_REPO_SLUG" == "keycloak/keycloak" ] && [ "$TRAVIS_BRANCH" == "master" ]; then
|
||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "master"}' -X POST https://registry.hub.docker.com/u/jboss/keycloak/trigger/$DOCKER_HUB_TOKEN/
|
||||
echo "Triggered Docker hub build"
|
||||
elif [ "$DOCKER_HUB_TOKEN" != "" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_REPO_SLUG" == "keycloak/keycloak" ] && [ "$TRAVIS_BRANCH" == "openshift-integration2" ]; then
|
||||
curl -H "Content-Type: application/json" --data '{"docker_tag": "nightly-openshift-integration2"}' -X POST https://registry.hub.docker.com/u/jboss/keycloak/trigger/$DOCKER_HUB_TOKEN/
|
||||
echo "Triggered Docker hub build"
|
||||
fi
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
ADOPTERS
|
||||
========
|
||||
|
||||
PUBLIC REFERENCES
|
||||
-----------------
|
||||
|
||||
List of organization names below is based on information collected using Keycloak Community Survey. It contains company names from participants who agreed to serve as public reference. If organizations would like get added or removed please make a PR.
|
||||
|
||||
* Accenture
|
||||
* Actinver
|
||||
* Akvo Foundation
|
||||
* Appier
|
||||
* BISPRO
|
||||
* Bluestem Brands, Inc
|
||||
* Bundesversicherungsamt
|
||||
* Capgemini
|
||||
* Chassi
|
||||
* CloudNative Inc.
|
||||
* Copenhagen Optimization
|
||||
* Curecomp GmbH
|
||||
* Cybertech
|
||||
* Devsu
|
||||
* Devoxx / Voxxed
|
||||
* DUENE e.V - project
|
||||
* DukeCon
|
||||
* European Synchrotron Radiation Facility
|
||||
* Fluance AG
|
||||
* Hewlett-Packard Enterprise
|
||||
* Hitachi
|
||||
* INEAT
|
||||
* Inventage
|
||||
* ISAAC
|
||||
* ITROI Solutions
|
||||
* Kindly Ops, LLC
|
||||
* msg systems ag
|
||||
* Netdava International
|
||||
* Ohio Supercomputer Center
|
||||
* Okta
|
||||
* PharmaPartners B.V.
|
||||
* Plivo
|
||||
* Price Insight
|
||||
* Prodesan
|
||||
* Quest Software
|
||||
* Research Industrial Software Engineering (RISE)
|
||||
* Sportsbet.com.au
|
||||
* Stack Labs
|
||||
* Storebrand
|
||||
* Synekus
|
||||
* Synetek Solutions
|
||||
* Taklane
|
||||
* TrackingSport
|
||||
* TRT9 - Brasil
|
||||
* UnitedHealthcare
|
||||
* ...More individuals
|
||||
@@ -32,6 +32,10 @@ To build the distribution run:
|
||||
|
||||
Once completed you will find distribution archives in `distribution`.
|
||||
|
||||
To build only the server run:
|
||||
|
||||
mvn -Pdistribution -pl distribution/server-dist -am -Dmaven.test.skip clean install
|
||||
|
||||
|
||||
Starting Keycloak
|
||||
-----------------
|
||||
@@ -40,6 +44,7 @@ To start Keycloak during development first build as specified above, then run:
|
||||
|
||||
mvn -f testsuite/utils/pom.xml exec:java -Pkeycloak-server
|
||||
|
||||
When running testsuite, by default an account with username `admin` and password `admin` will be created within the master realm at start.
|
||||
|
||||
To start Keycloak from the server distribution first build the distribution it as specified above, then run:
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -31,12 +31,24 @@
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.*
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
org.keycloak.*;version="${project.version}",
|
||||
org.apache.http.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.auth.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.client.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.conn.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.cookie.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.impl.auth.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.impl.client.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.impl.conn.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.impl.cookie.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.impl.execchain.*;version=${apache.httpcomponents.version},
|
||||
org.apache.http.*;version=${apache.httpcomponents.httpcore.version},
|
||||
org.apache.karaf.jaas.boot.principal;resolution:=optional,
|
||||
org.apache.karaf.jaas.modules;resolution:=optional,
|
||||
*;resolution:=optional
|
||||
@@ -52,7 +64,6 @@
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -98,15 +109,6 @@
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
||||
+20
@@ -307,6 +307,16 @@ public class AdapterDeploymentContext {
|
||||
delegate.setTokenStore(tokenStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdapterStateCookiePath() {
|
||||
return delegate.getAdapterStateCookiePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdapterStateCookiePath(String adapterStateCookiePath) {
|
||||
delegate.setAdapterStateCookiePath(adapterStateCookiePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStateCookieName() {
|
||||
return delegate.getStateCookieName();
|
||||
@@ -482,6 +492,16 @@ public class AdapterDeploymentContext {
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
||||
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerifyTokenAudience() {
|
||||
return delegate.isVerifyTokenAudience();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVerifyTokenAudience(boolean verifyTokenAudience) {
|
||||
delegate.setVerifyTokenAudience(verifyTokenAudience);
|
||||
}
|
||||
}
|
||||
|
||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||
|
||||
+11
@@ -102,6 +102,17 @@ public class AuthenticatedActionsHandler {
|
||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||
String exposeHeaders = deployment.getCorsExposedHeaders();
|
||||
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
if (exposeHeaders != null) {
|
||||
exposeHeaders += ",";
|
||||
} else {
|
||||
exposeHeaders = "";
|
||||
}
|
||||
|
||||
exposeHeaders += "WWW-Authenticate";
|
||||
}
|
||||
|
||||
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
||||
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
||||
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
|
||||
return authenticateToken(exchange, atr.getToken());
|
||||
}
|
||||
|
||||
private AccessTokenResponse getToken(String username, String password) throws Exception {
|
||||
protected AccessTokenResponse getToken(String username, String password) throws Exception {
|
||||
AccessTokenResponse tokenResponse=null;
|
||||
HttpClient client = deployment.getClient();
|
||||
|
||||
|
||||
+12
-4
@@ -18,7 +18,7 @@
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -72,8 +72,12 @@ public class BearerTokenRequestAuthenticator {
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
tokenString = split[1];
|
||||
if (split[0].equalsIgnoreCase("Bearer")) {
|
||||
tokenString = split[1];
|
||||
|
||||
log.debugf("Found [%d] values in authorization header, selecting the first value for Bearer.", (Integer) authHeaders.size());
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if (tokenString == null) {
|
||||
@@ -96,7 +100,7 @@ public class BearerTokenRequestAuthenticator {
|
||||
}
|
||||
}
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify token", e);
|
||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
|
||||
@@ -174,6 +178,10 @@ public class BearerTokenRequestAuthenticator {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade facade) {
|
||||
if (deployment.getPolicyEnforcer() != null) {
|
||||
deployment.getPolicyEnforcer().enforce(OIDCHttpFacade.class.cast(facade));
|
||||
return true;
|
||||
}
|
||||
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
|
||||
facade.getRequest().setError(error);
|
||||
facade.getResponse().addHeader("WWW-Authenticate", challenge);
|
||||
|
||||
+27
-6
@@ -19,7 +19,8 @@ package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
@@ -46,7 +47,7 @@ public class CookieTokenStore {
|
||||
.append(idToken).append(DELIM)
|
||||
.append(refreshToken).toString();
|
||||
|
||||
String cookiePath = getContextPath(facade);
|
||||
String cookiePath = getCookiePath(deployment, facade);
|
||||
facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
||||
}
|
||||
|
||||
@@ -71,7 +72,11 @@ public class CookieTokenStore {
|
||||
|
||||
try {
|
||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
||||
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
|
||||
TokenVerifier<AccessToken> tokenVerifier = AdapterTokenVerifier.createVerifier(accessTokenString, deployment, true, AccessToken.class)
|
||||
.checkActive(false)
|
||||
.verify();
|
||||
AccessToken accessToken = tokenVerifier.getToken();
|
||||
|
||||
IDToken idToken;
|
||||
if (idTokenString != null && idTokenString.length() > 0) {
|
||||
try {
|
||||
@@ -93,14 +98,30 @@ public class CookieTokenStore {
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeCookie(HttpFacade facade) {
|
||||
String cookiePath = getContextPath(facade);
|
||||
public static void removeCookie(KeycloakDeployment deployment, HttpFacade facade) {
|
||||
String cookiePath = getCookiePath(deployment, facade);
|
||||
facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath);
|
||||
}
|
||||
|
||||
private static String getContextPath(HttpFacade facade) {
|
||||
static String getCookiePath(KeycloakDeployment deployment, HttpFacade facade) {
|
||||
String path = deployment.getAdapterStateCookiePath() == null ? "" : deployment.getAdapterStateCookiePath().trim();
|
||||
if (path.startsWith("/")) {
|
||||
return path;
|
||||
}
|
||||
String contextPath = getContextPath(facade);
|
||||
StringBuilder cookiePath = new StringBuilder(contextPath);
|
||||
if (!contextPath.endsWith("/") && !path.isEmpty()) {
|
||||
cookiePath.append("/");
|
||||
}
|
||||
return cookiePath.append(path).toString();
|
||||
}
|
||||
|
||||
static String getContextPath(HttpFacade facade) {
|
||||
String uri = facade.getRequest().getURI();
|
||||
String path = KeycloakUriBuilder.fromUri(uri).getPath();
|
||||
if (path == null || path.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
int index = path.indexOf("/", 1);
|
||||
return index == -1 ? path : path.substring(0, index);
|
||||
}
|
||||
|
||||
+19
-1
@@ -70,6 +70,7 @@ public class KeycloakDeployment {
|
||||
protected SslRequired sslRequired = SslRequired.ALL;
|
||||
protected int confidentialPort = -1;
|
||||
protected TokenStore tokenStore = TokenStore.SESSION;
|
||||
protected String adapterStateCookiePath = "";
|
||||
protected String stateCookieName = "OAuth_Token_Request_State";
|
||||
protected boolean useResourceRoleMappings;
|
||||
protected boolean cors;
|
||||
@@ -92,10 +93,11 @@ public class KeycloakDeployment {
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
protected boolean ignoreOAuthQueryParameter;
|
||||
|
||||
|
||||
protected Map<String, String> redirectRewriteRules;
|
||||
|
||||
protected boolean delegateBearerErrorResponseSending = false;
|
||||
protected boolean verifyTokenAudience = false;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
@@ -296,6 +298,14 @@ public class KeycloakDeployment {
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
public String getAdapterStateCookiePath() {
|
||||
return adapterStateCookiePath;
|
||||
}
|
||||
|
||||
public void setAdapterStateCookiePath(String adapterStateCookiePath) {
|
||||
this.adapterStateCookiePath = adapterStateCookiePath;
|
||||
}
|
||||
|
||||
public String getStateCookieName() {
|
||||
return stateCookieName;
|
||||
}
|
||||
@@ -477,4 +487,12 @@ public class KeycloakDeployment {
|
||||
public void setDelegateBearerErrorResponseSending(boolean delegateBearerErrorResponseSending) {
|
||||
this.delegateBearerErrorResponseSending = delegateBearerErrorResponseSending;
|
||||
}
|
||||
|
||||
public boolean isVerifyTokenAudience() {
|
||||
return verifyTokenAudience;
|
||||
}
|
||||
|
||||
public void setVerifyTokenAudience(boolean verifyTokenAudience) {
|
||||
this.verifyTokenAudience = verifyTokenAudience;
|
||||
}
|
||||
}
|
||||
+4
@@ -88,6 +88,9 @@ public class KeycloakDeploymentBuilder {
|
||||
} else {
|
||||
deployment.setTokenStore(TokenStore.SESSION);
|
||||
}
|
||||
if (adapterConfig.getTokenCookiePath() != null) {
|
||||
deployment.setAdapterStateCookiePath(adapterConfig.getTokenCookiePath());
|
||||
}
|
||||
if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
|
||||
|
||||
deployment.setResourceCredentials(adapterConfig.getCredentials());
|
||||
@@ -122,6 +125,7 @@ public class KeycloakDeploymentBuilder {
|
||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
||||
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
|
||||
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
|
||||
deployment.setVerifyTokenAudience(adapterConfig.isVerifyTokenAudience());
|
||||
|
||||
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");
|
||||
|
||||
+4
-11
@@ -19,7 +19,7 @@ package org.keycloak.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
@@ -40,7 +40,6 @@ import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
/**
|
||||
@@ -359,15 +358,9 @@ public class OAuthRequestAuthenticator {
|
||||
}
|
||||
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
if (idTokenString != null) {
|
||||
try {
|
||||
JWSInput input = new JWSInput(idTokenString);
|
||||
idToken = input.readJsonContent(IDToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException();
|
||||
}
|
||||
}
|
||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString, deployment);
|
||||
token = tokens.getAccessToken();
|
||||
idToken = tokens.getIdToken();
|
||||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token: " + e.getMessage());
|
||||
|
||||
+24
-16
@@ -20,30 +20,27 @@ package org.keycloak.adapters;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
@@ -213,17 +210,19 @@ public class PreAuthActionsHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
JWSInput input = new JWSInput(token);
|
||||
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
|
||||
if (RSAProvider.verify(input, publicKey)) {
|
||||
return input;
|
||||
// Check just signature. Other things checked in validateAction
|
||||
TokenVerifier tokenVerifier = AdapterTokenVerifier.createVerifier(token, deployment, false, JsonWebToken.class);
|
||||
tokenVerifier.verify();
|
||||
return new JWSInput(token);
|
||||
} catch (VerificationException ignore) {
|
||||
log.warn("admin request failed, unable to verify token: " + ignore.getMessage());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(ignore.getMessage(), ignore);
|
||||
}
|
||||
} catch (JWSInputException ignore) {
|
||||
}
|
||||
|
||||
log.warn("admin request failed, unable to verify token");
|
||||
facade.getResponse().sendError(403, "no token");
|
||||
return null;
|
||||
facade.getResponse().sendError(403, "token failed verification");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +249,15 @@ public class PreAuthActionsHandler {
|
||||
protected void handleVersion() {
|
||||
try {
|
||||
facade.getResponse().setStatus(200);
|
||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (deployment.isCors()) {
|
||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||
if (origin == null) {
|
||||
log.debug("no origin header set in request");
|
||||
} else {
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
}
|
||||
}
|
||||
facade.getResponse().setHeader("Content-Type", "application/json");
|
||||
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), VersionRepresentation.SINGLETON);
|
||||
} catch (Exception e) {
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@ package org.keycloak.adapters;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
@@ -130,7 +130,8 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
||||
String tokenString = response.getToken();
|
||||
AccessToken token = null;
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, response.getIdToken(), deployment);
|
||||
token = tokens.getAccessToken();
|
||||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.keycloak.adapters.authentication;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client secret instead of private key .
|
||||
* See <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class);
|
||||
|
||||
public static final String PROVIDER_ID = "secret-jwt";
|
||||
|
||||
private SecretKey clientSecret;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(KeycloakDeployment deployment, Object config) {
|
||||
if (config == null || !(config instanceof Map)) {
|
||||
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
|
||||
}
|
||||
|
||||
Map<String, Object> cfg = (Map<String, Object>) config;
|
||||
String clientSecretString = (String) cfg.get("secret");
|
||||
if (clientSecretString == null) {
|
||||
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
|
||||
}
|
||||
setClientSecret(clientSecretString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
|
||||
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecretString) {
|
||||
// Get client secret and validate signature
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>
|
||||
// because it must be implemented in every java platform.
|
||||
try {
|
||||
clientSecret = new SecretKeySpec(clientSecretString.getBytes("UTF-8"), "HmacSHA256");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("Failed to create secret key spec due to unsupported encoding.");
|
||||
}
|
||||
}
|
||||
|
||||
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
|
||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||
// JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret.
|
||||
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
|
||||
// because it must be implemented in every java platform.
|
||||
return new JWSBuilder().jsonContent(jwt).hmac256(clientSecret);
|
||||
}
|
||||
|
||||
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
|
||||
// JWT claims is the same as one by private_key_jwt
|
||||
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.issuer(clientId);
|
||||
reqToken.subject(clientId);
|
||||
reqToken.audience(realmInfoUrl);
|
||||
|
||||
int now = Time.currentTime();
|
||||
reqToken.issuedAt(now);
|
||||
// the same as in KEYCLOAK-2986, JWTClientCredentialsProvider's timeout field
|
||||
reqToken.expiration(now + 10);
|
||||
reqToken.notBefore(now);
|
||||
return reqToken;
|
||||
}
|
||||
|
||||
}
|
||||
+155
-80
@@ -17,24 +17,30 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthorizationContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessToken.Authorization;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
@@ -43,76 +49,93 @@ import org.keycloak.representations.idm.authorization.Permission;
|
||||
public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private static final String HTTP_METHOD_DELETE = "DELETE";
|
||||
|
||||
private final PolicyEnforcer policyEnforcer;
|
||||
|
||||
private Map<String, PathConfig> paths;
|
||||
private AuthzClient authzClient;
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||
this.authzClient = policyEnforcer.getClient();
|
||||
this.pathMatcher = policyEnforcer.getPathMatcher();
|
||||
this.paths = policyEnforcer.getPaths();
|
||||
}
|
||||
|
||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
|
||||
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
Request request = httpFacade.getRequest();
|
||||
PathConfig pathConfig = getPathConfig(request);
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext != null) {
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
if (securityContext == null) {
|
||||
if (!isDefaultAccessDeniedUri(request)) {
|
||||
if (pathConfig != null) {
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
} else {
|
||||
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
|
||||
}
|
||||
} else {
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (accessToken != null) {
|
||||
Request request = httpFacade.getRequest();
|
||||
String path = getPath(request);
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
}
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
}
|
||||
|
||||
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
}
|
||||
|
||||
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
||||
if (!challenge(pathConfig, methodConfig, httpFacade)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
handleAccessDenied(httpFacade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,25 +144,24 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
protected abstract boolean challenge(PathConfig pathConfig, MethodConfig methodConfig, OIDCHttpFacade facade);
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
Request request = httpFacade.getRequest();
|
||||
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
if (isDefaultAccessDeniedUri(request)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||
Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
boolean hasPermission = false;
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
if (permission.getResourceSetId() != null) {
|
||||
for (Permission permission : grantedPermissions) {
|
||||
if (permission.getResourceId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
@@ -148,11 +170,14 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(methodConfig, permission)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||
this.paths.remove(actualPathConfig);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
return true;
|
||||
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
|
||||
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
|
||||
}
|
||||
|
||||
return hasValidClaims(permission, claims);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -167,24 +192,52 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, permissions);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasValidClaims(Permission permission, Map<String, List<String>> claims) {
|
||||
Map<String, Set<String>> grantedClaims = permission.getClaims();
|
||||
|
||||
if (grantedClaims != null) {
|
||||
if (claims.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
|
||||
List<String> requestClaims = claims.get(entry.getKey());
|
||||
|
||||
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
|
||||
httpFacade.getResponse().sendError(403);
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return policyEnforcer.getClient();
|
||||
}
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
if (request.getURI().contains(accessDeniedPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return policyEnforcer.getEnforcerConfig();
|
||||
}
|
||||
|
||||
protected PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
|
||||
@@ -212,20 +265,8 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return requiredScopes.isEmpty();
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return this.authzClient;
|
||||
}
|
||||
|
||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
protected PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new ClientAuthorizationContext(authzClient) {
|
||||
return new ClientAuthorizationContext(getAuthzClient()) {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
@@ -269,14 +310,22 @@ public abstract class AbstractPolicyEnforcer {
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
|
||||
methodConfig.setMethod(request.getMethod());
|
||||
methodConfig.setScopes(pathConfig.getScopes());
|
||||
List scopes = new ArrayList<>();
|
||||
|
||||
if (Boolean.TRUE.equals(getEnforcerConfig().getHttpMethodAsScope())) {
|
||||
scopes.add(request.getMethod());
|
||||
} else {
|
||||
scopes.addAll(pathConfig.getScopes());
|
||||
}
|
||||
|
||||
methodConfig.setScopes(scopes);
|
||||
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
|
||||
|
||||
return methodConfig;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
@@ -292,6 +341,32 @@ public abstract class AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
return permission.getResourceId().equals(actualPathConfig.getId());
|
||||
}
|
||||
|
||||
private PathConfig getPathConfig(Request request) {
|
||||
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
|
||||
}
|
||||
|
||||
protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, OIDCHttpFacade httpFacade) {
|
||||
Map<String, List<String>> claims = getClaims(getEnforcerConfig().getClaimInformationPointConfig(), httpFacade);
|
||||
|
||||
claims.putAll(getClaims(pathConfig.getClaimInformationPointConfig(), httpFacade));
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getClaims(Map<String, Map<String, Object>>claimInformationPointConfig, HttpFacade httpFacade) {
|
||||
if (claimInformationPointConfig != null) {
|
||||
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
|
||||
ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
|
||||
|
||||
if (factory != null) {
|
||||
return factory.create(claimDef.getValue()).resolve(httpFacade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* 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.authorization;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(BearerTokenPolicyEnforcer.class);
|
||||
|
||||
public BearerTokenPolicyEnforcer(PolicyEnforcer enforcer) {
|
||||
super(enforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
challengeUmaAuthentication(pathConfig, methodConfig, facade);
|
||||
} else {
|
||||
challengeEntitlementAuthentication(facade);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending Entitlement challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private void challengeUmaAuthentication(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient);
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending UMA challenge");
|
||||
}
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
return permission.forResource(permissionRequest).getTicket();
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProvider {
|
||||
|
||||
Map<String, List<String>> resolve(HttpFacade httpFacade);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface ClaimInformationPointProviderFactory<C extends ClaimInformationPointProvider> {
|
||||
|
||||
String getName();
|
||||
|
||||
void init(PolicyEnforcer policyEnforcer);
|
||||
|
||||
C create(Map<String, Object> config);
|
||||
}
|
||||
+124
-58
@@ -18,26 +18,30 @@
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
|
||||
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
import org.keycloak.representations.idm.authorization.PermissionRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
@@ -51,14 +55,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
AccessToken original = accessToken;
|
||||
|
||||
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
|
||||
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade);
|
||||
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade, claims);
|
||||
|
||||
if (accessToken == null) {
|
||||
return false;
|
||||
@@ -74,17 +78,45 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
||||
Collection<Permission> grantedPermissions = authorization.getPermissions();
|
||||
Collection<Permission> newPermissions = newAuthorization.getPermissions();
|
||||
|
||||
for (Permission newPermission : newPermissions) {
|
||||
if (!grantedPermissions.contains(newPermission)) {
|
||||
grantedPermissions.add(newPermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade);
|
||||
return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade, claims);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade facade) {
|
||||
handleAccessDenied(facade);
|
||||
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
if (isBearerAuthorization(httpFacade)) {
|
||||
HttpFacade.Response response = httpFacade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);
|
||||
|
||||
if (ticket != null) {
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
|
||||
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
|
||||
} else {
|
||||
response.setStatus(403);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending challenge");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,52 +133,86 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
}
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
|
||||
try {
|
||||
String accessToken = httpFacade.getSecurityContext().getTokenString();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
|
||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.debug("Obtaining entitlements for authenticated user.");
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||
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);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
} catch (AuthorizationDeniedException e) {
|
||||
LOGGER.debug("Authorization denied", e);
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade, Map<String, List<String>> claims) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
String accessTokenString = securityContext.getTokenString();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest();
|
||||
|
||||
if (isBearerAuthorization(httpFacade) || accessToken.getAuthorization() != null) {
|
||||
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
|
||||
}
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
|
||||
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
|
||||
}
|
||||
|
||||
if (accessToken.getAuthorization() != null) {
|
||||
authzRequest.setRpt(accessTokenString);
|
||||
}
|
||||
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
AuthorizationResponse authzResponse;
|
||||
|
||||
if (isBearerAuthorization(httpFacade)) {
|
||||
authzRequest.setSubjectToken(accessTokenString);
|
||||
authzResponse = getAuthzClient().authorization().authorize(authzRequest);
|
||||
} else {
|
||||
authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
|
||||
}
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterTokenVerifier.verifyToken(authzResponse.getToken(), deployment);
|
||||
}
|
||||
} catch (AuthorizationDeniedException ignore) {
|
||||
LOGGER.debug("Authorization denied", ignore);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected error during authorization request.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, OIDCHttpFacade httpFacade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceId(pathConfig.getId());
|
||||
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
|
||||
|
||||
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
|
||||
|
||||
if (!claims.isEmpty()) {
|
||||
permissionRequest.setClaims(claims);
|
||||
}
|
||||
|
||||
return permission.create(permissionRequest).getTicket();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
|
||||
List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
|
||||
|
||||
if (authHeaders != null) {
|
||||
for (String authHeader : authHeaders) {
|
||||
String[] split = authHeader.trim().split("\\s+");
|
||||
if (split == null || split.length != 2) continue;
|
||||
if (!split[0].equalsIgnoreCase("Bearer")) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return getPolicyEnforcer().getDeployment().isBearerOnly();
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -21,9 +21,12 @@ import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* A simple LRU cache implementation supporting expiration and maximum number of entries.
|
||||
*
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PathCache {
|
||||
@@ -38,15 +41,7 @@ public class PathCache {
|
||||
private final AtomicBoolean writing = new AtomicBoolean(false);
|
||||
|
||||
private final long maxAge;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param maxEntries the maximum number of entries to keep in the cache
|
||||
*/
|
||||
public PathCache(int maxEntries) {
|
||||
this(maxEntries, -1);
|
||||
}
|
||||
private final boolean enabled;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
@@ -62,9 +57,14 @@ public class PathCache {
|
||||
}
|
||||
};
|
||||
this.maxAge = maxAge;
|
||||
this.enabled = maxAge > 0;
|
||||
}
|
||||
|
||||
public void put(String uri, PathConfig newValue) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (parkForWriteAndCheckInterrupt()) {
|
||||
return;
|
||||
@@ -80,6 +80,10 @@ public class PathCache {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsKey(String uri) {
|
||||
return cache.containsKey(uri);
|
||||
}
|
||||
|
||||
public PathConfig get(String uri) {
|
||||
if (parkForReadAndCheckInterrupt()) {
|
||||
return null;
|
||||
@@ -151,7 +155,7 @@ public class PathCache {
|
||||
if(maxAge == -1) {
|
||||
expiration = -1;
|
||||
} else {
|
||||
expiration = System.currentTimeMillis() + maxAge;
|
||||
expiration = Time.currentTimeMillis() + maxAge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +168,7 @@ public class PathCache {
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return expiration != -1 ? System.currentTimeMillis() > expiration : false;
|
||||
return expiration != -1 ? Time.currentTimeMillis() > expiration : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* 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.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
class PathMatcher {
|
||||
|
||||
private static final char WILDCARD = '*';
|
||||
private final AuthzClient authzClient;
|
||||
// TODO: make this configurable
|
||||
private PathCache cache = new PathCache(100, 30000);
|
||||
|
||||
public PathMatcher(AuthzClient authzClient) {
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) {
|
||||
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri);
|
||||
|
||||
if (pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
PathConfig matchingAnyPath = null;
|
||||
PathConfig matchingAnySuffixPath = null;
|
||||
|
||||
for (PathConfig entry : paths.values()) {
|
||||
String expectedUri = entry.getPath();
|
||||
String matchingUri = null;
|
||||
|
||||
if (exactMatch(expectedUri, targetUri, expectedUri)) {
|
||||
matchingUri = expectedUri;
|
||||
}
|
||||
|
||||
if (isTemplate(expectedUri)) {
|
||||
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
|
||||
|
||||
if (templateUri != null) {
|
||||
if (exactMatch(expectedUri, targetUri, templateUri)) {
|
||||
matchingUri = templateUri;
|
||||
entry = resolvePathConfig(entry, targetUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingUri != null) {
|
||||
StringBuilder path = new StringBuilder(expectedUri);
|
||||
int patternIndex = path.indexOf("/" + WILDCARD);
|
||||
|
||||
if (patternIndex != -1) {
|
||||
path.delete(patternIndex, path.length());
|
||||
}
|
||||
|
||||
patternIndex = path.indexOf("{");
|
||||
|
||||
if (patternIndex != -1) {
|
||||
path.delete(patternIndex, path.length());
|
||||
}
|
||||
|
||||
String pathString = path.toString();
|
||||
|
||||
if ("".equals(pathString)) {
|
||||
pathString = "/";
|
||||
}
|
||||
|
||||
if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) {
|
||||
cache.put(targetUri, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) {
|
||||
matchingAnyPath = entry;
|
||||
} else {
|
||||
int suffixIndex = expectedUri.indexOf(WILDCARD + ".");
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
String protectedSuffix = expectedUri.substring(suffixIndex + 1);
|
||||
|
||||
if (targetUri.endsWith(protectedSuffix)) {
|
||||
matchingAnySuffixPath = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingAnySuffixPath != null) {
|
||||
cache.put(targetUri, matchingAnySuffixPath);
|
||||
return matchingAnySuffixPath;
|
||||
}
|
||||
|
||||
if (matchingAnyPath != null) {
|
||||
cache.put(targetUri, matchingAnyPath);
|
||||
}
|
||||
|
||||
return matchingAnyPath;
|
||||
}
|
||||
|
||||
private boolean exactMatch(String expectedUri, String targetUri, String value) {
|
||||
if (targetUri.equals(value)) {
|
||||
return value.equals(targetUri);
|
||||
}
|
||||
|
||||
if (endsWithWildcard(expectedUri)) {
|
||||
return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
|
||||
}
|
||||
|
||||
String suffix = "/*.";
|
||||
int suffixIndex = expectedUri.indexOf(suffix);
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String buildUriFromTemplate(String expectedUri, String targetUri) {
|
||||
int patternStartIndex = expectedUri.indexOf("{");
|
||||
|
||||
if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (expectedUri.split("/").length > targetUri.split("/").length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
char[] expectedUriChars = expectedUri.toCharArray();
|
||||
char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
|
||||
int matchingUriLastIndex = matchingUri.length;
|
||||
String targetUriParams = targetUri.substring(patternStartIndex);
|
||||
|
||||
if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
|
||||
matchingUri = Arrays.copyOf(matchingUri, targetUri.length());
|
||||
int paramIndex = 0;
|
||||
|
||||
for (int i = patternStartIndex; i < expectedUriChars.length; i++) {
|
||||
if (matchingUriLastIndex >= matchingUri.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
char c = expectedUriChars[i];
|
||||
|
||||
if (c == '{' || c == '*') {
|
||||
String[] params = targetUriParams.split("/");
|
||||
|
||||
for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) {
|
||||
if (k == params.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
int paramLength = params[k].length();
|
||||
|
||||
if (matchingUriLastIndex + paramLength > matchingUri.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int j = 0; j < paramLength; j++) {
|
||||
matchingUri[matchingUriLastIndex++] = params[k].charAt(j);
|
||||
}
|
||||
|
||||
if (c == '*' && matchingUriLastIndex < matchingUri.length) {
|
||||
matchingUri[matchingUriLastIndex++] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
i = expectedUri.indexOf('}', i);
|
||||
} else {
|
||||
if (c == '/') {
|
||||
paramIndex++;
|
||||
}
|
||||
matchingUri[matchingUriLastIndex++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingUri[matchingUri.length - 1] == '\u0000') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return String.valueOf(matchingUri);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean endsWithWildcard(String expectedUri) {
|
||||
return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
|
||||
}
|
||||
|
||||
private boolean isTemplate(String uri) {
|
||||
return uri.indexOf("{") != -1;
|
||||
}
|
||||
|
||||
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = this.authzClient.protection().resource();
|
||||
Set<String> search = resource.findByFilter("uri=" + path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return originalConfig;
|
||||
}
|
||||
}
|
||||
+181
-89
@@ -17,6 +17,17 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
@@ -25,22 +36,14 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.ClientAuthenticator;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.common.util.PathMatcher;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
@@ -52,20 +55,27 @@ public class PolicyEnforcer {
|
||||
private final KeycloakDeployment deployment;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final PathConfigMatcher pathMatcher;
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathMatcher pathMatcher;
|
||||
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() {
|
||||
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient());
|
||||
this.authzClient = AuthzClient.create(configuration, new ClientAuthenticator() {
|
||||
@Override
|
||||
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
|
||||
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
|
||||
Map<String, String> formparams = new HashMap<>();
|
||||
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, formparams);
|
||||
for (Entry<String, String> param : formparams.entrySet()) {
|
||||
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
|
||||
}
|
||||
}
|
||||
});
|
||||
this.pathMatcher = new PathMatcher(this.authzClient);
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
|
||||
paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configurations:");
|
||||
@@ -73,20 +83,17 @@ public class PolicyEnforcer {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
|
||||
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enable. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
||||
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context;
|
||||
|
||||
if (deployment.isBearerOnly()) {
|
||||
context = new BearerTokenPolicyEnforcer(this).authorize(facade);
|
||||
} else {
|
||||
context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
|
||||
}
|
||||
AuthorizationContext context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
@@ -99,11 +106,11 @@ public class PolicyEnforcer {
|
||||
return context;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig getEnforcerConfig() {
|
||||
public PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
AuthzClient getClient() {
|
||||
public AuthzClient getClient() {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
@@ -111,14 +118,30 @@ public class PolicyEnforcer {
|
||||
return paths;
|
||||
}
|
||||
|
||||
void addPath(PathConfig pathConfig) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
public PathConfigMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
|
||||
KeycloakDeployment getDeployment() {
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
|
||||
return claimInformationPointProviderFactories;
|
||||
}
|
||||
|
||||
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
|
||||
Iterator<ClaimInformationPointProviderFactory> iterator = loader.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
ClaimInformationPointProviderFactory factory = iterator.next();
|
||||
|
||||
factory.init(this);
|
||||
|
||||
claimInformationPointProviderFactories.put(factory.getName(), factory);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
boolean loadPathsFromServer = true;
|
||||
|
||||
@@ -131,7 +154,11 @@ public class PolicyEnforcer {
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
return configureAllPathsForResourceServer(protectedResource);
|
||||
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
|
||||
|
||||
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
|
||||
|
||||
return paths;
|
||||
} else {
|
||||
LOGGER.info("Paths provided in configuration.");
|
||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||
@@ -139,56 +166,41 @@ public class PolicyEnforcer {
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
Set<String> search;
|
||||
ResourceRepresentation resource;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
search = protectedResource.findByFilter("name=" + resourceName);
|
||||
resource = protectedResource.findByName(resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
search = protectedResource.findByFilter("uri=" + path);
|
||||
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
|
||||
|
||||
if (resources.isEmpty()) {
|
||||
resources = protectedResource.findByMatchingUri(path);
|
||||
}
|
||||
|
||||
if (resources.size() == 1) {
|
||||
resource = resources.get(0);
|
||||
} else if (resources.size() > 1) {
|
||||
throw new RuntimeException("Multiple resources found with the same uri");
|
||||
} else {
|
||||
resource = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (search.isEmpty()) {
|
||||
if (enforcerConfig.isCreateResources()) {
|
||||
LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
|
||||
resource.setName(resourceName);
|
||||
resource.setType(pathConfig.getType());
|
||||
resource.setUri(path);
|
||||
|
||||
HashSet<ScopeRepresentation> scopes = new HashSet<>();
|
||||
|
||||
for (String scopeName : pathConfig.getScopes()) {
|
||||
ScopeRepresentation scope = new ScopeRepresentation();
|
||||
|
||||
scope.setName(scopeName);
|
||||
|
||||
scopes.add(scope);
|
||||
}
|
||||
|
||||
resource.setScopes(scopes);
|
||||
|
||||
RegistrationResponse registrationResponse = protectedResource.create(resource);
|
||||
|
||||
pathConfig.setId(registrationResponse.getId());
|
||||
} else {
|
||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
|
||||
}
|
||||
} else {
|
||||
pathConfig.setId(search.iterator().next());
|
||||
if (resource != null) {
|
||||
pathConfig.setId(resource.getId());
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
||||
if (current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
@@ -209,46 +221,126 @@ public class PolicyEnforcer {
|
||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
for (String id : protectedResource.findAll()) {
|
||||
RegistrationResponse response = protectedResource.findById(id);
|
||||
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
||||
if (!enforcerConfig.getLazyLoadPaths()) {
|
||||
for (String id : protectedResource.findAll()) {
|
||||
ResourceRepresentation resourceDescription = protectedResource.findById(id);
|
||||
|
||||
if (resourceDescription.getUri() != null) {
|
||||
PathConfig pathConfig = createPathConfig(resourceDescription);
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
if (resourceDescription.getUris() != null && !resourceDescription.getUris().isEmpty()) {
|
||||
for(PathConfig pathConfig : PathConfig.createPathConfigs(resourceDescription)) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
||||
PathConfig pathConfig = new PathConfig();
|
||||
public class PathConfigMatcher extends PathMatcher<PathConfig> {
|
||||
|
||||
pathConfig.setId(resourceDescription.getId());
|
||||
pathConfig.setName(resourceDescription.getName());
|
||||
private final Map<String, PathConfig> paths;
|
||||
private final PathCache pathCache;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
|
||||
String uri = resourceDescription.getUri();
|
||||
public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
|
||||
this.paths = paths;
|
||||
this.enforcerConfig = enforcerConfig;
|
||||
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
|
||||
|
||||
if (uri == null || "".equals(uri.trim())) {
|
||||
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
|
||||
if (cacheConfig == null) {
|
||||
cacheConfig = new PathCacheConfig();
|
||||
}
|
||||
|
||||
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
|
||||
this.authzClient = authzClient;
|
||||
}
|
||||
|
||||
pathConfig.setPath(uri);
|
||||
@Override
|
||||
public PathConfig matches(String targetUri) {
|
||||
PathConfig pathConfig = pathCache.get(targetUri);
|
||||
|
||||
List<String> scopeNames = new ArrayList<>();
|
||||
if (pathCache.containsKey(targetUri) || pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
||||
scopeNames.add(scope.getName());
|
||||
pathConfig = super.matches(targetUri);
|
||||
|
||||
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
|
||||
if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
|
||||
try {
|
||||
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
|
||||
|
||||
if (!matchingResources.isEmpty()) {
|
||||
Map<String, Map<String, Object>> cipConfig = null;
|
||||
|
||||
if (pathConfig != null) {
|
||||
cipConfig = pathConfig.getClaimInformationPointConfig();
|
||||
}
|
||||
|
||||
pathConfig = PathConfig.createPathConfigs(matchingResources.get(0)).iterator().next();
|
||||
|
||||
if (cipConfig != null) {
|
||||
pathConfig.setClaimInformationPointConfig(cipConfig);
|
||||
}
|
||||
}
|
||||
} catch (Exception cause) {
|
||||
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCache.put(targetUri, pathConfig);
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
pathConfig.setScopes(scopeNames);
|
||||
pathConfig.setType(resourceDescription.getType());
|
||||
@Override
|
||||
protected String getPath(PathConfig entry) {
|
||||
return entry.getPath();
|
||||
}
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
@Override
|
||||
protected Collection<PathConfig> getPaths() {
|
||||
return paths.values();
|
||||
}
|
||||
|
||||
public PathMatcher getPathMatcher() {
|
||||
return pathMatcher;
|
||||
}
|
||||
@Override
|
||||
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = authzClient.protection().resource();
|
||||
|
||||
// search by an exact match
|
||||
List<ResourceRepresentation> search = resource.findByUri(path);
|
||||
|
||||
// if exact match not found, try to obtain from current path the parent path.
|
||||
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
|
||||
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
|
||||
// so any permission granted to parent also applies to sub resources
|
||||
if (search.isEmpty()) {
|
||||
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
|
||||
}
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
ResourceRepresentation targetResource = search.get(0);
|
||||
PathConfig config = PathConfig.createPathConfigs(targetResource).iterator().next();
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeFromCache(String pathConfig) {
|
||||
pathCache.remove(pathConfig);
|
||||
}
|
||||
};
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.cip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
|
||||
public ClaimsInformationPointProvider(Map<String, Object> config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
|
||||
for (Entry<String, Object> configEntry : config.entrySet()) {
|
||||
String claimName = configEntry.getKey();
|
||||
Object claimValue = configEntry.getValue();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (claimValue instanceof String) {
|
||||
values = getValues(claimValue.toString(), httpFacade);
|
||||
} else if (claimValue instanceof Collection) {
|
||||
Iterator iterator = Collection.class.cast(claimValue).iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
List<String> resolvedValues = getValues(iterator.next().toString(), httpFacade);
|
||||
|
||||
if (!resolvedValues.isEmpty()) {
|
||||
values.addAll(resolvedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.isEmpty()) {
|
||||
claims.put(claimName, values);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private List<String> getValues(String value, HttpFacade httpFacade) {
|
||||
return PlaceHolders.resolve(value, httpFacade);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class ClaimsInformationPointProviderFactory implements ClaimInformationPointProviderFactory<ClaimsInformationPointProvider> {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "claims";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClaimsInformationPointProvider create(Map<String, Object> config) {
|
||||
return new ClaimsInformationPointProvider(config);
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.cip;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.authorization.util.JsonUtils;
|
||||
import org.keycloak.adapters.authorization.util.PlaceHolders;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.util.HttpResponseException;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProvider implements ClaimInformationPointProvider {
|
||||
|
||||
private final Map<String, Object> config;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public HttpClaimInformationPointProvider(Map<String, Object> config, PolicyEnforcer policyEnforcer) {
|
||||
this.config = config;
|
||||
this.httpClient = policyEnforcer.getDeployment().getClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
|
||||
try {
|
||||
InputStream responseStream = executeRequest(httpFacade);
|
||||
|
||||
try (InputStream inputStream = new BufferedInputStream(responseStream)) {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
|
||||
Map<String, List<String>> claims = new HashMap<>();
|
||||
Map<String, Object> claimsDef = (Map<String, Object>) config.get("claims");
|
||||
|
||||
if (claimsDef == null) {
|
||||
Iterator<String> nodeNames = jsonNode.fieldNames();
|
||||
|
||||
while (nodeNames.hasNext()) {
|
||||
String nodeName = nodeNames.next();
|
||||
claims.put(nodeName, JsonUtils.getValues(jsonNode.get(nodeName)));
|
||||
}
|
||||
} else {
|
||||
for (Entry<String, Object> claimDef : claimsDef.entrySet()) {
|
||||
List<String> jsonPaths = new ArrayList<>();
|
||||
|
||||
if (claimDef.getValue() instanceof Collection) {
|
||||
jsonPaths.addAll(Collection.class.cast(claimDef.getValue()));
|
||||
} else {
|
||||
jsonPaths.add(claimDef.getValue().toString());
|
||||
}
|
||||
|
||||
List<String> claimValues = new ArrayList<>();
|
||||
|
||||
for (String path : jsonPaths) {
|
||||
claimValues.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
|
||||
claims.put(claimDef.getKey(), claimValues);
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not obtain claims from http claim information point [" + config.get("url") + "] response", cause);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream executeRequest(HttpFacade httpFacade) {
|
||||
String method = config.get("method").toString();
|
||||
|
||||
if (method == null) {
|
||||
method = "GET";
|
||||
}
|
||||
|
||||
RequestBuilder builder = null;
|
||||
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
builder = RequestBuilder.get();
|
||||
} else {
|
||||
builder = RequestBuilder.post();
|
||||
}
|
||||
|
||||
builder.setUri(config.get("url").toString());
|
||||
|
||||
byte[] bytes = new byte[0];
|
||||
|
||||
try {
|
||||
setParameters(builder, httpFacade);
|
||||
|
||||
if (config.containsKey("headers")) {
|
||||
setHeaders(builder, httpFacade);
|
||||
}
|
||||
|
||||
HttpResponse response = httpClient.execute(builder.build());
|
||||
HttpEntity entity = response.getEntity();
|
||||
|
||||
if (entity != null) {
|
||||
bytes = EntityUtils.toByteArray(entity);
|
||||
}
|
||||
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
int statusCode = statusLine.getStatusCode();
|
||||
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(bytes);
|
||||
} catch (Exception cause) {
|
||||
try {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]. Response : " + StreamUtil.readString(new ByteArrayInputStream(bytes), Charset.forName("UTF-8")), cause);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error executing http method [" + builder + "]", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
Object headersDef = config.get("headers");
|
||||
|
||||
if (headersDef != null) {
|
||||
Map<String, Object> headers = Map.class.cast(headersDef);
|
||||
|
||||
for (Entry<String, Object> header : headers.entrySet()) {
|
||||
Object value = header.getValue();
|
||||
List<String> headerValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
Iterator iterator = values.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
headerValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
|
||||
}
|
||||
} else {
|
||||
headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
}
|
||||
|
||||
for (String headerValue : headerValues) {
|
||||
builder.addHeader(header.getKey(), headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
|
||||
Object config = this.config.get("parameters");
|
||||
|
||||
if (config != null) {
|
||||
Map<String, Object> paramsDef = Map.class.cast(config);
|
||||
|
||||
for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
|
||||
Object value = paramDef.getValue();
|
||||
List<String> paramValues = new ArrayList<>();
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection values = Collection.class.cast(value);
|
||||
Iterator iterator = values.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
paramValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
|
||||
}
|
||||
} else {
|
||||
paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
|
||||
}
|
||||
|
||||
for (String paramValue : paramValues) {
|
||||
builder.addParameter(paramDef.getKey(), paramValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.cip;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class HttpClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<HttpClaimInformationPointProvider> {
|
||||
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClaimInformationPointProvider create(Map<String, Object> config) {
|
||||
return new HttpClaimInformationPointProvider(config, policyEnforcer);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode, String path) {
|
||||
return getValues(jsonNode.at(path));
|
||||
}
|
||||
|
||||
public static List<String> getValues(JsonNode jsonNode) {
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
if (jsonNode.isArray()) {
|
||||
Iterator<JsonNode> iterator = jsonNode.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String value = iterator.next().textValue();
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String value = jsonNode.textValue();
|
||||
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
public static final String NAME = "keycloak";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
|
||||
KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source.endsWith("access_token")) {
|
||||
return Arrays.asList(securityContext.getTokenString());
|
||||
}
|
||||
|
||||
if (source.endsWith("id_token")) {
|
||||
return Arrays.asList(securityContext.getIdTokenString());
|
||||
}
|
||||
|
||||
JsonNode jsonNode;
|
||||
|
||||
if (source.startsWith("access_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
|
||||
} else if (source.startsWith("id_token[")) {
|
||||
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
|
||||
} else {
|
||||
throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
|
||||
}
|
||||
|
||||
return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public interface PlaceHolderResolver {
|
||||
|
||||
List<String> resolve(String placeHolder, HttpFacade httpFacade);
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PlaceHolders {
|
||||
|
||||
private static Map<String, PlaceHolderResolver> resolvers = new HashMap<>();
|
||||
|
||||
static {
|
||||
resolvers.put(RequestPlaceHolderResolver.NAME, new RequestPlaceHolderResolver());
|
||||
resolvers.put(KeycloakSecurityContextPlaceHolderResolver.NAME, new KeycloakSecurityContextPlaceHolderResolver());
|
||||
}
|
||||
|
||||
private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
|
||||
private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
|
||||
|
||||
public static List<String> resolve(String value, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
|
||||
|
||||
if (!placeHolders.isEmpty()) {
|
||||
value = formatPlaceHolder(value);
|
||||
|
||||
for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
|
||||
List<String> values = entry.getValue();
|
||||
|
||||
if (values.isEmpty() || values.size() > 1) {
|
||||
return values;
|
||||
}
|
||||
|
||||
value = value.replaceAll(entry.getKey(), values.get(0)).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
|
||||
static String getParameter(String source, String messageIfNotFound) {
|
||||
Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);
|
||||
|
||||
while (matcher.find()) {
|
||||
return matcher.group(1).replaceAll("'", "");
|
||||
}
|
||||
|
||||
if (messageIfNotFound != null) {
|
||||
throw new RuntimeException(messageIfNotFound);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
|
||||
Map<String, List<String>> placeHolders = new HashMap<>();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
|
||||
|
||||
while (matcher.find()) {
|
||||
String placeHolder = matcher.group(1);
|
||||
int resolverNameIdx = placeHolder.indexOf('.');
|
||||
|
||||
if (resolverNameIdx == -1) {
|
||||
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
|
||||
}
|
||||
|
||||
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
|
||||
|
||||
if (resolver != null) {
|
||||
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
|
||||
|
||||
if (resolved != null) {
|
||||
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return placeHolders;
|
||||
}
|
||||
|
||||
private static String formatPlaceHolder(String placeHolder) {
|
||||
return placeHolder.replaceAll("\\{", "").replace("}", "").replace("[", "").replace("]", "").replace("[", "").replace("]", "");
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2018 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.authorization.util;
|
||||
|
||||
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Cookie;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class RequestPlaceHolderResolver implements PlaceHolderResolver {
|
||||
|
||||
static String NAME = "request";
|
||||
|
||||
@Override
|
||||
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
|
||||
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
|
||||
Request request = httpFacade.getRequest();
|
||||
|
||||
if (source.startsWith("parameter")) {
|
||||
String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
|
||||
String parameterValue = request.getQueryParamValue(parameterName);
|
||||
|
||||
if (parameterValue == null) {
|
||||
parameterValue = request.getFirstParam(parameterName);
|
||||
}
|
||||
|
||||
if (parameterValue != null) {
|
||||
return Arrays.asList(parameterValue);
|
||||
}
|
||||
} else if (source.startsWith("header")) {
|
||||
String headerName = getParameter(source, "Could not obtain header name from placeholder [" + source + "]");
|
||||
List<String> headerValue = request.getHeaders(headerName);
|
||||
|
||||
if (headerValue != null) {
|
||||
return headerValue;
|
||||
}
|
||||
} else if (source.startsWith("cookie")) {
|
||||
String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
|
||||
Cookie cookieValue = request.getCookie(cookieName);
|
||||
|
||||
if (cookieValue != null) {
|
||||
return Arrays.asList(cookieValue.getValue());
|
||||
}
|
||||
} else if (source.startsWith("remoteAddr")) {
|
||||
String value = request.getRemoteAddr();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("method")) {
|
||||
String value = request.getMethod();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("uri")) {
|
||||
String value = request.getURI();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("relativePath")) {
|
||||
String value = request.getRelativePath();
|
||||
|
||||
if (value != null) {
|
||||
return Arrays.asList(value);
|
||||
}
|
||||
} else if (source.startsWith("secure")) {
|
||||
return Arrays.asList(String.valueOf(request.isSecure()));
|
||||
} else if (source.startsWith("body")) {
|
||||
String contentType = request.getHeader("Content-Type");
|
||||
|
||||
if (contentType == null) {
|
||||
contentType = "";
|
||||
}
|
||||
|
||||
InputStream body = request.getInputStream(true);
|
||||
|
||||
try {
|
||||
if (body == null || body.available() == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to check available bytes in request input stream", cause);
|
||||
}
|
||||
|
||||
if (body.markSupported()) {
|
||||
body.mark(0);
|
||||
}
|
||||
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
try {
|
||||
switch (contentType) {
|
||||
case "application/json":
|
||||
try {
|
||||
JsonNode jsonNode = JsonSerialization.mapper.readTree(new BufferedInputStream(body) {
|
||||
@Override
|
||||
public void close() {
|
||||
// we can't close the stream because it may be used later by the application
|
||||
}
|
||||
});
|
||||
String path = getParameter(source, null);
|
||||
|
||||
if (path == null) {
|
||||
values.addAll(JsonUtils.getValues(jsonNode));
|
||||
} else {
|
||||
values.addAll(JsonUtils.getValues(jsonNode, path));
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request JSON body", cause);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
StringBuilder value = new StringBuilder();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(body));
|
||||
|
||||
try {
|
||||
int ch;
|
||||
|
||||
while ((ch = reader.read()) != -1) {
|
||||
value.append((char) ch);
|
||||
}
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Could not extract claim from request body", cause);
|
||||
}
|
||||
|
||||
values.add(value.toString());
|
||||
}
|
||||
} finally {
|
||||
if (body.markSupported()) {
|
||||
try {
|
||||
body.reset();
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to reset request input stream", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -23,7 +23,7 @@ import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.FindFile;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
@@ -202,8 +202,16 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||
|
||||
|
||||
protected Auth bearerAuth(String tokenString) throws VerificationException {
|
||||
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);
|
||||
return postTokenVerification(tokenString, token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after accessToken was verified (including signature, expiration etc)
|
||||
*
|
||||
*/
|
||||
protected Auth postTokenVerification(String tokenString, AccessToken token) {
|
||||
boolean verifyCaller;
|
||||
if (deployment.isUseResourceRoleMappings()) {
|
||||
verifyCaller = token.isVerifyCaller(deployment.getResourceName());
|
||||
|
||||
+11
-1
@@ -27,6 +27,7 @@ import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
@@ -56,11 +57,15 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
||||
|
||||
private static final Logger log = Logger.getLogger(DirectAccessGrantsLoginModule.class);
|
||||
|
||||
public static final String SCOPE_OPTION = "scope";
|
||||
|
||||
private String refreshToken;
|
||||
private String scope;
|
||||
|
||||
@Override
|
||||
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
|
||||
super.initialize(subject, callbackHandler, sharedState, options);
|
||||
this.scope = (String)options.get(SCOPE_OPTION);
|
||||
|
||||
// This is used just for logout
|
||||
Iterator<RefreshTokenHolder> iterator = subject.getPrivateCredentials(RefreshTokenHolder.class).iterator();
|
||||
@@ -89,6 +94,10 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
||||
formparams.add(new BasicNameValuePair("username", username));
|
||||
formparams.add(new BasicNameValuePair("password", password));
|
||||
|
||||
if (scope != null) {
|
||||
formparams.add(new BasicNameValuePair(OAuth2Constants.SCOPE, scope));
|
||||
}
|
||||
|
||||
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
|
||||
|
||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||
@@ -121,7 +130,8 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
||||
// refreshToken will be saved to privateCreds of Subject for now
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
|
||||
return bearerAuth(tokenResponse.getToken());
|
||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenResponse.getToken(), tokenResponse.getIdToken(), deployment);
|
||||
return postTokenVerification(tokenResponse.getToken(), tokens.getAccessToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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.rotation;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AdapterRSATokenVerifier {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||
return verifyToken(tokenString, deployment, true, true);
|
||||
}
|
||||
|
||||
|
||||
public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
|
||||
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||
|
||||
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
||||
if (publicKey == null) {
|
||||
log.errorf("Didn't find publicKey for kid: %s", kid);
|
||||
throw new VerificationException("Didn't find publicKey for specified kid");
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
|
||||
return verifier.publicKey(publicKey).verify().getToken();
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.rotation;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AdapterTokenVerifier {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AdapterTokenVerifier.class);
|
||||
|
||||
|
||||
/**
|
||||
* Verifies bearer token. Typically called when bearer token (access token) is sent to the service, which wants to verify it. Hence it also checks the audience in the token.
|
||||
*
|
||||
* @param tokenString
|
||||
* @param deployment
|
||||
* @return
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||
TokenVerifier<AccessToken> tokenVerifier = createVerifier(tokenString, deployment, true, AccessToken.class);
|
||||
|
||||
// Verify audience of bearer-token
|
||||
if (deployment.isVerifyTokenAudience()) {
|
||||
tokenVerifier.audience(deployment.getResourceName());
|
||||
}
|
||||
|
||||
return tokenVerifier.verify().getToken();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify access token and ID token. Typically called after successful tokenResponse is received from Keycloak
|
||||
*
|
||||
* @param accessTokenString
|
||||
* @param idTokenString
|
||||
* @param deployment
|
||||
* @return verified and parsed accessToken and idToken
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public static VerifiedTokens verifyTokens(String accessTokenString, String idTokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||
// Adapters currently do most of the checks including signature etc on the access token
|
||||
TokenVerifier<AccessToken> tokenVerifier = createVerifier(accessTokenString, deployment, true, AccessToken.class);
|
||||
AccessToken accessToken = tokenVerifier.verify().getToken();
|
||||
|
||||
if (idTokenString != null) {
|
||||
// Don't verify signature again on IDToken
|
||||
IDToken idToken = TokenVerifier.create(idTokenString, IDToken.class).getToken();
|
||||
TokenVerifier<IDToken> idTokenVerifier = TokenVerifier.createWithoutSignature(idToken);
|
||||
|
||||
// Always verify audience and azp on IDToken
|
||||
idTokenVerifier.audience(deployment.getResourceName());
|
||||
idTokenVerifier.issuedFor(deployment.getResourceName());
|
||||
|
||||
idTokenVerifier.verify();
|
||||
return new VerifiedTokens(accessToken, idToken);
|
||||
} else {
|
||||
return new VerifiedTokens(accessToken, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates verifier, initializes it from the KeycloakDeployment and adds the publicKey and some default basic checks (activeness and tokenType). Useful if caller wants to add/remove/update
|
||||
* some checks
|
||||
*
|
||||
* @param tokenString
|
||||
* @param deployment
|
||||
* @param withDefaultChecks
|
||||
* @param tokenClass
|
||||
* @param <T>
|
||||
* @return tokenVerifier
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public static <T extends JsonWebToken> TokenVerifier<T> createVerifier(String tokenString, KeycloakDeployment deployment, boolean withDefaultChecks, Class<T> tokenClass) throws VerificationException {
|
||||
TokenVerifier<T> tokenVerifier = TokenVerifier.create(tokenString, tokenClass);
|
||||
|
||||
if (withDefaultChecks) {
|
||||
tokenVerifier
|
||||
.withDefaultChecks()
|
||||
.realmUrl(deployment.getRealmInfoUrl());
|
||||
}
|
||||
|
||||
String kid = tokenVerifier.getHeader().getKeyId();
|
||||
PublicKey publicKey = getPublicKey(kid, deployment);
|
||||
tokenVerifier.publicKey(publicKey);
|
||||
|
||||
return tokenVerifier;
|
||||
}
|
||||
|
||||
|
||||
private static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
|
||||
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||
|
||||
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
||||
if (publicKey == null) {
|
||||
log.errorf("Didn't find publicKey for kid: %s", kid);
|
||||
throw new VerificationException("Didn't find publicKey for specified kid");
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
|
||||
public static class VerifiedTokens {
|
||||
|
||||
private final AccessToken accessToken;
|
||||
private final IDToken idToken;
|
||||
|
||||
public VerifiedTokens(AccessToken accessToken, IDToken idToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.idToken = idToken;
|
||||
}
|
||||
|
||||
|
||||
public AccessToken getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public IDToken getIdToken() {
|
||||
return idToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -16,4 +16,5 @@
|
||||
#
|
||||
|
||||
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
|
||||
org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#
|
||||
# * Copyright 2018 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.
|
||||
#
|
||||
|
||||
org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory
|
||||
org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory
|
||||
+7
@@ -21,6 +21,7 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
@@ -76,6 +77,7 @@ public class KeycloakDeploymentBuilderTest {
|
||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
||||
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
|
||||
assertTrue(deployment.isVerifyTokenAudience());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,4 +96,9 @@ public class KeycloakDeploymentBuilderTest {
|
||||
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadSecretJwtCredentials() throws Exception {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-secret-jwt.json"));
|
||||
assertEquals(JWTClientSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"realm": "demo",
|
||||
"resource": "customer-portal",
|
||||
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"auth-server-url": "https://localhost:8443/auth",
|
||||
"ssl-required": "external",
|
||||
"credentials": {
|
||||
"secret-jwt": {
|
||||
"secret": "234234-234234-234234"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@
|
||||
"min-time-between-jwks-requests": 20,
|
||||
"public-key-cache-ttl": 120,
|
||||
"ignore-oauth-query-parameter": true,
|
||||
"verify-token-audience": true,
|
||||
"redirect-rewrite-rules" : {
|
||||
"^/wsmaster/api/(.*)$" : "/api/$1"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -47,7 +47,6 @@
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -86,17 +85,4 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -118,17 +118,4 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+7
@@ -98,6 +98,12 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.setAllowExpression(true)
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition PUBLIC_KEY_CACHE_TTL =
|
||||
new SimpleAttributeDefinitionBuilder("public-key-cache-ttl", ModelType.INT, true)
|
||||
.setXmlName("public-key-cache-ttl")
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
@@ -110,6 +116,7 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_KEY_CACHE_TTL);
|
||||
}
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
|
||||
+16
@@ -166,6 +166,20 @@ class SharedAttributeDefinitons {
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition PROXY_URL =
|
||||
new SimpleAttributeDefinitionBuilder("proxy-url", ModelType.STRING, true)
|
||||
.setXmlName("proxy-url")
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition VERIFY_TOKEN_AUDIENCE =
|
||||
new SimpleAttributeDefinitionBuilder("verify-token-audience", ModelType.BOOLEAN, true)
|
||||
.setXmlName("verify-token-audience")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
@@ -192,6 +206,8 @@ class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(PROXY_URL);
|
||||
ATTRIBUTES.add(VERIFY_TOKEN_AUDIENCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-1
@@ -46,6 +46,8 @@ keycloak.realm.register-node-at-startup=Cluster setting
|
||||
keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.proxy-url=The URL for the HTTP proxy if one is used.
|
||||
keycloak.realm.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -82,7 +84,9 @@ keycloak.secure-deployment.principal-attribute=token attribute to use to set Pri
|
||||
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
|
||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
|
||||
keycloak.secure-deployment.public-key-cache-ttl=Maximum time the downloaded public keys are considered valid. When this time reach, the adapter is forced to download public keys from keycloak server
|
||||
keycloak.secure-deployment.proxy-url=The URL for the HTTP proxy if one is used.
|
||||
keycloak.secure-deployment.verify-token-audience=If true, then during bearer-only authentication, the adapter will verify if token contains this client name (resource) as an audience
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
keycloak.credential=Credential
|
||||
|
||||
@@ -65,6 +65,8 @@
|
||||
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
@@ -107,6 +109,9 @@
|
||||
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="public-key-cache-ttl" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="proxy-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="verify-token-audience" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required">
|
||||
<xs:annotation>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||
@@ -30,6 +30,11 @@
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -47,4 +52,4 @@
|
||||
<module>as7-adapter</module>
|
||||
<module>as7-subsystem</module>
|
||||
</modules>
|
||||
</project>
|
||||
</project>
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
export KC_AUTH_SERVER=http://localhost:8080/auth
|
||||
export KC_REALM=master
|
||||
export KC_CLIENT=cli
|
||||
|
||||
export KC_ACCESS_TOKEN=`java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar login`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar logout
|
||||
|
||||
unset KC_ACCESS_TOKEN
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-cli-sso</artifactId>
|
||||
<name>Keycloak CLI SSO Framework</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-installed-adapter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.keycloak.adapters.KeycloakCliSsoMain</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.adapters.installed.KeycloakCliSso;
|
||||
import org.keycloak.adapters.installed.KeycloakInstalled;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakCliSsoMain extends KeycloakCliSso {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new KeycloakCliSsoMain().mainCmd(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-fuse7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-camel-undertow</artifactId>
|
||||
<name>Keycloak Fuse 7.0 Adapter - Camel + Undertow</name>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.camel.undertow;version="${project.version}"
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
org.keycloak.*;version="${project.version}",
|
||||
org.apache.camel.*,
|
||||
org.apache.camel.component.undertow,
|
||||
io.undertow.*;version="[1.4,3)",
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.enterprise</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-runtime</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-undertow</artifactId>
|
||||
<version>2.21.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.camel</groupId>
|
||||
<artifactId>camel-core</artifactId>
|
||||
<version>2.21.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
<Export-Service>org.apache.camel.spi.ComponentResolver;component=undertow-keycloak</Export-Service>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2018 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.camel.undertow;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import org.apache.camel.CamelContext;
|
||||
import org.apache.camel.component.undertow.UndertowComponent;
|
||||
import org.apache.camel.component.undertow.UndertowEndpoint;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class UndertowKeycloakComponent extends UndertowComponent {
|
||||
|
||||
public UndertowKeycloakComponent() {
|
||||
}
|
||||
|
||||
public UndertowKeycloakComponent(CamelContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UndertowEndpoint createEndpointInstance(URI endpointUri, UndertowComponent component) throws URISyntaxException {
|
||||
return new UndertowKeycloakEndpoint(endpointUri.toString(), component);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getComponentName() {
|
||||
return "undertow-keycloak";
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2018 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.camel.undertow;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
|
||||
import org.keycloak.adapters.undertow.OIDCUndertowHttpFacade;
|
||||
import org.keycloak.adapters.undertow.SessionManagementBridge;
|
||||
import org.keycloak.adapters.undertow.UndertowCookieTokenStore;
|
||||
import org.keycloak.adapters.undertow.UndertowRequestAuthenticator;
|
||||
import org.keycloak.adapters.undertow.UndertowSessionTokenStore;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import io.undertow.security.api.SecurityContext;
|
||||
import io.undertow.security.idm.Account;
|
||||
import io.undertow.security.idm.Credential;
|
||||
import io.undertow.security.idm.IdentityManager;
|
||||
import io.undertow.security.impl.SecurityContextImpl;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import io.undertow.server.session.InMemorySessionManager;
|
||||
import io.undertow.server.session.SessionManager;
|
||||
import io.undertow.util.AttachmentKey;
|
||||
import io.undertow.util.StatusCodes;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.camel.Processor;
|
||||
import org.apache.camel.component.undertow.UndertowConsumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class UndertowKeycloakConsumer extends UndertowConsumer {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(UndertowKeycloakConsumer.class.getName());
|
||||
|
||||
public static final AttachmentKey<KeycloakPrincipal> KEYCLOAK_PRINCIPAL_KEY = AttachmentKey.create(KeycloakPrincipal.class);
|
||||
|
||||
private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() {
|
||||
@Override
|
||||
public Account verify(Account account) {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(String id, Credential credential) {
|
||||
throw new IllegalStateException("Should never be called in Keycloak flow");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(Credential credential) {
|
||||
throw new IllegalStateException("Should never be called in Keycloak flow");
|
||||
}
|
||||
};
|
||||
|
||||
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||
|
||||
protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
|
||||
private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
|
||||
|
||||
protected final AdapterDeploymentContext deploymentContext;
|
||||
|
||||
protected final SessionManager sessionManager;
|
||||
|
||||
protected final List<String> allowedRoles;
|
||||
|
||||
private final int confidentialPort;
|
||||
|
||||
private final Pattern skipPattern;
|
||||
|
||||
public UndertowKeycloakConsumer(UndertowKeycloakEndpoint endpoint, Processor processor,
|
||||
AdapterDeploymentContext deploymentContext, Pattern skipPattern, List<String> allowedRoles, int confidentialPort) {
|
||||
super(endpoint, processor);
|
||||
this.sessionManager = new InMemorySessionManager(endpoint.getEndpointUri());
|
||||
this.deploymentContext = deploymentContext;
|
||||
this.skipPattern = skipPattern;
|
||||
this.confidentialPort = confidentialPort;
|
||||
this.allowedRoles = allowedRoles == null ? Collections.<String>emptyList() : allowedRoles;
|
||||
}
|
||||
|
||||
public int getConfidentialPort() {
|
||||
return confidentialPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange httpExchange) throws Exception {
|
||||
if (shouldSkip(httpExchange.getRequestPath())) {
|
||||
super.handleRequest(httpExchange);
|
||||
return;
|
||||
}
|
||||
|
||||
//perform only non-blocking operation on exchange
|
||||
if (httpExchange.isInIoThread()) {
|
||||
httpExchange.dispatch(this);
|
||||
return;
|
||||
}
|
||||
|
||||
OIDCUndertowHttpFacade facade = new OIDCUndertowHttpFacade(httpExchange);
|
||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
|
||||
if (deployment == null || !deployment.isConfigured()) {
|
||||
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
|
||||
LOG.fine("deployment not configured");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.fine("executing PreAuthActionsHandler");
|
||||
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
|
||||
PreAuthActionsHandler preAuth = new PreAuthActionsHandler(bridge, deploymentContext, facade);
|
||||
if (preAuth.handleRequest()) return;
|
||||
|
||||
SecurityContext securityContext = httpExchange.getSecurityContext();
|
||||
if (securityContext == null) {
|
||||
securityContext = new SecurityContextImpl(httpExchange, IDENTITY_MANAGER);
|
||||
}
|
||||
AdapterTokenStore tokenStore = getTokenStore(httpExchange, facade, deployment, securityContext);
|
||||
tokenStore.checkCurrentToken();
|
||||
|
||||
LOG.fine("executing AuthenticatedActionsHandler");
|
||||
RequestAuthenticator authenticator = new UndertowRequestAuthenticator(facade, deployment, confidentialPort, securityContext, httpExchange, tokenStore);
|
||||
AuthOutcome outcome = authenticator.authenticate();
|
||||
|
||||
if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
LOG.fine("AUTHENTICATED");
|
||||
if (httpExchange.isResponseComplete()) {
|
||||
return;
|
||||
}
|
||||
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
|
||||
if (actions.handledRequest()) {
|
||||
return;
|
||||
} else {
|
||||
final Account authenticatedAccount = securityContext.getAuthenticatedAccount();
|
||||
if (authenticatedAccount instanceof KeycloakUndertowAccount) {
|
||||
final KeycloakUndertowAccount kua = (KeycloakUndertowAccount) authenticatedAccount;
|
||||
httpExchange.putAttachment(KEYCLOAK_PRINCIPAL_KEY, (KeycloakPrincipal) kua.getPrincipal());
|
||||
}
|
||||
|
||||
Set<String> roles = Optional
|
||||
.ofNullable(authenticatedAccount.getRoles())
|
||||
.orElse((Set<String>) Collections.EMPTY_SET);
|
||||
|
||||
LOG.log(Level.FINE, "Allowed roles: {0}, current roles: {1}", new Object[] {allowedRoles, roles});
|
||||
|
||||
if (isRoleAllowed(roles, httpExchange)) {
|
||||
super.handleRequest(httpExchange);
|
||||
} else {
|
||||
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
LOG.fine("challenge");
|
||||
challenge.challenge(facade);
|
||||
return;
|
||||
}
|
||||
|
||||
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
|
||||
}
|
||||
|
||||
public boolean isRoleAllowed(Set<String> roles, HttpServerExchange httpExchange) throws Exception {
|
||||
for (String role : allowedRoles) {
|
||||
if (roles.contains(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
|
||||
if (deployment.getTokenStore() == TokenStore.SESSION) {
|
||||
return new UndertowSessionTokenStore(exchange, deployment, userSessionManagement, securityContext);
|
||||
} else {
|
||||
return new UndertowCookieTokenStore(facade, deployment, securityContext);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkip(String requestPath) {
|
||||
return skipPattern != null && skipPattern.matcher(requestPath).matches();
|
||||
}
|
||||
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2018 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.camel.undertow;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.camel.Consumer;
|
||||
import org.apache.camel.Exchange;
|
||||
import org.apache.camel.Processor;
|
||||
import org.apache.camel.component.undertow.UndertowComponent;
|
||||
import org.apache.camel.component.undertow.UndertowEndpoint;
|
||||
import static org.keycloak.adapters.camel.undertow.UndertowKeycloakConsumer.KEYCLOAK_PRINCIPAL_KEY;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class UndertowKeycloakEndpoint extends UndertowEndpoint {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(UndertowKeycloakEndpoint.class.getName());
|
||||
|
||||
private KeycloakConfigResolver configResolver;
|
||||
|
||||
private AdapterConfig adapterConfig;
|
||||
|
||||
private String skipPattern;
|
||||
|
||||
private List<String> allowedRoles = Collections.emptyList();
|
||||
|
||||
private int confidentialPort = 8443;
|
||||
|
||||
public UndertowKeycloakEndpoint(String uri, UndertowComponent component) {
|
||||
super(uri, component);
|
||||
}
|
||||
|
||||
public AdapterConfig getAdapterConfig() {
|
||||
return adapterConfig;
|
||||
}
|
||||
|
||||
public void setAdapterConfig(AdapterConfig adapterConfig) {
|
||||
LOG.info("adapterConfig");
|
||||
this.adapterConfig = adapterConfig;
|
||||
}
|
||||
|
||||
public String getSkipPattern() {
|
||||
return skipPattern;
|
||||
}
|
||||
|
||||
public void setSkipPattern(String skipPattern) {
|
||||
this.skipPattern = skipPattern;
|
||||
}
|
||||
|
||||
public List<String> getAllowedRoles() {
|
||||
return allowedRoles;
|
||||
}
|
||||
|
||||
public void setAllowedRoles(List<String> allowedRoles) {
|
||||
this.allowedRoles = allowedRoles;
|
||||
}
|
||||
|
||||
public void setAllowedRoles(String allowedRoles) {
|
||||
this.allowedRoles = allowedRoles == null ? null : Arrays.asList(allowedRoles.split("\\s*,\\s*"));
|
||||
}
|
||||
|
||||
public int getConfidentialPort() {
|
||||
return confidentialPort;
|
||||
}
|
||||
|
||||
public void setConfidentialPort(int confidentialPort) {
|
||||
this.confidentialPort = confidentialPort;
|
||||
}
|
||||
|
||||
public KeycloakConfigResolver getConfigResolver() {
|
||||
return configResolver;
|
||||
}
|
||||
|
||||
public void setConfigResolver(KeycloakConfigResolver configResolver) {
|
||||
this.configResolver = configResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Consumer createConsumer(Processor processor) throws Exception {
|
||||
return new UndertowKeycloakConsumer(this, processor, getDeploymentContext(), getSkipPatternAsPattern(), computeAllowedRoles(), this.confidentialPort);
|
||||
}
|
||||
|
||||
public List<String> computeAllowedRoles() {
|
||||
List<String> res = this.allowedRoles == null ? Collections.<String>emptyList() : this.allowedRoles;
|
||||
if (res.isEmpty()) {
|
||||
LOG.warning("No roles were configured, Keycloak will deny every request");
|
||||
}
|
||||
LOG.log(Level.FINE, "Allowed roles: {0}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exchange createExchange(HttpServerExchange httpExchange) throws Exception {
|
||||
final Exchange res = super.createExchange(httpExchange);
|
||||
|
||||
KeycloakPrincipal principal = httpExchange.getAttachment(KEYCLOAK_PRINCIPAL_KEY);
|
||||
LOG.log(Level.FINE, "principal: {0}", principal);
|
||||
if (principal != null) {
|
||||
res.setProperty(KeycloakPrincipal.class.getName(), principal);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private AdapterDeploymentContext getDeploymentContext() {
|
||||
if (configResolver != null) {
|
||||
LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass());
|
||||
return new AdapterDeploymentContext(configResolver);
|
||||
} else if (adapterConfig != null) {
|
||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
|
||||
return new AdapterDeploymentContext(kd);
|
||||
}
|
||||
|
||||
LOG.warning("Adapter is unconfigured, Keycloak will deny every request");
|
||||
return new AdapterDeploymentContext();
|
||||
}
|
||||
|
||||
private Pattern getSkipPatternAsPattern() {
|
||||
return skipPattern == null
|
||||
? null
|
||||
: Pattern.compile(skipPattern, Pattern.DOTALL);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
class=org.keycloak.adapters.camel.undertow.UndertowKeycloakComponent
|
||||
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-fuse7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-pax-web-jetty94</artifactId>
|
||||
<name>Keycloak Fuse 7.0 Adapter - Jetty 9.4</name>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<properties>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.osgi.jetty94.*;version="${project.version}"
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
!org.keycloak.adapters.osgi.jetty94,
|
||||
org.ops4j.pax.web.*;version="[7.1,9)",
|
||||
org.keycloak.*;version="${project.version}",
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-jetty</keycloak.osgi.fragment>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.enterprise</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty94-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
<version>${jetty9.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2018 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.osgi.jetty94;
|
||||
|
||||
import org.ops4j.pax.web.service.AuthenticatorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class KeycloakAuthenticatorService implements AuthenticatorService {
|
||||
|
||||
@Override
|
||||
public <T> T getAuthenticatorService(String method, Class<T> iface) {
|
||||
if (method == null || iface != org.eclipse.jetty.security.Authenticator.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("KEYCLOAK".equalsIgnoreCase(method)) {
|
||||
return iface.cast(new org.keycloak.adapters.jetty.KeycloakJettyAuthenticator());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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.osgi.jetty94;
|
||||
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.ops4j.pax.web.service.WebContainer;
|
||||
import org.ops4j.pax.web.service.spi.model.SecurityConstraintMappingModel;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
import org.osgi.service.http.HttpContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
|
||||
* and allows to inject custom security constraint for securing resources by Keycloak.
|
||||
*
|
||||
* <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which
|
||||
* is true in karaf/fuse environment</p>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PaxWebIntegrationService {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class);
|
||||
|
||||
private BundleContext bundleContext;
|
||||
private String jettyWebXmlLocation;
|
||||
private List<Object> constraintMappings;
|
||||
|
||||
private ServiceTracker webContainerTracker;
|
||||
private HttpContext httpContext;
|
||||
|
||||
public BundleContext getBundleContext() {
|
||||
return bundleContext;
|
||||
}
|
||||
|
||||
public void setBundleContext(BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
}
|
||||
|
||||
public String getJettyWebXmlLocation() {
|
||||
return jettyWebXmlLocation;
|
||||
}
|
||||
|
||||
public void setJettyWebXmlLocation(String jettyWebXmlLocation) {
|
||||
this.jettyWebXmlLocation = jettyWebXmlLocation;
|
||||
}
|
||||
|
||||
public List<Object> getConstraintMappings() {
|
||||
return constraintMappings;
|
||||
}
|
||||
|
||||
public void setConstraintMappings(List<Object> constraintMappings) {
|
||||
this.constraintMappings = constraintMappings;
|
||||
}
|
||||
|
||||
protected ServiceTracker getWebContainerTracker() {
|
||||
return webContainerTracker;
|
||||
}
|
||||
|
||||
protected HttpContext getHttpContext() {
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
public void setHttpContext(HttpContext httpContext) {
|
||||
this.httpContext = httpContext;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
||||
|
||||
@Override
|
||||
public Object addingService(ServiceReference reference) {
|
||||
return addingWebContainerCallback(reference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifiedService(ServiceReference reference, Object service) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removedService(ServiceReference reference, Object service) {
|
||||
removingWebContainerCallback(reference);
|
||||
}
|
||||
};
|
||||
|
||||
webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer);
|
||||
webContainerTracker.open();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
webContainerTracker.remove(webContainerTracker.getServiceReference());
|
||||
}
|
||||
|
||||
protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) {
|
||||
WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
||||
if (httpContext == null) {
|
||||
httpContext = service.createDefaultHttpContext();
|
||||
}
|
||||
|
||||
addJettyWebXml(service);
|
||||
|
||||
if (constraintMappings == null) {
|
||||
throw new IllegalStateException("constraintMappings was null!");
|
||||
}
|
||||
List<ConstraintHandler> handlers = new ArrayList<>();
|
||||
try {
|
||||
handlers.add(new JettyConstraintHandler());
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
try {
|
||||
handlers.add(new PaxWebConstraintHandler());
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
for (Object constraintMapping : constraintMappings) {
|
||||
boolean handled = false;
|
||||
for (ConstraintHandler handler : handlers) {
|
||||
handled |= handler.addConstraintMapping(httpContext, service, constraintMapping);
|
||||
}
|
||||
if (!handled) {
|
||||
log.warnv("Unable to add constraint mapping for constraint of type " + constraintMapping.getClass().toString());
|
||||
}
|
||||
}
|
||||
|
||||
service.registerLoginConfig("BASIC", "does-not-matter", null, null, httpContext);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
protected void addJettyWebXml(WebContainer service) {
|
||||
String jettyWebXmlLoc;
|
||||
if (this.jettyWebXmlLocation == null) {
|
||||
jettyWebXmlLoc = "/WEB-INF/jetty-web.xml";
|
||||
} else {
|
||||
jettyWebXmlLoc = this.jettyWebXmlLocation;
|
||||
}
|
||||
|
||||
URL jettyWebXml = bundleContext.getBundle().getResource(jettyWebXmlLoc);
|
||||
if (jettyWebXml != null) {
|
||||
log.debug("Found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
|
||||
service.registerJettyWebXml(jettyWebXml, httpContext);
|
||||
} else {
|
||||
log.debug("Not found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
|
||||
}
|
||||
}
|
||||
|
||||
protected void addConstraintMapping(WebContainer service, SecurityConstraintMappingModel constraintMapping) {
|
||||
String name = constraintMapping.getConstraintName();
|
||||
if (name == null) {
|
||||
name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
|
||||
}
|
||||
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getUrl() + ", dataConstraint=" + constraintMapping.getDataConstraint() + ", canAuthenticate="
|
||||
+ constraintMapping.isAuthentication() + ", roles=" + constraintMapping.getRoles());
|
||||
service.registerConstraintMapping(name, constraintMapping.getUrl(), constraintMapping.getMapping(), constraintMapping.getDataConstraint(), constraintMapping.isAuthentication(), constraintMapping.getRoles(), httpContext);
|
||||
}
|
||||
|
||||
protected void addConstraintMapping(WebContainer service, ConstraintMapping constraintMapping) {
|
||||
Constraint constraint = constraintMapping.getConstraint();
|
||||
String[] roles = constraint.getRoles();
|
||||
// name property is unavailable on constraint object :/
|
||||
|
||||
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
|
||||
|
||||
int dataConstraint = constraint.getDataConstraint();
|
||||
String dataConstraintStr;
|
||||
switch (dataConstraint) {
|
||||
case Constraint.DC_UNSET: dataConstraintStr = null; break;
|
||||
case Constraint.DC_NONE: dataConstraintStr = "NONE"; break;
|
||||
case Constraint.DC_CONFIDENTIAL: dataConstraintStr = "CONFIDENTIAL"; break;
|
||||
case Constraint.DC_INTEGRAL: dataConstraintStr = "INTEGRAL"; break;
|
||||
default:
|
||||
log.warnv("Unknown data constraint: " + dataConstraint);
|
||||
dataConstraintStr = "CONFIDENTIAL";
|
||||
}
|
||||
List<String> rolesList = Arrays.asList(roles);
|
||||
|
||||
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate="
|
||||
+ constraint.getAuthenticate() + ", roles=" + rolesList);
|
||||
service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext);
|
||||
}
|
||||
|
||||
protected void removingWebContainerCallback(ServiceReference serviceReference) {
|
||||
WebContainer service = (WebContainer)bundleContext.getService(serviceReference);
|
||||
if (service != null) {
|
||||
service.unregisterLoginConfig(httpContext);
|
||||
service.unregisterConstraintMapping(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
private interface ConstraintHandler {
|
||||
boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm);
|
||||
}
|
||||
|
||||
private static class PaxWebConstraintHandler implements ConstraintHandler {
|
||||
|
||||
public boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm) {
|
||||
if (cm instanceof SecurityConstraintMappingModel) {
|
||||
SecurityConstraintMappingModel constraintMapping = (SecurityConstraintMappingModel) cm;
|
||||
String name = constraintMapping.getConstraintName();
|
||||
if (name == null) {
|
||||
name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
|
||||
}
|
||||
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getUrl() + ", dataConstraint=" + constraintMapping.getDataConstraint() + ", canAuthenticate="
|
||||
+ constraintMapping.isAuthentication() + ", roles=" + constraintMapping.getRoles());
|
||||
service.registerConstraintMapping(name, constraintMapping.getUrl(), constraintMapping.getMapping(), constraintMapping.getDataConstraint(), constraintMapping.isAuthentication(), constraintMapping.getRoles(), httpContext);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JettyConstraintHandler implements ConstraintHandler {
|
||||
|
||||
public boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm) {
|
||||
if (cm instanceof ConstraintMapping) {
|
||||
ConstraintMapping constraintMapping = (ConstraintMapping) cm;
|
||||
Constraint constraint = constraintMapping.getConstraint();
|
||||
String[] roles = constraint.getRoles();
|
||||
// name property is unavailable on constraint object :/
|
||||
|
||||
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
|
||||
|
||||
int dataConstraint = constraint.getDataConstraint();
|
||||
String dataConstraintStr;
|
||||
switch (dataConstraint) {
|
||||
case Constraint.DC_UNSET:
|
||||
dataConstraintStr = null;
|
||||
break;
|
||||
case Constraint.DC_NONE:
|
||||
dataConstraintStr = "NONE";
|
||||
break;
|
||||
case Constraint.DC_CONFIDENTIAL:
|
||||
dataConstraintStr = "CONFIDENTIAL";
|
||||
break;
|
||||
case Constraint.DC_INTEGRAL:
|
||||
dataConstraintStr = "INTEGRAL";
|
||||
break;
|
||||
default:
|
||||
log.warnv("Unknown data constraint: " + dataConstraint);
|
||||
dataConstraintStr = "CONFIDENTIAL";
|
||||
}
|
||||
List<String> rolesList = Arrays.asList(roles);
|
||||
|
||||
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate="
|
||||
+ constraint.getAuthenticate() + ", roles=" + rolesList);
|
||||
service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.adapters.osgi.jetty94.KeycloakAuthenticatorService
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-fuse7-integration-pom</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Keycloak Fuse 7.0 Integration</name>
|
||||
|
||||
<properties>
|
||||
<fuse7.version>7.0.1-SNAPSHOT</fuse7.version>
|
||||
<tomcat.version>8.0.14</tomcat.version>
|
||||
<jetty9.version>9.4.2.v20170220</jetty9.version>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>camel-undertow</module>
|
||||
<module>undertow</module>
|
||||
</modules>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>jetty94</module>
|
||||
<module>tomcat8</module>
|
||||
</modules>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-fuse7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-pax-web-tomcat8</artifactId>
|
||||
<name>Keycloak Fuse 7.0 Adapter - Tomcat 8</name>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<properties>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.osgi.tomcat.*;version="${project.version}"
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
!org.keycloak.adapters.osgi.tomcat,
|
||||
org.ops4j.pax.web.*;version="[7.1,9)",
|
||||
org.keycloak.*;version="${project.version}",
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-tomcat</keycloak.osgi.fragment>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-tomcat8-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-catalina</artifactId>
|
||||
<version>${tomcat.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2018 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.osgi.tomcat;
|
||||
|
||||
import org.ops4j.pax.web.service.AuthenticatorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class KeycloakAuthenticatorService implements AuthenticatorService {
|
||||
|
||||
@Override
|
||||
public <T> T getAuthenticatorService(String method, Class<T> iface) {
|
||||
if (method == null || iface != org.apache.catalina.Valve.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("KEYCLOAK".equalsIgnoreCase(method)) {
|
||||
return iface.cast(new org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.adapters.osgi.tomcat.KeycloakAuthenticatorService
|
||||
@@ -0,0 +1,147 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-fuse7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-pax-web-undertow</artifactId>
|
||||
<name>Keycloak Fuse 7.0 Adapter - Undertow</name>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.osgi.undertow.*;version="${project.version}"
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
!org.keycloak.adapters.osgi.undertow,
|
||||
org.keycloak.*;version="${project.version}",
|
||||
org.keycloak.adapters.osgi.*;version="${project.version}",
|
||||
org.ops4j.pax.web.*;version="[7.1,9)",
|
||||
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
||||
org.apache.cxf.transport.http;resolution:=optional;version="[3,4)",
|
||||
org.apache.cxf.transport.http_undertow;resolution:=optional;version="[3,4)",
|
||||
org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)",
|
||||
io.undertow.*;version="[1.4,3)",
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-undertow</keycloak.osgi.fragment>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.enterprise</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-transports-http</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
<artifactId>cxf-rt-transports-http-undertow</artifactId>
|
||||
<version>${cxf.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.undertow</groupId>
|
||||
<artifactId>undertow-servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-undertow-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-osgi-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundle-manifest</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Bundle-Name>${project.name}</Bundle-Name>
|
||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2018 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.osgi.undertow;
|
||||
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
|
||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||
import org.keycloak.adapters.undertow.UndertowAuthenticationMechanism;
|
||||
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import io.undertow.security.api.AuthenticationMechanism;
|
||||
import io.undertow.security.api.AuthenticationMode;
|
||||
import io.undertow.security.handlers.AuthenticationCallHandler;
|
||||
import io.undertow.security.handlers.AuthenticationConstraintHandler;
|
||||
import io.undertow.security.handlers.AuthenticationMechanismsHandler;
|
||||
import io.undertow.security.handlers.SecurityInitialHandler;
|
||||
import io.undertow.security.idm.Account;
|
||||
import io.undertow.security.idm.Credential;
|
||||
import io.undertow.security.idm.IdentityManager;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.cxf.transport.http_undertow.CXFUndertowHttpHandler;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class CxfKeycloakAuthHandler implements CXFUndertowHttpHandler {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(CxfKeycloakAuthHandler.class.getName());
|
||||
|
||||
private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() {
|
||||
@Override
|
||||
public Account verify(Account account) {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(String id, Credential credential) {
|
||||
throw new IllegalStateException("Should never be called in Keycloak flow");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account verify(Credential credential) {
|
||||
throw new IllegalStateException("Should never be called in Keycloak flow");
|
||||
}
|
||||
};
|
||||
|
||||
private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
|
||||
|
||||
protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
|
||||
protected final SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||
|
||||
private final AtomicReference<HttpHandler> securityHandler = new AtomicReference<>();
|
||||
|
||||
private Pattern skipPattern;
|
||||
|
||||
private int confidentialPort = 8443;
|
||||
|
||||
private HttpHandler next;
|
||||
|
||||
private KeycloakConfigResolver configResolver;
|
||||
|
||||
private AdapterConfig adapterConfig;
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
if (shouldSkip(exchange.getRequestPath())) {
|
||||
next.handleRequest(exchange);
|
||||
} else {
|
||||
getSecurityHandler().handleRequest(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpHandler getSecurityHandler() {
|
||||
if (this.securityHandler.get() == null) {
|
||||
HttpHandler handler = this.next;
|
||||
|
||||
handler = new AuthenticationCallHandler(handler);
|
||||
handler = new AuthenticationConstraintHandler(handler);
|
||||
|
||||
AdapterDeploymentContext deploymentContext = buildDeploymentContext();
|
||||
|
||||
final List<AuthenticationMechanism> mechanisms
|
||||
= Collections.<AuthenticationMechanism>singletonList(
|
||||
new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, confidentialPort, null));
|
||||
handler = new AuthenticationMechanismsHandler(handler, mechanisms);
|
||||
|
||||
this.securityHandler.compareAndSet(null, new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, IDENTITY_MANAGER, "KEYCLOAK", handler));
|
||||
}
|
||||
|
||||
return this.securityHandler.get();
|
||||
}
|
||||
|
||||
private AdapterDeploymentContext buildDeploymentContext() {
|
||||
if (configResolver != null) {
|
||||
LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass());
|
||||
return new AdapterDeploymentContext(configResolver);
|
||||
} else if (adapterConfig != null) {
|
||||
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
|
||||
return new AdapterDeploymentContext(kd);
|
||||
}
|
||||
|
||||
LOG.warning("Adapter is unconfigured, Keycloak will deny every request");
|
||||
return new AdapterDeploymentContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNext(HttpHandler nextHandler) {
|
||||
this.next = nextHandler;
|
||||
}
|
||||
|
||||
private boolean shouldSkip(String requestPath) {
|
||||
return skipPattern != null && skipPattern.matcher(requestPath).matches();
|
||||
}
|
||||
|
||||
public KeycloakConfigResolver getConfigResolver() {
|
||||
return configResolver;
|
||||
}
|
||||
|
||||
public void setConfigResolver(KeycloakConfigResolver configResolver) {
|
||||
this.configResolver = configResolver;
|
||||
}
|
||||
|
||||
public int getConfidentialPort() {
|
||||
return confidentialPort;
|
||||
}
|
||||
|
||||
public void setConfidentialPort(int confidentialPort) {
|
||||
this.confidentialPort = confidentialPort;
|
||||
}
|
||||
|
||||
public AdapterConfig getAdapterConfig() {
|
||||
return adapterConfig;
|
||||
}
|
||||
|
||||
public void setAdapterConfig(AdapterConfig adapterConfig) {
|
||||
this.adapterConfig = adapterConfig;
|
||||
}
|
||||
|
||||
public String getSkipPattern() {
|
||||
return skipPattern.pattern();
|
||||
}
|
||||
|
||||
public void setSkipPattern(String skipPattern) {
|
||||
this.skipPattern = Pattern.compile(skipPattern, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2018 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.osgi.undertow;
|
||||
|
||||
import org.ops4j.pax.web.service.AuthenticatorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class KeycloakAuthenticatorService implements AuthenticatorService {
|
||||
|
||||
@Override
|
||||
public <T> T getAuthenticatorService(String method, Class<T> iface) {
|
||||
if (method == null || iface != io.undertow.servlet.ServletExtension.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ("KEYCLOAK".equalsIgnoreCase(method)) {
|
||||
return iface.cast(new org.keycloak.adapters.undertow.KeycloakServletExtension());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.osgi.undertow;
|
||||
|
||||
import org.keycloak.adapters.osgi.PaxWebSecurityConstraintMapping;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.ops4j.pax.web.service.WebContainer;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.ServiceReference;
|
||||
import org.osgi.service.http.HttpContext;
|
||||
import org.osgi.util.tracker.ServiceTracker;
|
||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Integration with pax-web, which allows to inject custom security constraint for securing resources by Keycloak.
|
||||
*
|
||||
* <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which
|
||||
* is true in karaf/fuse environment</p>
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class PaxWebIntegrationService {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class);
|
||||
|
||||
private BundleContext bundleContext;
|
||||
private List<PaxWebSecurityConstraintMapping> constraintMappings;
|
||||
|
||||
private ServiceTracker webContainerTracker;
|
||||
private HttpContext httpContext;
|
||||
|
||||
public BundleContext getBundleContext() {
|
||||
return bundleContext;
|
||||
}
|
||||
|
||||
public void setBundleContext(BundleContext bundleContext) {
|
||||
this.bundleContext = bundleContext;
|
||||
}
|
||||
|
||||
public List<PaxWebSecurityConstraintMapping> getConstraintMappings() {
|
||||
return constraintMappings;
|
||||
}
|
||||
|
||||
public void setConstraintMappings(List<PaxWebSecurityConstraintMapping> constraintMappings) {
|
||||
this.constraintMappings = constraintMappings;
|
||||
}
|
||||
|
||||
protected ServiceTracker getWebContainerTracker() {
|
||||
return webContainerTracker;
|
||||
}
|
||||
|
||||
protected HttpContext getHttpContext() {
|
||||
return httpContext;
|
||||
}
|
||||
|
||||
public void setHttpContext(HttpContext httpContext) {
|
||||
this.httpContext = httpContext;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
||||
|
||||
@Override
|
||||
public Object addingService(ServiceReference reference) {
|
||||
return addingWebContainerCallback(reference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifiedService(ServiceReference reference, Object service) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removedService(ServiceReference reference, Object service) {
|
||||
removingWebContainerCallback(reference);
|
||||
}
|
||||
};
|
||||
|
||||
webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer);
|
||||
webContainerTracker.open();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
webContainerTracker.remove(webContainerTracker.getServiceReference());
|
||||
}
|
||||
|
||||
protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) {
|
||||
WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
||||
if (httpContext == null) {
|
||||
httpContext = service.createDefaultHttpContext();
|
||||
}
|
||||
|
||||
if (constraintMappings == null) {
|
||||
throw new IllegalStateException("constraintMappings was null!");
|
||||
}
|
||||
for (PaxWebSecurityConstraintMapping constraintMapping : constraintMappings) {
|
||||
addConstraintMapping(service, constraintMapping);
|
||||
}
|
||||
|
||||
service.registerLoginConfig("KEYCLOAK", "specified-in-keycloak-json", null, null, httpContext);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
protected void addConstraintMapping(WebContainer service, PaxWebSecurityConstraintMapping cm) {
|
||||
log.debug("Adding security constraint name=" + cm.getConstraintName() + ", url=" + cm.getUrl() + ", dataConstraint=" + cm.getDataConstraint() + ", canAuthenticate="
|
||||
+ cm.isAuthentication() + ", roles=" + cm.getRoles());
|
||||
service.registerConstraintMapping(
|
||||
cm.getConstraintName(),
|
||||
cm.getMapping(),
|
||||
cm.getUrl(),
|
||||
cm.getDataConstraint(),
|
||||
cm.isAuthentication(),
|
||||
cm.getRoles(),
|
||||
httpContext
|
||||
);
|
||||
}
|
||||
|
||||
protected void removingWebContainerCallback(ServiceReference serviceReference) {
|
||||
WebContainer service = (WebContainer)bundleContext.getService(serviceReference);
|
||||
if (service != null) {
|
||||
service.unregisterLoginConfig(httpContext);
|
||||
service.unregisterConstraintMapping(httpContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.keycloak.adapters.osgi.undertow.KeycloakAuthenticatorService
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -63,19 +63,15 @@
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+702
@@ -0,0 +1,702 @@
|
||||
/*
|
||||
* 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.installed;
|
||||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jwe.*;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.*;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* All kcinit commands that take input ask for
|
||||
* <p>
|
||||
* 1. . kcinit
|
||||
* - setup and export KC_SESSION_KEY env var if not set.
|
||||
* - checks to see if master token valid, refresh is possible, exit if token valid
|
||||
* - performs command line login
|
||||
* - stores master token for master client
|
||||
* 2. app.sh is a wrapper for app cli.
|
||||
* - token=`kcinit token app`
|
||||
* - checks to see if token for app client has been fetched, refresh if valid, output token to sys.out if exists
|
||||
* - if no token, login. Prompts go to stderr.
|
||||
* - pass token as cmd line param to app or as environment variable.
|
||||
* <p>
|
||||
* 3. kcinit password {password}
|
||||
* - outputs password key that is used for encryption.
|
||||
* - can be used in .bashrc as export KC_SESSSION_KEY=`kcinit password {password}` or just set it in .bat file
|
||||
* <p>
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KcinitDriver {
|
||||
|
||||
public static final String KC_SESSION_KEY = "KC_SESSION_KEY";
|
||||
public static final String KC_LOGIN_CONFIG_PATH = "KC_LOGIN_CONFIG_PATH";
|
||||
protected Map<String, String> config;
|
||||
protected boolean debug = true;
|
||||
|
||||
protected static byte[] salt = new byte[]{-4, 88, 66, -101, 78, -94, 21, 105};
|
||||
|
||||
String[] args = null;
|
||||
|
||||
protected boolean forceLogin;
|
||||
protected boolean browserLogin;
|
||||
|
||||
public void mainCmd(String[] args) throws Exception {
|
||||
|
||||
this.args = args;
|
||||
|
||||
|
||||
if (args.length == 0) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0].equalsIgnoreCase("token")) {
|
||||
//System.err.println("executing token");
|
||||
token();
|
||||
} else if (args[0].equalsIgnoreCase("login")) {
|
||||
login();
|
||||
} else if (args[0].equalsIgnoreCase("logout")) {
|
||||
logout();
|
||||
} else if (args[0].equalsIgnoreCase("env")) {
|
||||
System.out.println(System.getenv().toString());
|
||||
} else if (args[0].equalsIgnoreCase("install")) {
|
||||
install();
|
||||
} else if (args[0].equalsIgnoreCase("uninstall")) {
|
||||
uninstall();
|
||||
} else if (args[0].equalsIgnoreCase("password")) {
|
||||
passwordKey();
|
||||
} else {
|
||||
KeycloakInstalled.console().writer().println("Unknown command: " + args[0]);
|
||||
KeycloakInstalled.console().writer().println();
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
|
||||
public String getHome() {
|
||||
String home = System.getenv("HOME");
|
||||
if (home == null) {
|
||||
home = System.getProperty("HOME");
|
||||
if (home == null) {
|
||||
home = Paths.get("").toAbsolutePath().normalize().toString();
|
||||
}
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
public void passwordKey() {
|
||||
if (args.length < 2) {
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
String password = args[1];
|
||||
try {
|
||||
String encodedKey = generateEncryptionKey(password);
|
||||
System.out.printf(encodedKey);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected String generateEncryptionKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
|
||||
SecretKey tmp = factory.generateSecret(spec);
|
||||
byte[] aeskey = tmp.getEncoded();
|
||||
return Base64.encodeBytes(aeskey);
|
||||
}
|
||||
|
||||
public JWE createJWE() {
|
||||
String key = getEncryptionKey();
|
||||
if (key == null) {
|
||||
throw new RuntimeException(KC_SESSION_KEY + " env var not set");
|
||||
}
|
||||
byte[] aesKey = null;
|
||||
try {
|
||||
aesKey = Base64.decode(key.getBytes("UTF-8"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("invalid " + KC_SESSION_KEY + "env var");
|
||||
}
|
||||
|
||||
JWE jwe = new JWE();
|
||||
final SecretKey aesSecret = new SecretKeySpec(aesKey, "AES");
|
||||
jwe.getKeyStorage()
|
||||
.setEncryptionKey(aesSecret);
|
||||
return jwe;
|
||||
}
|
||||
|
||||
protected String encryptionKey;
|
||||
|
||||
protected String getEncryptionKey() {
|
||||
if (encryptionKey != null) return encryptionKey;
|
||||
return System.getenv(KC_SESSION_KEY);
|
||||
}
|
||||
|
||||
public String encrypt(String payload) {
|
||||
JWE jwe = createJWE();
|
||||
JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
|
||||
try {
|
||||
jwe.header(jweHeader).content(payload.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("cannot encode payload as UTF-8");
|
||||
}
|
||||
try {
|
||||
return jwe.encodeJwe();
|
||||
} catch (JWEException e) {
|
||||
throw new RuntimeException("cannot encrypt payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String encoded) {
|
||||
JWE jwe = createJWE();
|
||||
try {
|
||||
jwe.verifyAndDecodeJwe(encoded);
|
||||
byte[] content = jwe.getContent();
|
||||
if (content == null) return null;
|
||||
return new String(content, "UTF-8");
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("cannot decrypt payload", ex);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static String getenv(String name, String defaultValue) {
|
||||
String val = System.getenv(name);
|
||||
return val == null ? defaultValue : val;
|
||||
}
|
||||
|
||||
public File getConfigDirectory() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit").toFile();
|
||||
}
|
||||
|
||||
|
||||
public File getConfigFile() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "config.json").toFile();
|
||||
}
|
||||
|
||||
public File getTokenFilePath(String client) {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens", client).toFile();
|
||||
}
|
||||
|
||||
public File getTokenDirectory() {
|
||||
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens").toFile();
|
||||
}
|
||||
|
||||
protected boolean encrypted = false;
|
||||
|
||||
protected void checkEnv() {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
}
|
||||
byte[] data = new byte[0];
|
||||
try {
|
||||
data = readFileRaw(configFile);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
if (data == null) {
|
||||
KeycloakInstalled.console().writer().println("Config file unreadable. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
String encodedJwe = null;
|
||||
try {
|
||||
encodedJwe = new String(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (encodedJwe.contains("realm")) {
|
||||
encrypted = false;
|
||||
return;
|
||||
} else {
|
||||
encrypted = true;
|
||||
}
|
||||
|
||||
if (System.getenv(KC_SESSION_KEY) == null) {
|
||||
promptLocalPassword();
|
||||
}
|
||||
}
|
||||
|
||||
protected void promptLocalPassword() {
|
||||
String password = KeycloakInstalled.console().passwordPrompt("Enter password to unlock kcinit config files: ");
|
||||
try {
|
||||
encryptionKey = generateEncryptionKey(password);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String readFile(File fp) {
|
||||
try {
|
||||
byte[] data = readFileRaw(fp);
|
||||
if (data == null) return null;
|
||||
String file = new String(data, "UTF-8");
|
||||
if (!encrypted) {
|
||||
return file;
|
||||
}
|
||||
String decrypted = decrypt(file);
|
||||
if (decrypted == null)
|
||||
throw new RuntimeException("Unable to decrypt file. Did you set your local password correctly?");
|
||||
return decrypted;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to decrypt file: " + fp.getAbsolutePath() + " Did you set your local password correctly?", e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected byte[] readFileRaw(File fp) throws IOException {
|
||||
if (!fp.exists()) return null;
|
||||
FileInputStream fis = new FileInputStream(fp);
|
||||
byte[] data = new byte[(int) fp.length()];
|
||||
fis.read(data);
|
||||
fis.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
protected void writeFile(File fp, String payload) {
|
||||
try {
|
||||
String data = payload;
|
||||
if (encrypted) data = encrypt(payload);
|
||||
FileOutputStream fos = new FileOutputStream(fp);
|
||||
fos.write(data.getBytes("UTF-8"));
|
||||
fos.flush();
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void install() {
|
||||
if (getEncryptionKey() == null) {
|
||||
if (KeycloakInstalled.console().confirm("Do you want to protect tokens stored locally with a password? (y/n): ")) {
|
||||
String password = "p";
|
||||
String confirm = "c";
|
||||
do {
|
||||
password = KeycloakInstalled.console().passwordPrompt("Enter local password: ");
|
||||
confirm = KeycloakInstalled.console().passwordPrompt("Confirm local password: ");
|
||||
if (!password.equals(confirm)) {
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println("Confirmation does not match. Try again.");
|
||||
KeycloakInstalled.console().writer().println();
|
||||
}
|
||||
} while (!password.equals(confirm));
|
||||
try {
|
||||
this.encrypted = true;
|
||||
this.encryptionKey = generateEncryptionKey(password);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!KeycloakInstalled.console().confirm("KC_SESSION_KEY env var already set. Do you want to use this as your local encryption key? (y/n): ")) {
|
||||
KeycloakInstalled.console().writer().println("Unset KC_SESSION_KEY env var and run again");
|
||||
System.exit(1);
|
||||
}
|
||||
this.encrypted = true;
|
||||
this.encryptionKey = getEncryptionKey();
|
||||
}
|
||||
String server = KeycloakInstalled.console().readLine("Authentication server URL [http://localhost:8080/auth]: ").trim();
|
||||
String realm = KeycloakInstalled.console().readLine("Name of realm [master]: ").trim();
|
||||
String client = KeycloakInstalled.console().readLine("CLI client id [kcinit]: ").trim();
|
||||
String secret = KeycloakInstalled.console().readLine("CLI client secret [none]: ").trim();
|
||||
if (server.equals("")) {
|
||||
server = "http://localhost:8080/auth";
|
||||
}
|
||||
if (realm.equals("")) {
|
||||
realm = "master";
|
||||
}
|
||||
if (client.equals("")) {
|
||||
client = "kcinit";
|
||||
}
|
||||
File configDir = getTokenDirectory();
|
||||
configDir.mkdirs();
|
||||
|
||||
File configFile = getConfigFile();
|
||||
Map<String, String> props = new HashMap<>();
|
||||
props.put("server", server);
|
||||
props.put("realm", realm);
|
||||
props.put("client", client);
|
||||
props.put("secret", secret);
|
||||
|
||||
try {
|
||||
String json = JsonSerialization.writeValueAsString(props);
|
||||
writeFile(configFile, json);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println("Installation complete!");
|
||||
KeycloakInstalled.console().writer().println();
|
||||
}
|
||||
|
||||
|
||||
public void printHelp() {
|
||||
KeycloakInstalled.console().writer().println("Commands:");
|
||||
KeycloakInstalled.console().writer().println(" login [-f] -f forces login");
|
||||
KeycloakInstalled.console().writer().println(" logout");
|
||||
KeycloakInstalled.console().writer().println(" token [client] - print access token of desired client. Defaults to default master client. Will print either 'error', 'not-allowed', or 'login-required' on error.");
|
||||
KeycloakInstalled.console().writer().println(" install - Install this utility. Will store in $HOME/.keycloak/kcinit unless " + KC_LOGIN_CONFIG_PATH + " env var is set");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
|
||||
public AdapterConfig getConfig() {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
|
||||
AdapterConfig config = new AdapterConfig();
|
||||
config.setAuthServerUrl((String) getConfigProperties().get("server"));
|
||||
config.setRealm((String) getConfigProperties().get("realm"));
|
||||
config.setResource((String) getConfigProperties().get("client"));
|
||||
config.setSslRequired("external");
|
||||
String secret = (String) getConfigProperties().get("secret");
|
||||
if (secret != null && !secret.trim().equals("")) {
|
||||
Map<String, Object> creds = new HashMap<>();
|
||||
creds.put("secret", secret);
|
||||
config.setCredentials(creds);
|
||||
} else {
|
||||
config.setPublicClient(true);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private Map<String, String> getConfigProperties() {
|
||||
if (this.config != null) return this.config;
|
||||
if (!getConfigFile().exists()) {
|
||||
KeycloakInstalled.console().writer().println();
|
||||
KeycloakInstalled.console().writer().println(("Config file does not exist. Run kcinit install to set it up."));
|
||||
System.exit(1);
|
||||
}
|
||||
String json = readFile(getConfigFile());
|
||||
try {
|
||||
Map map = JsonSerialization.readValue(json, Map.class);
|
||||
config = (Map<String, String>) map;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public String readToken(String client) throws Exception {
|
||||
String json = getTokenResponse(client);
|
||||
if (json == null) return null;
|
||||
|
||||
|
||||
if (json != null) {
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
return tokenResponse.getToken();
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.refreshToken(tokenResponse.getRefreshToken());
|
||||
processResponse(installed, client);
|
||||
return tokenResponse.getToken();
|
||||
} catch (Exception e) {
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
if (tokenFile.exists()) {
|
||||
tokenFile.delete();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public String readRefreshToken(String client) throws Exception {
|
||||
String json = getTokenResponse(client);
|
||||
if (json == null) return null;
|
||||
|
||||
|
||||
if (json != null) {
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
return tokenResponse.getRefreshToken();
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
if (tokenFile.exists()) {
|
||||
tokenFile.delete();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private String getTokenResponse(String client) throws IOException {
|
||||
File tokenFile = getTokenFilePath(client);
|
||||
try {
|
||||
return readFile(tokenFile);
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
System.err.println("Failed to read encrypted file");
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (tokenFile.exists()) tokenFile.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void token() throws Exception {
|
||||
KeycloakInstalled.console().stderrOutput();
|
||||
|
||||
checkEnv();
|
||||
String masterClient = getMasterClient();
|
||||
String client = masterClient;
|
||||
if (args.length > 1) {
|
||||
client = args[1];
|
||||
}
|
||||
//System.err.println("readToken: " + client);
|
||||
String token = readToken(client);
|
||||
if (token != null) {
|
||||
System.out.print(token);
|
||||
return;
|
||||
}
|
||||
if (token == null && client.equals(masterClient)) {
|
||||
//System.err.println("not logged in, logging in.");
|
||||
doConsoleLogin();
|
||||
token = readToken(client);
|
||||
if (token != null) {
|
||||
System.out.print(token);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
String masterToken = readToken(masterClient);
|
||||
if (masterToken == null) {
|
||||
//System.err.println("not logged in, logging in.");
|
||||
doConsoleLogin();
|
||||
masterToken = readToken(masterClient);
|
||||
if (masterToken == null) {
|
||||
System.err.println("Login failed. Cannot retrieve token");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
//System.err.println("exchange: " + client);
|
||||
Client httpClient = getHttpClient();
|
||||
|
||||
WebTarget exchangeUrl = httpClient.target(getServer())
|
||||
.path("/realms")
|
||||
.path(getRealm())
|
||||
.path("protocol/openid-connect/token");
|
||||
|
||||
Form form = new Form()
|
||||
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
|
||||
.param(OAuth2Constants.CLIENT_ID, masterClient)
|
||||
.param(OAuth2Constants.SUBJECT_TOKEN, masterToken)
|
||||
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
|
||||
.param(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE)
|
||||
.param(OAuth2Constants.AUDIENCE, client);
|
||||
if (getMasterClientSecret() != null) {
|
||||
form.param(OAuth2Constants.CLIENT_SECRET, getMasterClientSecret());
|
||||
}
|
||||
Response response = exchangeUrl.request().post(Entity.form(
|
||||
form
|
||||
));
|
||||
|
||||
if (response.getStatus() == 401 || response.getStatus() == 403) {
|
||||
response.close();
|
||||
System.err.println("Not allowed to exchange for client token");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (response.getStatus() != 200) {
|
||||
if (response.getMediaType() != null && response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
|
||||
try {
|
||||
String json = response.readEntity(String.class);
|
||||
OAuth2ErrorRepresentation error = JsonSerialization.readValue(json, OAuth2ErrorRepresentation.class);
|
||||
System.err.println("Failed to exchange token: " + error.getError() + ". " + error.getErrorDescription());
|
||||
System.exit(1);
|
||||
} catch (Exception ignore) {
|
||||
ignore.printStackTrace();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
response.close();
|
||||
System.err.println("Unknown error exchanging for client token: " + response.getStatus());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String json = response.readEntity(String.class);
|
||||
response.close();
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (tokenResponse.getToken() != null) {
|
||||
getTokenDirectory().mkdirs();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
json = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
writeFile(getTokenFilePath(client), json);
|
||||
System.out.printf(tokenResponse.getToken());
|
||||
} else {
|
||||
System.err.println("Error processing token");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getMasterClientSecret() {
|
||||
return getProperty("secret");
|
||||
}
|
||||
|
||||
protected String getServer() {
|
||||
return getProperty("server");
|
||||
}
|
||||
|
||||
protected String getRealm() {
|
||||
return getProperty("realm");
|
||||
}
|
||||
|
||||
public String getProperty(String name) {
|
||||
return (String) getConfigProperties().get(name);
|
||||
}
|
||||
|
||||
protected boolean forceLogin() {
|
||||
return args.length > 0 && args[0].equals("-f");
|
||||
|
||||
}
|
||||
|
||||
public Client getHttpClient() {
|
||||
return new ResteasyClientBuilder().disableTrustManager().build();
|
||||
}
|
||||
|
||||
public void login() throws Exception {
|
||||
checkEnv();
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
for (String arg : args) {
|
||||
if (arg.equals("-f") || arg.equals("-force")) {
|
||||
forceLogin = true;
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
} else if (arg.equals("-browser") || arg.equals("-b")) {
|
||||
browserLogin = true;
|
||||
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
|
||||
} else {
|
||||
System.err.println("Illegal argument: " + arg);
|
||||
printHelp();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
String masterClient = getMasterClient();
|
||||
if (!forceLogin && readToken(masterClient) != null) {
|
||||
KeycloakInstalled.console().writer().println("Already logged in. `kcinit -f` to force relogin");
|
||||
return;
|
||||
}
|
||||
doConsoleLogin();
|
||||
KeycloakInstalled.console().writer().println("Login successful!");
|
||||
}
|
||||
|
||||
public void doConsoleLogin() throws Exception {
|
||||
String masterClient = getMasterClient();
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
//System.err.println("calling loginCommandLine");
|
||||
if (!installed.loginCommandLine()) {
|
||||
System.exit(1);
|
||||
}
|
||||
processResponse(installed, masterClient);
|
||||
}
|
||||
|
||||
private String getMasterClient() {
|
||||
return getProperty("client");
|
||||
}
|
||||
|
||||
private void processResponse(KeycloakInstalled installed, String client) throws IOException {
|
||||
AccessTokenResponse tokenResponse = installed.getTokenResponse();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
String json = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
getTokenDirectory().mkdirs();
|
||||
writeFile(getTokenFilePath(client), json);
|
||||
}
|
||||
|
||||
public void logout() throws Exception {
|
||||
String token = readRefreshToken(getMasterClient());
|
||||
if (token != null) {
|
||||
try {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getConfig());
|
||||
ServerRequest.invokeLogout(deployment, token);
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (getTokenDirectory().exists()) {
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
}
|
||||
}
|
||||
public void uninstall() throws Exception {
|
||||
File configFile = getConfigFile();
|
||||
if (configFile.exists()) configFile.delete();
|
||||
if (getTokenDirectory().exists()) {
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
-266
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* 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.installed;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakCliSso {
|
||||
|
||||
public void mainCmd(String[] args) throws Exception {
|
||||
if (args.length != 1) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0].equalsIgnoreCase("login")) {
|
||||
login();
|
||||
} else if (args[0].equalsIgnoreCase("login-manual")) {
|
||||
loginManual();
|
||||
} else if (args[0].equalsIgnoreCase("token")) {
|
||||
token();
|
||||
} else if (args[0].equalsIgnoreCase("logout")) {
|
||||
logout();
|
||||
} else if (args[0].equalsIgnoreCase("env")) {
|
||||
System.out.println(System.getenv().toString());
|
||||
} else {
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void printHelp() {
|
||||
System.err.println("Commands:");
|
||||
System.err.println(" login - login with desktop browser if available, otherwise do manual login. Output is access token.");
|
||||
System.err.println(" login-manual - manual login");
|
||||
System.err.println(" token - print access token if logged in");
|
||||
System.err.println(" logout - logout.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public AdapterConfig getConfig() {
|
||||
String url = System.getProperty("KEYCLOAK_AUTH_SERVER");
|
||||
if (url == null) {
|
||||
System.err.println("KEYCLOAK_AUTH_SERVER property not set");
|
||||
System.exit(1);
|
||||
}
|
||||
String realm = System.getProperty("KEYCLOAK_REALM");
|
||||
if (realm == null) {
|
||||
System.err.println("KEYCLOAK_REALM property not set");
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
String client = System.getProperty("KEYCLOAK_CLIENT");
|
||||
if (client == null) {
|
||||
System.err.println("KEYCLOAK_CLIENT property not set");
|
||||
System.exit(1);
|
||||
}
|
||||
String secret = System.getProperty("KEYCLOAK_CLIENT_SECRET");
|
||||
|
||||
|
||||
|
||||
AdapterConfig config = new AdapterConfig();
|
||||
config.setAuthServerUrl(url);
|
||||
config.setRealm(realm);
|
||||
config.setResource(client);
|
||||
config.setSslRequired("external");
|
||||
if (secret != null) {
|
||||
Map<String, Object> creds = new HashMap<>();
|
||||
creds.put("secret", secret);
|
||||
config.setCredentials(creds);
|
||||
} else {
|
||||
config.setPublicClient(true);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public boolean checkToken() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token == null) return false;
|
||||
|
||||
|
||||
if (token != null) {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
return true;
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.refreshToken(tokenResponse.getRefreshToken());
|
||||
processResponse(installed);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error processing existing token");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private String getTokenResponse() throws IOException {
|
||||
String token = null;
|
||||
File tokenFile = getTokenFilePath();
|
||||
if (tokenFile.exists()) {
|
||||
FileInputStream fis = new FileInputStream(tokenFile);
|
||||
byte[] data = new byte[(int) tokenFile.length()];
|
||||
fis.read(data);
|
||||
fis.close();
|
||||
token = new String(data, "UTF-8");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public void token() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token == null) {
|
||||
System.err.println("There is no token for client");
|
||||
System.exit(1);
|
||||
} else {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
System.out.println(tokenResponse.getToken());
|
||||
return;
|
||||
} else {
|
||||
System.err.println("token in response file is expired");
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure processing token response file");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Could not find json within token response file");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void login() throws Exception {
|
||||
if (checkToken()) return;
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.login();
|
||||
processResponse(installed);
|
||||
}
|
||||
|
||||
public String getHome() {
|
||||
String home = System.getenv("HOME");
|
||||
if (home == null) {
|
||||
home = System.getProperty("HOME");
|
||||
if (home == null) {
|
||||
home = Paths.get("").toAbsolutePath().normalize().toString();
|
||||
}
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
public File getTokenDirectory() {
|
||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM")).toFile();
|
||||
}
|
||||
|
||||
public File getTokenFilePath() {
|
||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM"), System.getProperty("KEYCLOAK_CLIENT") + ".json").toFile();
|
||||
}
|
||||
|
||||
private void processResponse(KeycloakInstalled installed) throws IOException {
|
||||
AccessTokenResponse tokenResponse = installed.getTokenResponse();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
String output = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
getTokenDirectory().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(getTokenFilePath());
|
||||
fos.write(output.getBytes("UTF-8"));
|
||||
fos.flush();
|
||||
fos.close();
|
||||
System.out.println(tokenResponse.getToken());
|
||||
}
|
||||
|
||||
public void loginManual() throws Exception {
|
||||
if (checkToken()) return;
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
||||
KeycloakInstalled installed = new KeycloakInstalled(deployment);
|
||||
installed.loginManual();
|
||||
processResponse(installed);
|
||||
}
|
||||
|
||||
public void logout() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token != null) {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() > tokenResponse.getExpiresIn()) {
|
||||
System.err.println("Login is expired");
|
||||
System.exit(1);
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
||||
ServerRequest.invokeLogout(deployment, tokenResponse.getRefreshToken());
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
System.out.println("logout complete");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure processing token response file");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Could not find json within token response file");
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Not logged in");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+308
-86
@@ -17,30 +17,26 @@
|
||||
|
||||
package org.keycloak.adapters.installed;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.rotation.AdapterTokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.Form;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.awt.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
@@ -48,6 +44,8 @@ import java.net.URISyntaxException;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
@@ -56,6 +54,7 @@ public class KeycloakInstalled {
|
||||
|
||||
public interface HttpResponseWriter {
|
||||
void success(PrintWriter pw, KeycloakInstalled ki);
|
||||
|
||||
void failure(PrintWriter pw, KeycloakInstalled ki);
|
||||
}
|
||||
|
||||
@@ -77,7 +76,10 @@ public class KeycloakInstalled {
|
||||
private Locale locale;
|
||||
private HttpResponseWriter loginResponseWriter;
|
||||
private HttpResponseWriter logoutResponseWriter;
|
||||
|
||||
private ResteasyClient resteasyClient;
|
||||
Pattern callbackPattern = Pattern.compile("callback\\s*=\\s*\"([^\"]+)\"");
|
||||
Pattern paramPattern = Pattern.compile("param=\"([^\"]+)\"\\s+label=\"([^\"]+)\"\\s+mask=(\\S+)");
|
||||
Pattern codePattern = Pattern.compile("code=([^&]+)");
|
||||
|
||||
|
||||
public KeycloakInstalled() {
|
||||
@@ -93,70 +95,12 @@ public class KeycloakInstalled {
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
private static HttpResponseWriter defaultLoginWriter = new HttpResponseWriter() {
|
||||
@Override
|
||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Login completed.</h1><div>");
|
||||
pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Login attempt failed.</h1><div>");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
};
|
||||
private static HttpResponseWriter defaultLogoutWriter = new HttpResponseWriter() {
|
||||
@Override
|
||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Logout completed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Logout failed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public HttpResponseWriter getLoginResponseWriter() {
|
||||
if (loginResponseWriter == null) {
|
||||
return defaultLoginWriter;
|
||||
} else {
|
||||
return loginResponseWriter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public HttpResponseWriter getLogoutResponseWriter() {
|
||||
if (logoutResponseWriter == null) {
|
||||
return defaultLogoutWriter;
|
||||
} else {
|
||||
return logoutResponseWriter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
|
||||
@@ -167,6 +111,10 @@ public class KeycloakInstalled {
|
||||
this.logoutResponseWriter = logoutResponseWriter;
|
||||
}
|
||||
|
||||
public void setResteasyClient(ResteasyClient resteasyClient) {
|
||||
this.resteasyClient = resteasyClient;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
@@ -290,6 +238,272 @@ public class KeycloakInstalled {
|
||||
status = Status.LOGGED_MANUAL;
|
||||
}
|
||||
|
||||
public static class Console {
|
||||
protected java.io.Console console = System.console();
|
||||
protected PrintWriter writer;
|
||||
protected BufferedReader reader;
|
||||
|
||||
static Console SINGLETON = new Console();
|
||||
|
||||
private Console() {
|
||||
}
|
||||
|
||||
|
||||
public PrintWriter writer() {
|
||||
if (console == null) {
|
||||
if (writer == null) {
|
||||
writer = new PrintWriter(System.err, true);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
return console.writer();
|
||||
}
|
||||
|
||||
public Reader reader() {
|
||||
if (console == null) {
|
||||
return getReader();
|
||||
}
|
||||
return console.reader();
|
||||
}
|
||||
|
||||
protected BufferedReader getReader() {
|
||||
if (reader != null) return reader;
|
||||
reader = new BufferedReader(new BufferedReader(new InputStreamReader(System.in)));
|
||||
return reader;
|
||||
}
|
||||
|
||||
public Console format(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
writer().format(fmt, args);
|
||||
return this;
|
||||
}
|
||||
console.format(fmt, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Console printf(String format, Object... args) {
|
||||
if (console == null) {
|
||||
writer().printf(format, args);
|
||||
return this;
|
||||
}
|
||||
console.printf(format, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String readLine(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
format(fmt, args);
|
||||
return readLine();
|
||||
}
|
||||
return console.readLine(fmt, args);
|
||||
}
|
||||
|
||||
public boolean confirm(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (!"y".equals(prompt) && !"n".equals(prompt)) {
|
||||
prompt = readLine(fmt, args);
|
||||
}
|
||||
return "y".equals(prompt);
|
||||
|
||||
}
|
||||
|
||||
public String prompt(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (prompt.equals("")) {
|
||||
prompt = readLine(fmt, args).trim();
|
||||
}
|
||||
return prompt;
|
||||
|
||||
}
|
||||
|
||||
public String passwordPrompt(String fmt, Object... args) {
|
||||
String prompt = "";
|
||||
while (prompt.equals("")) {
|
||||
char[] val = readPassword(fmt, args);
|
||||
prompt = new String(val);
|
||||
prompt = prompt.trim();
|
||||
}
|
||||
return prompt;
|
||||
|
||||
}
|
||||
|
||||
public String readLine() {
|
||||
if (console == null) {
|
||||
try {
|
||||
return getReader().readLine();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return console.readLine();
|
||||
}
|
||||
|
||||
public char[] readPassword(String fmt, Object... args) {
|
||||
if (console == null) {
|
||||
return readLine(fmt, args).toCharArray();
|
||||
|
||||
}
|
||||
return console.readPassword(fmt, args);
|
||||
}
|
||||
|
||||
public char[] readPassword() {
|
||||
if (console == null) {
|
||||
return readLine().toCharArray();
|
||||
}
|
||||
return console.readPassword();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (console == null) {
|
||||
System.err.flush();
|
||||
return;
|
||||
}
|
||||
console.flush();
|
||||
}
|
||||
|
||||
public void stderrOutput() {
|
||||
//System.err.println("not using System.console()");
|
||||
console = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Console console() {
|
||||
return Console.SINGLETON;
|
||||
}
|
||||
|
||||
public boolean loginCommandLine() throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
||||
|
||||
return loginCommandLine(redirectUri);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Experimental proprietary WWW-Authentication challenge protocol.
|
||||
* WWW-Authentication: X-Text-Form-Challenge callback="{url}" param="{param-name}" label="{param-display-label}"
|
||||
*
|
||||
* @param redirectUri
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws ServerRequest.HttpFailure
|
||||
* @throws VerificationException
|
||||
*/
|
||||
public boolean loginCommandLine(String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
String authUrl = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||
.queryParam("display", "console")
|
||||
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||
.build().toString();
|
||||
ResteasyClient client = createResteasyClient();
|
||||
try {
|
||||
//System.err.println("initial request");
|
||||
Response response = client.target(authUrl).request().get();
|
||||
while (true) {
|
||||
if (response.getStatus() == 403) {
|
||||
if (response.getMediaType() != null) {
|
||||
String splash = response.readEntity(String.class);
|
||||
console().writer().println(splash);
|
||||
} else {
|
||||
System.err.println("Forbidden to login");
|
||||
}
|
||||
return false;
|
||||
} else if (response.getStatus() == 401) {
|
||||
String authenticationHeader = response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
|
||||
if (authenticationHeader == null) {
|
||||
System.err.println("Failure: Invalid protocol. No WWW-Authenticate header");
|
||||
return false;
|
||||
}
|
||||
//System.err.println("got header: " + authenticationHeader);
|
||||
if (!authenticationHeader.contains("X-Text-Form-Challenge")) {
|
||||
System.err.println("Failure: Invalid WWW-Authenticate header.");
|
||||
return false;
|
||||
}
|
||||
if (response.getMediaType() != null) {
|
||||
String splash = response.readEntity(String.class);
|
||||
console().writer().println(splash);
|
||||
} else {
|
||||
response.close();
|
||||
}
|
||||
Matcher m = callbackPattern.matcher(authenticationHeader);
|
||||
if (!m.find()) {
|
||||
System.err.println("Failure: Invalid WWW-Authenticate header.");
|
||||
return false;
|
||||
}
|
||||
String callback = m.group(1);
|
||||
//System.err.println("callback: " + callback);
|
||||
m = paramPattern.matcher(authenticationHeader);
|
||||
Form form = new Form();
|
||||
while (m.find()) {
|
||||
String param = m.group(1);
|
||||
String label = m.group(2);
|
||||
String mask = m.group(3).trim();
|
||||
boolean maskInput = mask.equals("true");
|
||||
String value = null;
|
||||
if (maskInput) {
|
||||
char[] txt = console().readPassword(label);
|
||||
value = new String(txt);
|
||||
} else {
|
||||
value = console().readLine(label);
|
||||
}
|
||||
form.param(param, value);
|
||||
}
|
||||
response.close();
|
||||
client.close();
|
||||
client = createResteasyClient();
|
||||
response = client.target(callback).request().post(Entity.form(form));
|
||||
} else if (response.getStatus() == 302) {
|
||||
int redirectCount = 0;
|
||||
do {
|
||||
String location = response.getLocation().toString();
|
||||
Matcher m = codePattern.matcher(location);
|
||||
if (!m.find()) {
|
||||
response.close();
|
||||
client.close();
|
||||
client = createResteasyClient();
|
||||
response = client.target(location).request().get();
|
||||
} else {
|
||||
response.close();
|
||||
client.close();
|
||||
String code = m.group(1);
|
||||
processCode(code, redirectUri);
|
||||
return true;
|
||||
}
|
||||
if (response.getStatus() == 302 && redirectCount++ > 4) {
|
||||
System.err.println("Too many redirects. Aborting");
|
||||
return false;
|
||||
}
|
||||
} while (response.getStatus() == 302);
|
||||
} else {
|
||||
System.err.println("Unknown response from server: " + response.getStatus());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
client.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected ResteasyClient getResteasyClient() {
|
||||
if (this.resteasyClient == null) {
|
||||
this.resteasyClient = createResteasyClient();
|
||||
}
|
||||
return this.resteasyClient;
|
||||
}
|
||||
|
||||
protected ResteasyClient createResteasyClient() {
|
||||
return new ResteasyClientBuilder()
|
||||
.connectionCheckoutTimeout(1, TimeUnit.HOURS)
|
||||
.connectionTTL(1, TimeUnit.HOURS)
|
||||
.socketTimeout(1, TimeUnit.HOURS)
|
||||
.disableTrustManager().build();
|
||||
}
|
||||
|
||||
|
||||
public String getTokenString() throws VerificationException, IOException, ServerRequest.HttpFailure {
|
||||
return tokenString;
|
||||
}
|
||||
@@ -308,7 +522,7 @@ public class KeycloakInstalled {
|
||||
parseAccessToken(tokenResponse);
|
||||
}
|
||||
|
||||
public void refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
public void refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
|
||||
parseAccessToken(tokenResponse);
|
||||
|
||||
@@ -320,15 +534,9 @@ public class KeycloakInstalled {
|
||||
refreshToken = tokenResponse.getRefreshToken();
|
||||
idTokenString = tokenResponse.getIdToken();
|
||||
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
if (idTokenString != null) {
|
||||
try {
|
||||
JWSInput input = new JWSInput(idTokenString);
|
||||
idToken = input.readJsonContent(IDToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException();
|
||||
}
|
||||
}
|
||||
AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(tokenString, idTokenString, deployment);
|
||||
token = tokens.getAccessToken();
|
||||
idToken = tokens.getIdToken();
|
||||
}
|
||||
|
||||
public AccessToken getToken() {
|
||||
@@ -360,7 +568,6 @@ public class KeycloakInstalled {
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
|
||||
AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
|
||||
parseAccessToken(tokenResponse);
|
||||
@@ -435,11 +642,26 @@ public class KeycloakInstalled {
|
||||
|
||||
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
|
||||
PrintWriter pw = new PrintWriter(out);
|
||||
if (writer != null) {
|
||||
System.err.println("Using a writer is deprecated. Please remove its usage. This is now handled by endpoint on server");
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
writer.success(pw, KeycloakInstalled.this);
|
||||
if (writer != null) {
|
||||
writer.success(pw, KeycloakInstalled.this);
|
||||
} else {
|
||||
pw.println("HTTP/1.1 302 Found");
|
||||
pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated"));
|
||||
|
||||
}
|
||||
} else {
|
||||
writer.failure(pw, KeycloakInstalled.this);
|
||||
if (writer != null) {
|
||||
writer.failure(pw, KeycloakInstalled.this);
|
||||
} else {
|
||||
pw.println("HTTP/1.1 302 Found");
|
||||
pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated?error=true"));
|
||||
|
||||
}
|
||||
}
|
||||
pw.flush();
|
||||
socket.close();
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -33,7 +33,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.spec.javax.ws.rs</groupId>
|
||||
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
|
||||
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -87,17 +87,5 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+18
@@ -27,6 +27,9 @@ import javax.security.cert.X509Certificate;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
@@ -51,6 +54,8 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
|
||||
|
||||
protected class RequestFacade implements OIDCHttpFacade.Request {
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
@Override
|
||||
public String getFirstParam(String param) {
|
||||
throw new RuntimeException("NOT IMPLEMENTED");
|
||||
@@ -108,6 +113,19 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return getInputStream(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(boolean buffered) {
|
||||
if (inputStream != null) {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
if (buffered) {
|
||||
return inputStream = new BufferedInputStream(requestContext.getEntityStream());
|
||||
}
|
||||
|
||||
return requestContext.getEntityStream();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,21 +14,24 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
<name>Keycloak Jetty Core Integration</name>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
<name>Keycloak Jetty Core Integration</name>
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<jetty9.version>8.1.17.v20150415</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.jetty.core.*
|
||||
@@ -40,18 +43,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-spi</artifactId>
|
||||
@@ -61,29 +63,29 @@
|
||||
<artifactId>keycloak-jetty-adapter-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -105,23 +107,14 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -154,7 +147,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+2
-2
@@ -85,7 +85,7 @@ public class JettyCookieTokenStore implements AdapterTokenStore {
|
||||
|
||||
@Override
|
||||
public void logout() {
|
||||
CookieTokenStore.removeCookie(facade);
|
||||
CookieTokenStore.removeCookie(deployment, facade);
|
||||
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class JettyCookieTokenStore implements AdapterTokenStore {
|
||||
if (success && session.isActive()) return principal;
|
||||
|
||||
log.debugf("Cleanup and expire cookie for user %s after failed refresh", principal.getName());
|
||||
CookieTokenStore.removeCookie(facade);
|
||||
CookieTokenStore.removeCookie(deployment, facade);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,21 +14,24 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty81-adapter</artifactId>
|
||||
<name>Keycloak Jetty 8.1.x Integration</name>
|
||||
<artifactId>keycloak-jetty81-adapter</artifactId>
|
||||
<name>Keycloak Jetty 8.1.x Integration</name>
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<jetty9.version>8.1.17.v20150415</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.jetty.*
|
||||
@@ -39,18 +42,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
@@ -63,26 +65,26 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -104,23 +106,14 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -153,7 +146,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -14,20 +14,20 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty91-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.1.x Integration</name>
|
||||
<artifactId>keycloak-jetty91-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.1.x Integration</name>
|
||||
<properties>
|
||||
<jetty9.version>9.1.5.v20140505</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
@@ -40,18 +40,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
@@ -67,33 +66,33 @@
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
</exclusion>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-security</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -115,22 +114,22 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
@@ -164,7 +163,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -14,21 +14,24 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty92-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.2.x Integration</name>
|
||||
<artifactId>keycloak-jetty92-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.2.x Integration</name>
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<jetty9.version>9.2.4.v20141103</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.jetty.*
|
||||
@@ -40,18 +43,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
@@ -60,26 +62,26 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -101,23 +103,14 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -150,7 +143,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -14,20 +14,20 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty93-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.3.x Integration</name>
|
||||
<artifactId>keycloak-jetty93-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.3.x Integration</name>
|
||||
<properties>
|
||||
<jetty9.version>9.3.9.v20160517</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
@@ -40,18 +40,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
@@ -60,26 +59,26 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -101,23 +100,14 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -150,7 +140,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -14,20 +14,20 @@
|
||||
~ 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.
|
||||
-->
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-jetty94-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.4.x Integration</name>
|
||||
<artifactId>keycloak-jetty94-adapter</artifactId>
|
||||
<name>Keycloak Jetty 9.4.x Integration</name>
|
||||
<properties>
|
||||
<jetty9.version>9.4.2.v20170220</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
@@ -40,18 +40,17 @@
|
||||
*;resolution:=optional
|
||||
</keycloak.osgi.import>
|
||||
</properties>
|
||||
<description />
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<version>${jboss.logging.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-adapter-core</artifactId>
|
||||
@@ -60,26 +59,26 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-jetty-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
@@ -101,23 +100,14 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
@@ -150,7 +140,7 @@
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Jetty Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+73
-10
@@ -18,7 +18,7 @@
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import * as Keycloak from 'keycloak';
|
||||
import * as Keycloak from './keycloak';
|
||||
|
||||
export as namespace KeycloakAuthorization;
|
||||
|
||||
@@ -35,6 +35,64 @@ declare namespace KeycloakAuthorization {
|
||||
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
|
||||
}
|
||||
|
||||
interface AuthorizationRequest {
|
||||
/**
|
||||
* An array of objects representing the resource and scopes.
|
||||
*/
|
||||
permissions?:ResourcePermission[],
|
||||
|
||||
/**
|
||||
* A permission ticket obtained from a resource server when using UMA authorization protocol.
|
||||
*/
|
||||
ticket?:string,
|
||||
|
||||
/**
|
||||
* A boolean value indicating whether the server should create permission requests to the resources
|
||||
* and scopes referenced by a permission ticket. This parameter will only take effect when used together
|
||||
* with the ticket parameter as part of a UMA authorization process.
|
||||
*/
|
||||
submitRequest?:boolean,
|
||||
|
||||
/**
|
||||
* Defines additional information about this authorization request in order to specify how it should be processed
|
||||
* by the server.
|
||||
*/
|
||||
metadata?:AuthorizationRequestMetadata,
|
||||
|
||||
/**
|
||||
* Defines whether or not this authorization request should include the current RPT. If set to true, the RPT will
|
||||
* be sent and permissions in the current RPT will be included in the new RPT. Otherwise, only the permissions referenced in this
|
||||
* authorization request will be granted in the new RPT.
|
||||
*/
|
||||
incrementalAuthorization?:boolean
|
||||
}
|
||||
|
||||
interface AuthorizationRequestMetadata {
|
||||
/**
|
||||
* A boolean value indicating to the server if resource names should be included in the RPT’s permissions.
|
||||
* If false, only the resource identifier is included.
|
||||
*/
|
||||
responseIncludeResourceName?:any,
|
||||
|
||||
/**
|
||||
* An integer N that defines a limit for the amount of permissions an RPT can have. When used together with
|
||||
* rpt parameter, only the last N requested permissions will be kept in the RPT.
|
||||
*/
|
||||
response_permissions_limit?:number
|
||||
}
|
||||
|
||||
interface ResourcePermission {
|
||||
/**
|
||||
* The id or name of a resource.
|
||||
*/
|
||||
id:string,
|
||||
|
||||
/**
|
||||
* An array of strings where each value is the name of a scope associated with the resource.
|
||||
*/
|
||||
scopes?:string[]
|
||||
}
|
||||
|
||||
interface KeycloakAuthorizationInstance {
|
||||
rpt: any;
|
||||
config: { rpt_endpoint: string };
|
||||
@@ -42,18 +100,23 @@ declare namespace KeycloakAuthorization {
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||
* policy enforcer.
|
||||
*
|
||||
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
|
||||
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
|
||||
* depending on how the policy enforcer at the resource server was configured.
|
||||
*/
|
||||
authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
|
||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||
* policy enforcer using UMA protocol.
|
||||
*
|
||||
* The authorization request must be provided with a ticket.
|
||||
*
|
||||
* @param authorizationRequest An AuthorizationRequest instance with a valid permission ticket set.
|
||||
* @returns A promise to set functions to be invoked on grant, deny or error.
|
||||
*/
|
||||
authorize(authorizationRequest: AuthorizationRequest): KeycloakAuthorizationPromise;
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
|
||||
*
|
||||
* @param resourceServerId The id (client id) of the resource server to obtain permissions from.
|
||||
* @param authorizationRequest An AuthorizationRequest instance.
|
||||
* @returns A promise to set functions to be invoked on grant, deny or error.
|
||||
*/
|
||||
entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
|
||||
entitlement(resourceServerId: string, authorizationRequest?: AuthorizationRequest): KeycloakAuthorizationPromise;
|
||||
}
|
||||
}
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
(function( window, undefined ) {
|
||||
|
||||
var KeycloakAuthorization = function (keycloak) {
|
||||
var KeycloakAuthorization = function (keycloak, options) {
|
||||
var _instance = this;
|
||||
this.rpt = null;
|
||||
|
||||
this.init = function () {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
|
||||
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
if (request.status == 200) {
|
||||
@@ -41,74 +41,65 @@
|
||||
|
||||
/**
|
||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||
* policy enforcer.
|
||||
* policy enforcer using UMA protocol.
|
||||
*
|
||||
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
|
||||
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
|
||||
* depending on how the policy enforcer at the resource server was configured.
|
||||
* The authorization request must be provided with a ticket.
|
||||
*/
|
||||
this.authorize = function (wwwAuthenticateHeader) {
|
||||
this.authorize = function (authorizationRequest) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
|
||||
var params = wwwAuthenticateHeader.split(',');
|
||||
if (authorizationRequest && authorizationRequest.ticket) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
for (i = 0; i < params.length; i++) {
|
||||
var param = params[i].split('=');
|
||||
request.open('POST', _instance.config.token_endpoint, true);
|
||||
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
|
||||
|
||||
if (param[0] == 'ticket') {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
request.open('POST', _instance.config.rpt_endpoint, true);
|
||||
request.setRequestHeader('Content-Type', 'application/json')
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
if (onDeny) {
|
||||
onDeny();
|
||||
} else {
|
||||
console.error('Authorization request was denied by the server.');
|
||||
}
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
} else {
|
||||
console.error('Could not obtain authorization data from server.');
|
||||
}
|
||||
}
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).access_token;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
if (onDeny) {
|
||||
onDeny();
|
||||
} else {
|
||||
console.error('Authorization request was denied by the server.');
|
||||
}
|
||||
};
|
||||
|
||||
var ticket = param[1].substring(1, param[1].length - 1).trim();
|
||||
|
||||
request.send(JSON.stringify(
|
||||
{
|
||||
ticket: ticket,
|
||||
rpt: _instance.rpt
|
||||
} else {
|
||||
if (onError) {
|
||||
onError();
|
||||
} else {
|
||||
console.error('Could not obtain authorization data from server.');
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
|
||||
|
||||
if (authorizationRequest.submitRequest != undefined) {
|
||||
params += "&submit_request=" + authorizationRequest.submitRequest;
|
||||
}
|
||||
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) {
|
||||
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
|
||||
var clientId = null;
|
||||
|
||||
for (i = 0; i < params.length; i++) {
|
||||
var param = params[i].split('=');
|
||||
var metadata = authorizationRequest.metadata;
|
||||
|
||||
if (param[0] == 'realm') {
|
||||
clientId = param[1].substring(1, param[1].length - 1).trim();
|
||||
if (metadata) {
|
||||
if (metadata.responseIncludeResourceName) {
|
||||
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
|
||||
}
|
||||
if (metadata.responsePermissionsLimit) {
|
||||
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
|
||||
}
|
||||
}
|
||||
|
||||
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
|
||||
if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
|
||||
params += "&rpt=" + _instance.rpt;
|
||||
}
|
||||
|
||||
request.send(params);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,20 +107,22 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
|
||||
*/
|
||||
this.entitlement = function (resourceSeververId, entitlementRequest ) {
|
||||
this.entitlement = function (resourceServerId, authorizationRequest) {
|
||||
this.then = function (onGrant, onDeny, onError) {
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
|
||||
request.open('POST', _instance.config.token_endpoint, true);
|
||||
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
|
||||
if (status >= 200 && status < 300) {
|
||||
var rpt = JSON.parse(request.responseText).rpt;
|
||||
var rpt = JSON.parse(request.responseText).access_token;
|
||||
_instance.rpt = rpt;
|
||||
onGrant(rpt);
|
||||
} else if (status == 403) {
|
||||
@@ -148,25 +141,70 @@
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
if (!authorizationRequest) {
|
||||
authorizationRequest = {};
|
||||
}
|
||||
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||
request.send(erJson);
|
||||
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
|
||||
|
||||
if (authorizationRequest.claimToken) {
|
||||
params += "&claim_token=" + authorizationRequest.claimToken;
|
||||
|
||||
if (authorizationRequest.claimTokenFormat) {
|
||||
params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
|
||||
}
|
||||
}
|
||||
|
||||
params += "&audience=" + resourceServerId;
|
||||
|
||||
var permissions = authorizationRequest.permissions;
|
||||
|
||||
if (!permissions) {
|
||||
permissions = [];
|
||||
}
|
||||
|
||||
for (i = 0; i < permissions.length; i++) {
|
||||
var resource = permissions[i];
|
||||
var permission = resource.id;
|
||||
|
||||
if (resource.scopes && resource.scopes.length > 0) {
|
||||
permission += "#";
|
||||
for (j = 0; j < resource.scopes.length; j++) {
|
||||
var scope = resource.scopes[j];
|
||||
if (permission.indexOf('#') != permission.length - 1) {
|
||||
permission += ",";
|
||||
}
|
||||
permission += scope;
|
||||
}
|
||||
}
|
||||
|
||||
params += "&permission=" + permission;
|
||||
}
|
||||
|
||||
var metadata = authorizationRequest.metadata;
|
||||
|
||||
if (metadata) {
|
||||
if (metadata.responseIncludeResourceName) {
|
||||
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
|
||||
}
|
||||
if (metadata.responsePermissionsLimit) {
|
||||
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
|
||||
}
|
||||
}
|
||||
|
||||
if (_instance.rpt) {
|
||||
params += "&rpt=" + _instance.rpt;
|
||||
}
|
||||
|
||||
request.send(params);
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
this.init(this);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
|
||||
|
||||
+511
-478
@@ -1,478 +1,511 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export as namespace Keycloak;
|
||||
|
||||
export = Keycloak;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
|
||||
declare namespace Keycloak {
|
||||
type KeycloakAdapterName = 'cordova'|'default';
|
||||
type KeycloakOnLoad = 'login-required'|'check-sso';
|
||||
type KeycloakResponseMode = 'query'|'fragment';
|
||||
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
|
||||
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
|
||||
|
||||
interface KeycloakInitOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
adapter?: KeycloakAdapterName;
|
||||
|
||||
/**
|
||||
* Specifies an action to do on load.
|
||||
*/
|
||||
onLoad?: KeycloakOnLoad;
|
||||
|
||||
/**
|
||||
* Set an initial value for the token.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the refresh token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the id token (only together with `token` or
|
||||
* `refreshToken`).
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for skew between local time and Keycloak server in
|
||||
* seconds (only together with `token` or `refreshToken`).
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* Set to enable/disable monitoring login state.
|
||||
* @default true
|
||||
*/
|
||||
checkLoginIframe?: boolean;
|
||||
|
||||
/**
|
||||
* Set the interval to check login state (in seconds).
|
||||
* @default 5
|
||||
*/
|
||||
checkLoginIframeInterval?: boolean;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect response mode to send to Keycloak upon login.
|
||||
* @default fragment After successful authentication Keycloak will redirect
|
||||
* to JavaScript application with OpenID Connect parameters
|
||||
* added in URL fragment. This is generally safer and
|
||||
* recommended over query.
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect flow.
|
||||
* @default standard
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
}
|
||||
|
||||
interface KeycloakLoginOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
scope?: string;
|
||||
|
||||
/**
|
||||
* Specifies the uri to redirect to after login.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* By default the login screen is displayed if the user is not logged into
|
||||
* Keycloak. To only authenticate to the application if the user is already
|
||||
* logged in and not display the login page if the user is not logged in, set
|
||||
* this option to `'none'`. To always require re-authentication and ignore
|
||||
* SSO, set this option to `'login'`.
|
||||
*/
|
||||
prompt?: 'none'|'login';
|
||||
|
||||
/**
|
||||
* If value is `'register'` then user is redirected to registration page,
|
||||
* otherwise to login page.
|
||||
*/
|
||||
action?: 'register';
|
||||
|
||||
/**
|
||||
* Used just if user is already authenticated. Specifies maximum time since
|
||||
* the authentication of user happened. If user is already authenticated for
|
||||
* longer time than `'maxAge'`, the SSO is ignored and he will need to
|
||||
* authenticate again.
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Used to pre-fill the username/email field on the login form.
|
||||
*/
|
||||
loginHint?: string;
|
||||
|
||||
/**
|
||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||
*/
|
||||
idpHint?: string;
|
||||
|
||||
/**
|
||||
* Specifies the desired locale for the UI.
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
|
||||
interface KeycloakPromise<TSuccess, TError> {
|
||||
/**
|
||||
* Function to call if the promised action succeeds.
|
||||
*/
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
|
||||
/**
|
||||
* Function to call if the promised action throws an error.
|
||||
*/
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
|
||||
interface KeycloakError {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
|
||||
interface KeycloakProfile {
|
||||
id?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
totp?: boolean;
|
||||
createdTimestamp?: number;
|
||||
}
|
||||
|
||||
// export interface KeycloakUserInfo {}
|
||||
|
||||
/**
|
||||
* A client for the Keycloak authentication server.
|
||||
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
|
||||
*/
|
||||
interface KeycloakInstance {
|
||||
/**
|
||||
* Is true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
authenticated?: boolean;
|
||||
|
||||
/**
|
||||
* The user id.
|
||||
*/
|
||||
subject?: string;
|
||||
|
||||
/**
|
||||
* Response mode passed in init (default value is `'fragment'`).
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Response type sent to Keycloak with login requests. This is determined
|
||||
* based on the flow value used during initialization, but can be overridden
|
||||
* by setting this value.
|
||||
*/
|
||||
responseType?: KeycloakResponseType;
|
||||
|
||||
/**
|
||||
* Flow passed in init.
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
|
||||
/**
|
||||
* The realm roles associated with the token.
|
||||
*/
|
||||
realmAccess?: { roles: string[] };
|
||||
|
||||
/**
|
||||
* The resource roles associated with the token.
|
||||
*/
|
||||
resourceAccess?: string[];
|
||||
|
||||
/**
|
||||
* The base64 encoded token that can be sent in the Authorization header in
|
||||
* requests to services.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The parsed token as a JavaScript object.
|
||||
*/
|
||||
tokenParsed?: {
|
||||
exp?: number;
|
||||
iat?: number;
|
||||
nonce?: string;
|
||||
sub?: string;
|
||||
session_state?: string;
|
||||
realm_access?: { roles: string[] };
|
||||
resource_access?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The base64 encoded refresh token that can be used to retrieve a new token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed refresh token as a JavaScript object.
|
||||
*/
|
||||
refreshTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The base64 encoded ID token.
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed id token as a JavaScript object.
|
||||
*/
|
||||
idTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The estimated time difference between the browser time and the Keycloak
|
||||
* server in seconds. This value is just an estimation, but is accurate
|
||||
* enough when determining if a token is expired or not.
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loginRequired?: boolean;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
authServerUrl?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
realm?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
profile?: KeycloakProfile;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
userInfo?: {}; // KeycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Called when the adapter is initialized.
|
||||
*/
|
||||
onReady?(authenticated?: boolean): void;
|
||||
|
||||
/**
|
||||
* Called when a user is successfully authenticated.
|
||||
*/
|
||||
onAuthSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error during authentication.
|
||||
*/
|
||||
onAuthError?(errorData: KeycloakError): void;
|
||||
|
||||
/**
|
||||
* Called when the token is refreshed.
|
||||
*/
|
||||
onAuthRefreshSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error while trying to refresh the token.
|
||||
*/
|
||||
onAuthRefreshError?(): void;
|
||||
|
||||
/**
|
||||
* Called if the user is logged out (will only be called if the session
|
||||
* status iframe is enabled, or in Cordova mode).
|
||||
*/
|
||||
onAuthLogout?(): void;
|
||||
|
||||
/**
|
||||
* Called when the access token is expired. If a refresh token is available
|
||||
* the token can be refreshed with Keycloak#updateToken, or in cases where
|
||||
* it's not (ie. with implicit flow) you can redirect to login screen to
|
||||
* obtain a new access token.
|
||||
*/
|
||||
onTokenExpired?(): void;
|
||||
|
||||
/**
|
||||
* Called to initialize the adapter.
|
||||
* @param initOptions Initialization options.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
|
||||
|
||||
/**
|
||||
* Redirects to login form.
|
||||
* @param options Login options.
|
||||
*/
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to logout.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to registration form.
|
||||
* @param options Supports same options as Keycloak#login but `action` is
|
||||
* set to `'register'`.
|
||||
*/
|
||||
register(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to the Account Management Console.
|
||||
*/
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Returns the URL to login form.
|
||||
* @param options Supports same options as Keycloak#login.
|
||||
*/
|
||||
createLoginUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to logout the user.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
createLogoutUrl(options?: any): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to registration page.
|
||||
* @param options Supports same options as Keycloak#createLoginUrl but
|
||||
* `action` is set to `'register'`.
|
||||
*/
|
||||
createRegisterUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to the Account Management Console.
|
||||
*/
|
||||
createAccountUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the token has less than `minValidity` seconds left before
|
||||
* it expires.
|
||||
* @param minValidity If not specified, `0` is used.
|
||||
*/
|
||||
isTokenExpired(minValidity?: number): boolean;
|
||||
|
||||
/**
|
||||
* If the token expires within `minValidity` seconds, the token is refreshed.
|
||||
* If the session status iframe is enabled, the session status is also
|
||||
* checked.
|
||||
* @returns A promise to set functions that can be invoked if the token is
|
||||
* still valid, or if the token is no longer valid.
|
||||
* @example
|
||||
* ```js
|
||||
* keycloak.updateToken(5).success(function(refreshed) {
|
||||
* if (refreshed) {
|
||||
* alert('Token was successfully refreshed');
|
||||
* } else {
|
||||
* alert('Token is still valid');
|
||||
* }
|
||||
* }).error(function() {
|
||||
* alert('Failed to refresh the token, or the session has expired');
|
||||
* });
|
||||
*/
|
||||
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
|
||||
|
||||
/**
|
||||
* Clears authentication state, including tokens. This can be useful if
|
||||
* the application has detected the session was expired, for example if
|
||||
* updating token fails. Invoking this results in Keycloak#onAuthLogout
|
||||
* callback listener being invoked.
|
||||
*/
|
||||
clearToken(): void;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given realm role.
|
||||
* @param role A realm role name.
|
||||
*/
|
||||
hasRealmRole(role: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given role for the resource.
|
||||
* @param role A role name.
|
||||
* @param resource If not specified, `clientId` is used.
|
||||
*/
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
|
||||
/**
|
||||
* Loads the user's profile.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loadUserInfo(): KeycloakPromise<{}, void>;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export as namespace Keycloak;
|
||||
|
||||
export = Keycloak;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
|
||||
declare namespace Keycloak {
|
||||
type KeycloakAdapterName = 'cordova' | 'cordova-native' |'default' | any;
|
||||
type KeycloakOnLoad = 'login-required'|'check-sso';
|
||||
type KeycloakResponseMode = 'query'|'fragment';
|
||||
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
|
||||
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
|
||||
|
||||
interface KeycloakInitOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
useNonce?: boolean;
|
||||
|
||||
/**
|
||||
* Allows to use different adapter:
|
||||
*
|
||||
* - {string} default - using browser api for redirects
|
||||
* - {string} cordova - using cordova plugins
|
||||
* - {function} - allows to provide custom function as adapter.
|
||||
*/
|
||||
adapter?: KeycloakAdapterName;
|
||||
|
||||
/**
|
||||
* Specifies an action to do on load.
|
||||
*/
|
||||
onLoad?: KeycloakOnLoad;
|
||||
|
||||
/**
|
||||
* Set an initial value for the token.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the refresh token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the id token (only together with `token` or
|
||||
* `refreshToken`).
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for skew between local time and Keycloak server in
|
||||
* seconds (only together with `token` or `refreshToken`).
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* Set to enable/disable monitoring login state.
|
||||
* @default true
|
||||
*/
|
||||
checkLoginIframe?: boolean;
|
||||
|
||||
/**
|
||||
* Set the interval to check login state (in seconds).
|
||||
* @default 5
|
||||
*/
|
||||
checkLoginIframeInterval?: number;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect response mode to send to Keycloak upon login.
|
||||
* @default fragment After successful authentication Keycloak will redirect
|
||||
* to JavaScript application with OpenID Connect parameters
|
||||
* added in URL fragment. This is generally safer and
|
||||
* recommended over query.
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Specifies a default uri to redirect to after login or logout.
|
||||
* This is currently supported for adapter 'cordova-native' and 'default'
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect flow.
|
||||
* @default standard
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
}
|
||||
|
||||
interface KeycloakLoginOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
scope?: string;
|
||||
|
||||
/**
|
||||
* Specifies the uri to redirect to after login.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* By default the login screen is displayed if the user is not logged into
|
||||
* Keycloak. To only authenticate to the application if the user is already
|
||||
* logged in and not display the login page if the user is not logged in, set
|
||||
* this option to `'none'`. To always require re-authentication and ignore
|
||||
* SSO, set this option to `'login'`.
|
||||
*/
|
||||
prompt?: 'none'|'login';
|
||||
|
||||
/**
|
||||
* If value is `'register'` then user is redirected to registration page,
|
||||
* otherwise to login page.
|
||||
*/
|
||||
action?: 'register';
|
||||
|
||||
/**
|
||||
* Used just if user is already authenticated. Specifies maximum time since
|
||||
* the authentication of user happened. If user is already authenticated for
|
||||
* longer time than `'maxAge'`, the SSO is ignored and he will need to
|
||||
* authenticate again.
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Used to pre-fill the username/email field on the login form.
|
||||
*/
|
||||
loginHint?: string;
|
||||
|
||||
/**
|
||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||
*/
|
||||
idpHint?: string;
|
||||
|
||||
/**
|
||||
* Sets the 'ui_locales' query param in compliance with section 3.1.2.1
|
||||
* of the OIDC 1.0 specification.
|
||||
*/
|
||||
locale?: string;
|
||||
|
||||
/**
|
||||
* Specifies the desired Keycloak locale for the UI. This differs from
|
||||
* the locale param in that it tells the Keycloak server to set a cookie and update
|
||||
* the user's profile to a new preferred locale.
|
||||
*/
|
||||
kcLocale?: string;
|
||||
|
||||
/**
|
||||
* Specifies arguments that are passed to the Cordova in-app-browser (if applicable).
|
||||
* Options 'hidden' and 'location' are not affected by these arguments.
|
||||
* All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/.
|
||||
* Example of use: { zoom: "no", hardwareback: "yes" }
|
||||
*/
|
||||
cordovaOptions?: { [optionName: string]: string };
|
||||
}
|
||||
|
||||
type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
|
||||
interface KeycloakPromise<TSuccess, TError> {
|
||||
/**
|
||||
* Function to call if the promised action succeeds.
|
||||
*/
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
|
||||
/**
|
||||
* Function to call if the promised action throws an error.
|
||||
*/
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
|
||||
interface KeycloakError {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
|
||||
interface KeycloakProfile {
|
||||
id?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
totp?: boolean;
|
||||
createdTimestamp?: number;
|
||||
}
|
||||
|
||||
interface KeycloakTokenParsed {
|
||||
exp?: number;
|
||||
iat?: number;
|
||||
nonce?: string;
|
||||
sub?: string;
|
||||
session_state?: string;
|
||||
realm_access?: { roles: string[] };
|
||||
resource_access?: string[];
|
||||
}
|
||||
|
||||
// export interface KeycloakUserInfo {}
|
||||
|
||||
/**
|
||||
* A client for the Keycloak authentication server.
|
||||
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
|
||||
*/
|
||||
interface KeycloakInstance {
|
||||
/**
|
||||
* Is true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
authenticated?: boolean;
|
||||
|
||||
/**
|
||||
* The user id.
|
||||
*/
|
||||
subject?: string;
|
||||
|
||||
/**
|
||||
* Response mode passed in init (default value is `'fragment'`).
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Response type sent to Keycloak with login requests. This is determined
|
||||
* based on the flow value used during initialization, but can be overridden
|
||||
* by setting this value.
|
||||
*/
|
||||
responseType?: KeycloakResponseType;
|
||||
|
||||
/**
|
||||
* Flow passed in init.
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
|
||||
/**
|
||||
* The realm roles associated with the token.
|
||||
*/
|
||||
realmAccess?: { roles: string[] };
|
||||
|
||||
/**
|
||||
* The resource roles associated with the token.
|
||||
*/
|
||||
resourceAccess?: string[];
|
||||
|
||||
/**
|
||||
* The base64 encoded token that can be sent in the Authorization header in
|
||||
* requests to services.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The parsed token as a JavaScript object.
|
||||
*/
|
||||
tokenParsed?: KeycloakTokenParsed;
|
||||
|
||||
/**
|
||||
* The base64 encoded refresh token that can be used to retrieve a new token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed refresh token as a JavaScript object.
|
||||
*/
|
||||
refreshTokenParsed?: KeycloakTokenParsed;
|
||||
|
||||
/**
|
||||
* The base64 encoded ID token.
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed id token as a JavaScript object.
|
||||
*/
|
||||
idTokenParsed?: KeycloakTokenParsed;
|
||||
|
||||
/**
|
||||
* The estimated time difference between the browser time and the Keycloak
|
||||
* server in seconds. This value is just an estimation, but is accurate
|
||||
* enough when determining if a token is expired or not.
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loginRequired?: boolean;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
authServerUrl?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
realm?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
profile?: KeycloakProfile;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
userInfo?: {}; // KeycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Called when the adapter is initialized.
|
||||
*/
|
||||
onReady?(authenticated?: boolean): void;
|
||||
|
||||
/**
|
||||
* Called when a user is successfully authenticated.
|
||||
*/
|
||||
onAuthSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error during authentication.
|
||||
*/
|
||||
onAuthError?(errorData: KeycloakError): void;
|
||||
|
||||
/**
|
||||
* Called when the token is refreshed.
|
||||
*/
|
||||
onAuthRefreshSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error while trying to refresh the token.
|
||||
*/
|
||||
onAuthRefreshError?(): void;
|
||||
|
||||
/**
|
||||
* Called if the user is logged out (will only be called if the session
|
||||
* status iframe is enabled, or in Cordova mode).
|
||||
*/
|
||||
onAuthLogout?(): void;
|
||||
|
||||
/**
|
||||
* Called when the access token is expired. If a refresh token is available
|
||||
* the token can be refreshed with Keycloak#updateToken, or in cases where
|
||||
* it's not (ie. with implicit flow) you can redirect to login screen to
|
||||
* obtain a new access token.
|
||||
*/
|
||||
onTokenExpired?(): void;
|
||||
|
||||
/**
|
||||
* Called to initialize the adapter.
|
||||
* @param initOptions Initialization options.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
|
||||
|
||||
/**
|
||||
* Redirects to login form.
|
||||
* @param options Login options.
|
||||
*/
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to logout.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to registration form.
|
||||
* @param options Supports same options as Keycloak#login but `action` is
|
||||
* set to `'register'`.
|
||||
*/
|
||||
register(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to the Account Management Console.
|
||||
*/
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Returns the URL to login form.
|
||||
* @param options Supports same options as Keycloak#login.
|
||||
*/
|
||||
createLoginUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to logout the user.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
createLogoutUrl(options?: any): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to registration page.
|
||||
* @param options Supports same options as Keycloak#createLoginUrl but
|
||||
* `action` is set to `'register'`.
|
||||
*/
|
||||
createRegisterUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to the Account Management Console.
|
||||
*/
|
||||
createAccountUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the token has less than `minValidity` seconds left before
|
||||
* it expires.
|
||||
* @param minValidity If not specified, `0` is used.
|
||||
*/
|
||||
isTokenExpired(minValidity?: number): boolean;
|
||||
|
||||
/**
|
||||
* If the token expires within `minValidity` seconds, the token is refreshed.
|
||||
* If the session status iframe is enabled, the session status is also
|
||||
* checked.
|
||||
* @returns A promise to set functions that can be invoked if the token is
|
||||
* still valid, or if the token is no longer valid.
|
||||
* @example
|
||||
* ```js
|
||||
* keycloak.updateToken(5).success(function(refreshed) {
|
||||
* if (refreshed) {
|
||||
* alert('Token was successfully refreshed');
|
||||
* } else {
|
||||
* alert('Token is still valid');
|
||||
* }
|
||||
* }).error(function() {
|
||||
* alert('Failed to refresh the token, or the session has expired');
|
||||
* });
|
||||
*/
|
||||
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
|
||||
|
||||
/**
|
||||
* Clears authentication state, including tokens. This can be useful if
|
||||
* the application has detected the session was expired, for example if
|
||||
* updating token fails. Invoking this results in Keycloak#onAuthLogout
|
||||
* callback listener being invoked.
|
||||
*/
|
||||
clearToken(): void;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given realm role.
|
||||
* @param role A realm role name.
|
||||
*/
|
||||
hasRealmRole(role: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given role for the resource.
|
||||
* @param role A role name.
|
||||
* @param resource If not specified, `clientId` is used.
|
||||
*/
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
|
||||
/**
|
||||
* Loads the user's profile.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loadUserInfo(): KeycloakPromise<{}, void>;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -23,9 +23,20 @@
|
||||
function checkState(clientId, origin, sessionState, callback) {
|
||||
var cookie = getCookie();
|
||||
|
||||
if (!cookie) {
|
||||
callback('changed');
|
||||
} else if (!init) {
|
||||
var checkCookie = function() {
|
||||
if (clientId === init.clientId && origin === init.origin) {
|
||||
var c = cookie.split('/');
|
||||
if (sessionState === c[2]) {
|
||||
callback('unchanged');
|
||||
} else {
|
||||
callback('changed');
|
||||
}
|
||||
} else {
|
||||
callback('error');
|
||||
}
|
||||
}
|
||||
|
||||
if (!init) {
|
||||
var req = new XMLHttpRequest();
|
||||
|
||||
var url = location.href.split("?")[0] + "/init";
|
||||
@@ -41,9 +52,15 @@
|
||||
clientId: clientId,
|
||||
origin: origin
|
||||
}
|
||||
callback('unchanged');
|
||||
} else if (req.status === 404) {
|
||||
callback('changed');
|
||||
if (!cookie) {
|
||||
if (sessionState != '') {
|
||||
callback('changed');
|
||||
} else {
|
||||
callback('unchanged');
|
||||
}
|
||||
} else {
|
||||
checkCookie();
|
||||
}
|
||||
} else {
|
||||
callback('error');
|
||||
}
|
||||
@@ -51,17 +68,14 @@
|
||||
};
|
||||
|
||||
req.send();
|
||||
} else {
|
||||
if (clientId === init.clientId && origin === init.origin) {
|
||||
var c = cookie.split('/');
|
||||
if (sessionState === c[2]) {
|
||||
callback('unchanged');
|
||||
} else {
|
||||
callback('changed');
|
||||
}
|
||||
} else if (!cookie) {
|
||||
if (sessionState != '') {
|
||||
callback('changed');
|
||||
} else {
|
||||
callback('error');
|
||||
callback('unchanged');
|
||||
}
|
||||
} else {
|
||||
checkCookie();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +93,10 @@
|
||||
|
||||
function receiveMessage(event)
|
||||
{
|
||||
if (typeof event.data !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
var origin = event.origin;
|
||||
var data = event.data.split(' ');
|
||||
if (data.length != 2) {
|
||||
|
||||
File renamed without changes.
Executable
+76
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>kcinit</artifactId>
|
||||
<name>Keycloak CLI SSO Framework</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-installed-adapter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.keycloak.adapters.KcinitMain</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
case "`uname`" in
|
||||
CYGWIN*)
|
||||
CFILE = `cygpath "$0"`
|
||||
RESOLVED_NAME=`readlink -f "$CFILE"`
|
||||
;;
|
||||
Darwin*)
|
||||
RESOLVED_NAME=`readlink "$0"`
|
||||
;;
|
||||
FreeBSD)
|
||||
RESOLVED_NAME=`readlink -f "$0"`
|
||||
;;
|
||||
Linux)
|
||||
RESOLVED_NAME=`readlink -f "$0"`
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "x$RESOLVED_NAME" = "x" ]; then
|
||||
RESOLVED_NAME="$0"
|
||||
fi
|
||||
|
||||
SCRIPTPATH=`dirname "$RESOLVED_NAME"`
|
||||
JAR=$SCRIPTPATH/kcinit-${project.version}.jar
|
||||
|
||||
java -jar $JAR $@
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
|
||||
if "%OS%" == "Windows_NT" (
|
||||
set "DIRNAME=%~dp0%"
|
||||
) else (
|
||||
set DIRNAME=.\
|
||||
)
|
||||
java -jar %DIRNAME%\kcinit-${project.version}.jar %*
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.adapters.installed.KcinitDriver;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KcinitMain extends KcinitDriver {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new KcinitMain().mainCmd(args);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.3.Final</version>
|
||||
<version>4.7.0.Final-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -31,12 +31,15 @@
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>1.7</maven.compiler.target>
|
||||
<maven.compiler.source>1.7</maven.compiler.source>
|
||||
|
||||
<jetty9.version>8.1.17.v20150415</jetty9.version>
|
||||
<keycloak.osgi.export>
|
||||
org.keycloak.adapters.osgi.*
|
||||
</keycloak.osgi.export>
|
||||
<keycloak.osgi.import>
|
||||
org.ops4j.pax.web.*;version="[3.0,5)",
|
||||
org.ops4j.pax.web.*;version="[3.0,8)",
|
||||
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
||||
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
|
||||
org.keycloak.*;version="${project.version}",
|
||||
@@ -77,11 +80,6 @@
|
||||
<artifactId>org.osgi.enterprise</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-runtime</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.web</groupId>
|
||||
<artifactId>pax-web-api</artifactId>
|
||||
@@ -99,19 +97,16 @@
|
||||
<version>${cxf.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.osgi;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
|
||||
/**
|
||||
* This {@link KeycloakConfigResolver} tries to resolve most specific configuration for given URI path. If not found,
|
||||
* <em>parent</em> path is checked up to top-level path.
|
||||
*/
|
||||
public class HierarchicalPathBasedKeycloakConfigResolver extends PathBasedKeycloakConfigResolver {
|
||||
|
||||
protected static final Logger log = Logger.getLogger(HierarchicalPathBasedKeycloakConfigResolver.class);
|
||||
|
||||
public HierarchicalPathBasedKeycloakConfigResolver() {
|
||||
prepopulateCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
|
||||
// we cached all available deployments initially and now we'll try to check them from
|
||||
// most specific to most general
|
||||
URI uri = URI.create(request.getURI());
|
||||
String path = uri.getPath();
|
||||
if (path != null) {
|
||||
while (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
String[] segments = path.split("/");
|
||||
List<String> paths = collectPaths(segments);
|
||||
for (String pathFragment: paths) {
|
||||
KeycloakDeployment cachedDeployment = super.getCachedDeployment(pathFragment);
|
||||
if (cachedDeployment != null) {
|
||||
return cachedDeployment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Can't find Keycloak configuration related to URI path " + uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>For segments like "a, b, c, d", returns:<ul>
|
||||
* <li>"a-b-c-d"</li>
|
||||
* <li>"a-b-c"</li>
|
||||
* <li>"a-b"</li>
|
||||
* <li>"a"</li>
|
||||
* <li>""</li>
|
||||
* </ul></p>
|
||||
* @param segments
|
||||
* @return
|
||||
*/
|
||||
private List<String> collectPaths(String[] segments) {
|
||||
List<String> result = new ArrayList<>(segments.length + 1);
|
||||
for (int idx = segments.length; idx >= 0; idx--) {
|
||||
StringBuilder sb = null;
|
||||
for (int i = 0; i < idx; i++) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
sb.append("-").append(segments[i]);
|
||||
}
|
||||
result.add(sb == null ? "" : sb.toString().substring(1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 6314 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user