Compare commits
1201
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a5087cf7e | ||
|
|
7a91cd5545 | ||
|
|
1e58a8207d | ||
|
|
dd48b01d83 | ||
|
|
f548c210e3 | ||
|
|
4569fb761d | ||
|
|
930da93182 | ||
|
|
05ae84d533 | ||
|
|
59674e44bf | ||
|
|
efaa88b4e3 | ||
|
|
f55a526ebb | ||
|
|
ccb9433e5c | ||
|
|
9d79a847c8 | ||
|
|
1630b9a20c | ||
|
|
65befb16fd | ||
|
|
e8f99a2109 | ||
|
|
7af16fc747 | ||
|
|
84f5c0926b | ||
|
|
11245701d2 | ||
|
|
5c45c5e6e4 | ||
|
|
04ee5ca7c1 | ||
|
|
df5d37a8d9 | ||
|
|
55a86dc0f4 | ||
|
|
fa8f60a5f0 | ||
|
|
2a5c778af5 | ||
|
|
f24d73e8d6 | ||
|
|
6d40e0dd07 | ||
|
|
529ef3a425 | ||
|
|
36bb94afb8 | ||
|
|
4fd0238ca9 | ||
|
|
76e1160b36 | ||
|
|
df3079852e | ||
|
|
71b648a9b6 | ||
|
|
ab7bb21991 | ||
|
|
348e0b6226 | ||
|
|
44b7210af7 | ||
|
|
f726caea9b | ||
|
|
16282aeb7b | ||
|
|
d2c546bdc2 | ||
|
|
1f27fc9e4b | ||
|
|
7c292b1213 | ||
|
|
3b9a6b32e1 | ||
|
|
b0bb0d36ab | ||
|
|
cdda19d1f8 | ||
|
|
759747ab5c | ||
|
|
01e48dc4b8 | ||
|
|
61b93c318f | ||
|
|
3f35234cf5 | ||
|
|
da135389c7 | ||
|
|
b3d7d48ec8 | ||
|
|
5a015a6518 | ||
|
|
7912bd4dae | ||
|
|
c860d03a60 | ||
|
|
1b2a5eda32 | ||
|
|
517413d38e | ||
|
|
fd25dbcb5c | ||
|
|
bdbc4e1f6d | ||
|
|
eebd496c2f | ||
|
|
15d31a202f | ||
|
|
6714c1a136 | ||
|
|
ca6d1f3c48 | ||
|
|
c5a149a892 | ||
|
|
b33648cc29 | ||
|
|
4a583712b8 | ||
|
|
5d20651c66 | ||
|
|
a49a288bbe | ||
|
|
fbace351b3 | ||
|
|
fbb2dfcf59 | ||
|
|
05813832fc | ||
|
|
e67f8c7a61 | ||
|
|
7a66b055be | ||
|
|
e3ddc5a639 | ||
|
|
8c5b1e4892 | ||
|
|
ad1d3adb9e | ||
|
|
e18f3edbcd | ||
|
|
6ede760725 | ||
|
|
db0f05fb86 | ||
|
|
03c05bd72b | ||
|
|
c867fcf9e7 | ||
|
|
22e85b11eb | ||
|
|
39fe439573 | ||
|
|
fc6b7ea8ee | ||
|
|
0e1edb3d7a | ||
|
|
7a77055e58 | ||
|
|
d9e95455a2 | ||
|
|
f5b4efc080 | ||
|
|
b1a233e41f | ||
|
|
4d6e656b02 | ||
|
|
3354e7c98e | ||
|
|
05fe17be4f | ||
|
|
2e9f14dfd8 | ||
|
|
404aa69adb | ||
|
|
a24a43c4be | ||
|
|
892d5fd1b7 | ||
|
|
258f7c8d60 | ||
|
|
b17bd9e660 | ||
|
|
43d23835d0 | ||
|
|
d11efa363c | ||
|
|
8f0afb2551 | ||
|
|
c874f96228 | ||
|
|
ce78cc1d1c | ||
|
|
599c69a2a3 | ||
|
|
9daced953e | ||
|
|
1b085d3e13 | ||
|
|
1bea93b1e1 | ||
|
|
16bd2fb8f8 | ||
|
|
e8efe10fc8 | ||
|
|
73bcfba5f3 | ||
|
|
02f28a7e8e | ||
|
|
5a4bb5f3f0 | ||
|
|
235623301e | ||
|
|
f4aee129e4 | ||
|
|
a7f9a6e095 | ||
|
|
539d9863aa | ||
|
|
4f51b7b34c | ||
|
|
0e5af3710e | ||
|
|
a462dd78bb | ||
|
|
ec5289b5c8 | ||
|
|
2a29f2a9c6 | ||
|
|
9ffd6548ce | ||
|
|
cfd41fbc8b | ||
|
|
4c2cca4672 | ||
|
|
53557d9cfd | ||
|
|
a0054bd77f | ||
|
|
099de9e6e3 | ||
|
|
cfd61dc359 | ||
|
|
99f453b25c | ||
|
|
d138b19adb | ||
|
|
345163994a | ||
|
|
09090cec24 | ||
|
|
3abcf713e5 | ||
|
|
e4d97485ec | ||
|
|
1489bd734a | ||
|
|
ef442cae92 | ||
|
|
3493aa4ab7 | ||
|
|
3be47dee37 | ||
|
|
494d9973a6 | ||
|
|
e28a3750cd | ||
|
|
014c8e068f | ||
|
|
3890867383 | ||
|
|
d499b8a489 | ||
|
|
352602da39 | ||
|
|
c333d1eaa9 | ||
|
|
c522a20ab9 | ||
|
|
ee66cb51da | ||
|
|
93d65fa895 | ||
|
|
6aafde7819 | ||
|
|
365a1b66c8 | ||
|
|
fa1fb3a3a9 | ||
|
|
b109ce14b0 | ||
|
|
561661d726 | ||
|
|
6592014707 | ||
|
|
4cd0a8e894 | ||
|
|
6c44e9befa | ||
|
|
8e9cfc4a28 | ||
|
|
d20d5ce2d7 | ||
|
|
243c8cc2cd | ||
|
|
7d444c8b13 | ||
|
|
be83e2fb6b | ||
|
|
b34acd4cc2 | ||
|
|
85ac784fd0 | ||
|
|
ab2368be34 | ||
|
|
ba076ad159 | ||
|
|
a8d2b810cf | ||
|
|
285a99d903 | ||
|
|
fd983690e2 | ||
|
|
006a0f856e | ||
|
|
bfe10e34e7 | ||
|
|
2cba13db9c | ||
|
|
3eb9134e02 | ||
|
|
b7f2e0b5ff | ||
|
|
27187c11f1 | ||
|
|
0030df060b | ||
|
|
f6f587e472 | ||
|
|
841c6b619a | ||
|
|
e0d70a35d6 | ||
|
|
0520d465c1 | ||
|
|
a8fb988e31 | ||
|
|
c34d175572 | ||
|
|
9c7442c75b | ||
|
|
de888fc8a1 | ||
|
|
4d8e19eb7c | ||
|
|
2df7d6252e | ||
|
|
d52e043322 | ||
|
|
6c8d55d534 | ||
|
|
26bc07b2c4 | ||
|
|
7f6fc170dc | ||
|
|
741bbf471f | ||
|
|
70eb27ec83 | ||
|
|
96b729886f | ||
|
|
530870f05e | ||
|
|
1633f062d5 | ||
|
|
ff703f935f | ||
|
|
4974079794 | ||
|
|
d94515cdae | ||
|
|
f838c697d1 | ||
|
|
d14de43ac7 | ||
|
|
e198e7e0b9 | ||
|
|
83306963e8 | ||
|
|
65e2f127c9 | ||
|
|
64c2077c0b | ||
|
|
9be6777685 | ||
|
|
f14f303dfe | ||
|
|
33d7d89ad9 | ||
|
|
65c49c39f4 | ||
|
|
6c0f685c39 | ||
|
|
e0a59baaf2 | ||
|
|
6a797a61a9 | ||
|
|
e49afb2d83 | ||
|
|
0d225f44de | ||
|
|
586f6eeece | ||
|
|
f91907c8f9 | ||
|
|
9e0667bc11 | ||
|
|
a3ffb32532 | ||
|
|
1e7cf9fb3f | ||
|
|
f75c0d3e61 | ||
|
|
534ee2e50c | ||
|
|
70722d0d3d | ||
|
|
0292acd5e2 | ||
|
|
bf3541414c | ||
|
|
9242e972c0 | ||
|
|
57a96fa6aa | ||
|
|
7f08717dfb | ||
|
|
9365a40426 | ||
|
|
ae58d64741 | ||
|
|
4a8b4bcdec | ||
|
|
e3aec098a2 | ||
|
|
3b9b673e5e | ||
|
|
17e75950fe | ||
|
|
1c75b03e59 | ||
|
|
27814dab65 | ||
|
|
b3a55df1e7 | ||
|
|
7ec3813a07 | ||
|
|
62b96143a1 | ||
|
|
ec6b81e42d | ||
|
|
3a5d4a3723 | ||
|
|
1d695237b7 | ||
|
|
09693eb108 | ||
|
|
3b3368eead | ||
|
|
a5cbe113fd | ||
|
|
9688880837 | ||
|
|
98be930027 | ||
|
|
83b4495e69 | ||
|
|
bd73b0aee6 | ||
|
|
ae1a7542d8 | ||
|
|
7d1bdf6e81 | ||
|
|
d64e7394a7 | ||
|
|
059011e82f | ||
|
|
eaf28f72c1 | ||
|
|
aa177dd9f9 | ||
|
|
a8a77add39 | ||
|
|
5facec73e4 | ||
|
|
91a267a0d8 | ||
|
|
0147fbe489 | ||
|
|
a9ccabdce5 | ||
|
|
e04ad9d7b6 | ||
|
|
c5d40a76ff | ||
|
|
79c7203fe8 | ||
|
|
0d99b797b6 | ||
|
|
59f228be2b | ||
|
|
159b752fb0 | ||
|
|
b0c7746eeb | ||
|
|
bbeac6d741 | ||
|
|
a8eb3ca65a | ||
|
|
fea277a7f5 | ||
|
|
cd94778dec | ||
|
|
bd5b434894 | ||
|
|
26f0f6509a | ||
|
|
7983ed064f | ||
|
|
607b305c2f | ||
|
|
8ad3c3fb03 | ||
|
|
824890323d | ||
|
|
5011960cac | ||
|
|
3c8ed8e3d8 | ||
|
|
e6ce2e138d | ||
|
|
8cfa50f134 | ||
|
|
2b3859f462 | ||
|
|
5d9fe09599 | ||
|
|
2219cd363e | ||
|
|
b08800fbd8 | ||
|
|
7e1b97888a | ||
|
|
322597b420 | ||
|
|
bd499e1c55 | ||
|
|
46b4bb0909 | ||
|
|
59e0570cdf | ||
|
|
c925033944 | ||
|
|
94f583e935 | ||
|
|
69f051fb0a | ||
|
|
3973aed57d | ||
|
|
73d3db6d00 | ||
|
|
535a0763fc | ||
|
|
283581f920 | ||
|
|
488dee71d2 | ||
|
|
b6811b96b9 | ||
|
|
db852f7e11 | ||
|
|
01830fd7f3 | ||
|
|
00d439b6f0 | ||
|
|
06fca9c897 | ||
|
|
72d134748c | ||
|
|
e2ad7608c8 | ||
|
|
a73dd537f3 | ||
|
|
9169bcd88d | ||
|
|
94af2e1789 | ||
|
|
56e011dce4 | ||
|
|
079026a7be | ||
|
|
7c6cf8f3b7 | ||
|
|
981ff90c81 | ||
|
|
484d5d6e08 | ||
|
|
0315bd0b87 | ||
|
|
2a05f00d0c | ||
|
|
05a804b972 | ||
|
|
f4ddfe4a52 | ||
|
|
76c4fbb241 | ||
|
|
3b0fd71155 | ||
|
|
6f92bac782 | ||
|
|
8b535c5da6 | ||
|
|
e719722e32 | ||
|
|
c709598fdd | ||
|
|
3ef0527a91 | ||
|
|
eb12a307f7 | ||
|
|
f36a68308d | ||
|
|
7571dc07f9 | ||
|
|
e03bf6eef6 | ||
|
|
0e470d75e9 | ||
|
|
187f4a49ad | ||
|
|
0a5e061ca6 | ||
|
|
1b517a461e | ||
|
|
a913b8a158 | ||
|
|
f574173ed3 | ||
|
|
a6bdf81e6d | ||
|
|
a42e4af78d | ||
|
|
b1ae0a9000 | ||
|
|
07d7141d98 | ||
|
|
0632bc8939 | ||
|
|
e708c53730 | ||
|
|
243105f952 | ||
|
|
1ce17c459d | ||
|
|
daa4d23ac0 | ||
|
|
30d651bcee | ||
|
|
fda0a79e27 | ||
|
|
021c2ec701 | ||
|
|
fbf00ba1cd | ||
|
|
a2ead8743f | ||
|
|
5a0b86f814 | ||
|
|
970c89dd6a | ||
|
|
f9bfb5d3e5 | ||
|
|
f384c2340e | ||
|
|
2f14fb7fb7 | ||
|
|
fe367d207a | ||
|
|
13a21e5fda | ||
|
|
dcc4ea3aea | ||
|
|
7bc34c0ba4 | ||
|
|
aacf2e9390 | ||
|
|
966a580895 | ||
|
|
bffbc9e198 | ||
|
|
ee3ac3fdaf | ||
|
|
9ddec7d1eb | ||
|
|
4f1d83b9dc | ||
|
|
eeabc0092b | ||
|
|
ea44b5888b | ||
|
|
abde62f369 | ||
|
|
16184d1e9d | ||
|
|
38f89b93ff | ||
|
|
912bc8464e | ||
|
|
d5199501c7 | ||
|
|
f9235cb07d | ||
|
|
d7649c2547 | ||
|
|
be5468a7cf | ||
|
|
eba56e4784 | ||
|
|
26efb2cbee | ||
|
|
02896768f5 | ||
|
|
1d4e76117c | ||
|
|
a912ba3148 | ||
|
|
5b0980172d | ||
|
|
ff8dce77f3 | ||
|
|
7dbdb023a0 | ||
|
|
2fa200889a | ||
|
|
f97d0846ed | ||
|
|
19e5ddeba5 | ||
|
|
df090fa5e1 | ||
|
|
c283f9803c | ||
|
|
3a370e2726 | ||
|
|
73cbf857c1 | ||
|
|
64ad222a28 | ||
|
|
6e94931023 | ||
|
|
ba3b6fd564 | ||
|
|
039bb103c2 | ||
|
|
e946c1c512 | ||
|
|
c67d834d39 | ||
|
|
8bdfd57e9b | ||
|
|
629390dd4a | ||
|
|
3bfd999590 | ||
|
|
80a67149af | ||
|
|
7962f68991 | ||
|
|
c10a005997 | ||
|
|
4dd28c0adf | ||
|
|
4bdd67da79 | ||
|
|
8e24c73ac8 | ||
|
|
bd2887aa77 | ||
|
|
bdc57d57c1 | ||
|
|
38cc183be5 | ||
|
|
930b0d9ad7 | ||
|
|
bf2ca4008e | ||
|
|
a7c9e71490 | ||
|
|
0040d3fc3b | ||
|
|
7e5a5f79cf | ||
|
|
7a161cc8bb | ||
|
|
c5e8a010dc | ||
|
|
5638e9e1c5 | ||
|
|
56e09bf189 | ||
|
|
7aafbcd5d9 | ||
|
|
78fbf45b80 | ||
|
|
bd124d5f9b | ||
|
|
5ef65e837c | ||
|
|
2591dd862b | ||
|
|
159b371973 | ||
|
|
948f37b01a | ||
|
|
1f41b3195f | ||
|
|
4628946a55 | ||
|
|
a231c1b31b | ||
|
|
3fc215d041 | ||
|
|
fed7339558 | ||
|
|
7e0238d1c7 | ||
|
|
2dd0d7189e | ||
|
|
f52504ac41 | ||
|
|
7cfee80e58 | ||
|
|
435cdb6180 | ||
|
|
fe856d3fa7 | ||
|
|
98923d0ea4 | ||
|
|
dd6434a487 | ||
|
|
842b811a41 | ||
|
|
07ab632351 | ||
|
|
9bcfda64fa | ||
|
|
453859d14f | ||
|
|
122cb1f490 | ||
|
|
6bb59b7f63 | ||
|
|
1ec88cf5b0 | ||
|
|
74139ec787 | ||
|
|
f3a780cc2d | ||
|
|
00cb0a798a | ||
|
|
c9bbfc3b0b | ||
|
|
cb085cc598 | ||
|
|
a8b39edd73 | ||
|
|
8b8ebfc7e1 | ||
|
|
d90a708ceb | ||
|
|
450b57c76a | ||
|
|
fa312fb3db | ||
|
|
9384aa1398 | ||
|
|
ce7e7ef1d7 | ||
|
|
a19469aba5 | ||
|
|
b224917fc5 | ||
|
|
3f1eecc4be | ||
|
|
3ba3be877e | ||
|
|
daefbcf994 | ||
|
|
5d9669e5a6 | ||
|
|
1e7745b5c3 | ||
|
|
46d02f15fe | ||
|
|
01f3dddd91 | ||
|
|
2db41ef052 | ||
|
|
afa9471c7c | ||
|
|
08f28de903 | ||
|
|
1fe8da67dc | ||
|
|
591a794247 | ||
|
|
a9e8f315a4 | ||
|
|
c738f9fbd3 | ||
|
|
588f6ba253 | ||
|
|
a9f6948d74 | ||
|
|
f51098c50b | ||
|
|
8b0bf503c3 | ||
|
|
ec402f759b | ||
|
|
bc82f18acb | ||
|
|
a001d53d2a | ||
|
|
1a4d03e283 | ||
|
|
8a8bf47043 | ||
|
|
836ab2820a | ||
|
|
01d0821142 | ||
|
|
47fab2d034 | ||
|
|
fc295ac2ac | ||
|
|
69919902b4 | ||
|
|
a6af72cbbc | ||
|
|
4aa102fcab | ||
|
|
0dc8bb7c3b | ||
|
|
5e450d5cd4 | ||
|
|
38722e8273 | ||
|
|
ce17999d26 | ||
|
|
d869ab2823 | ||
|
|
5dc6f2f835 | ||
|
|
9b9a656192 | ||
|
|
7e985dce85 | ||
|
|
184108c5ff | ||
|
|
f6ce1438fd | ||
|
|
1b9fe8edf7 | ||
|
|
e205535c55 | ||
|
|
5c25619cd6 | ||
|
|
2e2f34d94e | ||
|
|
7eab4bc859 | ||
|
|
074a312fe5 | ||
|
|
7512abe9a7 | ||
|
|
f48288865b | ||
|
|
333bdb62dd | ||
|
|
07824bdc4d | ||
|
|
905421a292 | ||
|
|
363e948141 | ||
|
|
f7a2ad021e | ||
|
|
e2082ce29f | ||
|
|
462843cc9c | ||
|
|
5890f7cae1 | ||
|
|
767c832f24 | ||
|
|
6e4abe19f3 | ||
|
|
107830bd2c | ||
|
|
90d0ee7934 | ||
|
|
5c731b4d14 | ||
|
|
8402cedd82 | ||
|
|
190a1beefa | ||
|
|
0a7504f9a8 | ||
|
|
da945a69e6 | ||
|
|
c4513fdad9 | ||
|
|
48bd019165 | ||
|
|
61be5cc90e | ||
|
|
5f91f4e466 | ||
|
|
d5167c1632 | ||
|
|
44d7d776bc | ||
|
|
f4ead484de | ||
|
|
6a1fb8f870 | ||
|
|
dd279dd0fd | ||
|
|
111bcb7433 | ||
|
|
8f3cfed7c5 | ||
|
|
807fc71626 | ||
|
|
8ae08f0432 | ||
|
|
2beb1f2466 | ||
|
|
1a9fada964 | ||
|
|
9030edc04d | ||
|
|
3c0f7e2ee2 | ||
|
|
086c29112a | ||
|
|
0df2525298 | ||
|
|
cdb40fce19 | ||
|
|
af5ac7dd6b | ||
|
|
524ad9728a | ||
|
|
b50513a946 | ||
|
|
3203842b58 | ||
|
|
2d036b58a6 | ||
|
|
55910f5f70 | ||
|
|
c1e202eaf9 | ||
|
|
e538394e60 | ||
|
|
46b17e6149 | ||
|
|
e6b3586b87 | ||
|
|
bdaba4c85a | ||
|
|
1510ac5eb4 | ||
|
|
1422d2962e | ||
|
|
443772d5ec | ||
|
|
72736e5e47 | ||
|
|
ec180db39f | ||
|
|
f6d075fbe4 | ||
|
|
5fc5bafbb6 | ||
|
|
a189545f81 | ||
|
|
1c694b4795 | ||
|
|
819c42dad2 | ||
|
|
7bdccc21b2 | ||
|
|
a9f461bfd1 | ||
|
|
56a565f913 | ||
|
|
b6a76b1267 | ||
|
|
67a63a806e | ||
|
|
816b01860b | ||
|
|
ce2f009e76 | ||
|
|
d5a44db2de | ||
|
|
5474496867 | ||
|
|
54d9943a3b | ||
|
|
81de73df8e | ||
|
|
51312ff7ff | ||
|
|
b2d8c6bca2 | ||
|
|
4c9a0b45d4 | ||
|
|
b3f3449e39 | ||
|
|
a76a4730e3 | ||
|
|
dbeb3353f8 | ||
|
|
a2d1c8313d | ||
|
|
193233899f | ||
|
|
c42b8f81e3 | ||
|
|
13bf36ce49 | ||
|
|
31eee347d4 | ||
|
|
c37f1c24ee | ||
|
|
fba78f3e2a | ||
|
|
ec258c6515 | ||
|
|
087f84bfff | ||
|
|
733a51b1ea | ||
|
|
4141690857 | ||
|
|
0ad86b3a53 | ||
|
|
a39907de76 | ||
|
|
fd2fc34386 | ||
|
|
93d3a0eb38 | ||
|
|
ef95510da4 | ||
|
|
8fab2f0718 | ||
|
|
6da8ca70dc | ||
|
|
2343e517c9 | ||
|
|
60f954a497 | ||
|
|
af30142097 | ||
|
|
c5e287b7b1 | ||
|
|
c206ae13db | ||
|
|
882dbc3f25 | ||
|
|
022be3aee5 | ||
|
|
5f73c338d8 | ||
|
|
f58936025f | ||
|
|
1042a22cf7 | ||
|
|
5148e69006 | ||
|
|
08320890b1 | ||
|
|
5ed09acd94 | ||
|
|
4728729302 | ||
|
|
d016ea4116 | ||
|
|
fa3a2aafec | ||
|
|
d85f9cb4e2 | ||
|
|
9edec8e4a6 | ||
|
|
f12c54387c | ||
|
|
d51a2cde2f | ||
|
|
39d2c3ad81 | ||
|
|
6ee11a94b4 | ||
|
|
b2850ec1d5 | ||
|
|
8d56ab90ba | ||
|
|
3e4f5f40dc | ||
|
|
097c49149c | ||
|
|
a37d43714d | ||
|
|
ff20c530cd | ||
|
|
e0ae98eb95 | ||
|
|
477c0872b0 | ||
|
|
27bdc996e7 | ||
|
|
e52f7c56a9 | ||
|
|
74809b4132 | ||
|
|
bee9ba0c19 | ||
|
|
f4995410f8 | ||
|
|
22a94100c8 | ||
|
|
183feeb952 | ||
|
|
049b0dda23 | ||
|
|
4bc64350b2 | ||
|
|
7f474f574c | ||
|
|
af7fd0ef61 | ||
|
|
7f3ccad4cc | ||
|
|
264c816f08 | ||
|
|
a6f8ff44a6 | ||
|
|
c03c5d2fe9 | ||
|
|
fcfe8582de | ||
|
|
63af03f11c | ||
|
|
fe841e560d | ||
|
|
88583362ad | ||
|
|
babe94c50d | ||
|
|
e7c98bd31b | ||
|
|
771b4c5b6e | ||
|
|
ef716b8b94 | ||
|
|
6bcd72f7f2 | ||
|
|
c434dc8dcc | ||
|
|
92db7b3618 | ||
|
|
62d6bf1dec | ||
|
|
fc9dbcf6cb | ||
|
|
7193b54419 | ||
|
|
686e48e9e2 | ||
|
|
d8ea4bbc00 | ||
|
|
9467370a8f | ||
|
|
fff595b163 | ||
|
|
a46982f8ec | ||
|
|
4ddd2ae31d | ||
|
|
1c1afc9eaa | ||
|
|
e6f0da99e6 | ||
|
|
c59fdb4299 | ||
|
|
0878109647 | ||
|
|
74ff101de4 | ||
|
|
ebc184bf94 | ||
|
|
a09f38589f | ||
|
|
4664bb01d8 | ||
|
|
c9e92bce60 | ||
|
|
c04c047fbd | ||
|
|
9e857fe245 | ||
|
|
9ad584b34d | ||
|
|
43ab806f8d | ||
|
|
18a64103cd | ||
|
|
bb6a7e357f | ||
|
|
0ec23fd40b | ||
|
|
d43b230b93 | ||
|
|
43b51d4a30 | ||
|
|
bea2678e85 | ||
|
|
146a26e714 | ||
|
|
016a19fcee | ||
|
|
9949101fba | ||
|
|
30f4f920c1 | ||
|
|
22200d857c | ||
|
|
713909fac9 | ||
|
|
f4d751c839 | ||
|
|
411b71bcbb | ||
|
|
0ca117b8e9 | ||
|
|
ce450e3806 | ||
|
|
f2c1c26385 | ||
|
|
dbd906b330 | ||
|
|
b7e70ea7e3 | ||
|
|
0c5d1c7090 | ||
|
|
1d87d1cce1 | ||
|
|
39019eed10 | ||
|
|
ddc9766ec9 | ||
|
|
2f4c2c20f9 | ||
|
|
741e10060e | ||
|
|
2a60f65229 | ||
|
|
a5687e4660 | ||
|
|
f337085ed0 | ||
|
|
8418fec113 | ||
|
|
534a7b6e59 | ||
|
|
87a7879e71 | ||
|
|
d35f8c1905 | ||
|
|
53799297b3 | ||
|
|
f5f36545f3 | ||
|
|
40bf5fb5d2 | ||
|
|
49dc5363bb | ||
|
|
1cc4cc30a6 | ||
|
|
8799405673 | ||
|
|
9a3bea7062 | ||
|
|
c392f6659e | ||
|
|
6a75aec6d8 | ||
|
|
a867a1646a | ||
|
|
5abe06a2b7 | ||
|
|
aba69e0628 | ||
|
|
2355db57da | ||
|
|
7796bf3510 | ||
|
|
09d1c8bafc | ||
|
|
dcc9e30e8d | ||
|
|
01a96b62dc | ||
|
|
a206a6d16d | ||
|
|
19c29387fa | ||
|
|
3565706379 | ||
|
|
6ec19d0b63 | ||
|
|
71247538ee | ||
|
|
b491a10aa9 | ||
|
|
20dac1d885 | ||
|
|
1de3d143aa | ||
|
|
0b875d4688 | ||
|
|
acba6cd210 | ||
|
|
231e049771 | ||
|
|
43e88b4d73 | ||
|
|
c52da46e54 | ||
|
|
29d6832882 | ||
|
|
15f4c764bc | ||
|
|
35a9527407 | ||
|
|
cd8ba1653d | ||
|
|
8d9047c338 | ||
|
|
c8d47926b8 | ||
|
|
83370f0666 | ||
|
|
d77c1e616f | ||
|
|
ae56bb30c9 | ||
|
|
27ef919d07 | ||
|
|
dcb6cedfb7 | ||
|
|
95724e36f3 | ||
|
|
ce58f00caa | ||
|
|
225523fee0 | ||
|
|
c7335fa242 | ||
|
|
57ccbb7f49 | ||
|
|
4433a058a4 | ||
|
|
bf76be21c7 | ||
|
|
bbe7ff1dce | ||
|
|
5f16f0ede8 | ||
|
|
7dd2b3d7be | ||
|
|
320a9da585 | ||
|
|
fa8b272e76 | ||
|
|
6c3d31dd4c | ||
|
|
1cc4ca2e71 | ||
|
|
eea19e7850 | ||
|
|
2047363e86 | ||
|
|
0d88c9050a | ||
|
|
236fcd66d4 | ||
|
|
1a5517f986 | ||
|
|
01cc0d0e1d | ||
|
|
e0aedfb93d | ||
|
|
0797cf6db7 | ||
|
|
ec77970021 | ||
|
|
30f34173c5 | ||
|
|
0e52781a1f | ||
|
|
10bda7c96d | ||
|
|
afcdce6b71 | ||
|
|
f6a718f10a | ||
|
|
9335ab6b22 | ||
|
|
631dcd93e6 | ||
|
|
756cc0dca0 | ||
|
|
9fcef2c80f | ||
|
|
19b05ad62f | ||
|
|
578b5e2b59 | ||
|
|
6776f5c341 | ||
|
|
c3c3b90fb9 | ||
|
|
34d5e85316 | ||
|
|
d0a9e45826 | ||
|
|
23b4b4283b | ||
|
|
673253a539 | ||
|
|
4f5b71d81a | ||
|
|
70ae0b2317 | ||
|
|
d91a9df177 | ||
|
|
27fd5f395f | ||
|
|
2f3b5e9b88 | ||
|
|
9edc2000e7 | ||
|
|
b6257e66b3 | ||
|
|
890af8da52 | ||
|
|
974906d37f | ||
|
|
f2f2158154 | ||
|
|
ea99a05ffe | ||
|
|
0cffa5740d | ||
|
|
a341889d2c | ||
|
|
9007f8b19c | ||
|
|
b8832d2d71 | ||
|
|
86dfcecef6 | ||
|
|
8efbdce810 | ||
|
|
1667ada111 | ||
|
|
b6ed681402 | ||
|
|
2dcf96eaad | ||
|
|
b8b40e75ef | ||
|
|
81e4f4b351 | ||
|
|
afc8179cf8 | ||
|
|
919a3791ea | ||
|
|
e490a6e3bd | ||
|
|
eab548d7f0 | ||
|
|
37256b6b8f | ||
|
|
04d76b0052 | ||
|
|
ecd3d2187f | ||
|
|
b3d40c6f85 | ||
|
|
d415bdb3d2 | ||
|
|
060527ac67 | ||
|
|
f71273a1f9 | ||
|
|
656161ff61 | ||
|
|
2044e47d03 | ||
|
|
6baa2228e7 | ||
|
|
d9f82affb0 | ||
|
|
d2e3389414 | ||
|
|
7cf77e1bb4 | ||
|
|
da2ad8f645 | ||
|
|
f4a41071ea | ||
|
|
a44add29b4 | ||
|
|
de8ffae427 | ||
|
|
3d370f9d11 | ||
|
|
257ccbb939 | ||
|
|
bead313e27 | ||
|
|
0ad4041fd9 | ||
|
|
8d1108ba1b | ||
|
|
fb5e5d5cb6 | ||
|
|
899c9f48e9 | ||
|
|
2d36d48356 | ||
|
|
5606160e70 | ||
|
|
cd1094c3ad | ||
|
|
069d362778 | ||
|
|
77c37ccaf6 | ||
|
|
c52c2eb471 | ||
|
|
92d9808cc0 | ||
|
|
8830f87a76 | ||
|
|
600f429abb | ||
|
|
fdcc2d32f6 | ||
|
|
9083bbf669 | ||
|
|
58b5c3d75d | ||
|
|
44c3a56a21 | ||
|
|
6805b991e1 | ||
|
|
e25a0d3cf1 | ||
|
|
ca72a3bd70 | ||
|
|
3f6377b31f | ||
|
|
0008d50dfa | ||
|
|
a723dde180 | ||
|
|
9ef29b6a3e | ||
|
|
2a5fd36b6c | ||
|
|
2d9bb402fc | ||
|
|
44ea920f92 | ||
|
|
b4ce19c231 | ||
|
|
93b54ce13a | ||
|
|
3275b528f9 | ||
|
|
5263101bc0 | ||
|
|
32d7464e52 | ||
|
|
9e3fa4b566 | ||
|
|
f64ffcbefe | ||
|
|
3bc57c19f0 | ||
|
|
5328d760fa | ||
|
|
8095587e63 | ||
|
|
5c4bcdce57 | ||
|
|
125eff9a95 | ||
|
|
641dc9b3ef | ||
|
|
b080e1e782 | ||
|
|
f103480518 | ||
|
|
9af8af73bf | ||
|
|
7279c88638 | ||
|
|
a714896264 | ||
|
|
29eadf915a | ||
|
|
3d310f8cc6 | ||
|
|
5a108c60a0 | ||
|
|
59535e4c43 | ||
|
|
df1c111a72 | ||
|
|
7bcd954e91 | ||
|
|
1754f242c7 | ||
|
|
794c4bc5f1 | ||
|
|
3c3bbdbbdb | ||
|
|
6a428c8ee7 | ||
|
|
d1a3ff8707 | ||
|
|
9f0149d69c | ||
|
|
bb3937e3c1 | ||
|
|
a52fb53ac7 | ||
|
|
d5c0769499 | ||
|
|
8f7f9e0b9c | ||
|
|
6b261cd6e9 | ||
|
|
b2a803929d | ||
|
|
137edf9b1a | ||
|
|
3a669ad7d5 | ||
|
|
7ec82ee7e2 | ||
|
|
515ed226be | ||
|
|
cca91dd175 | ||
|
|
ca46621782 | ||
|
|
1c2eafeb80 | ||
|
|
7109ad247f | ||
|
|
538e49117f | ||
|
|
fcf7b28b8f | ||
|
|
36266b8574 | ||
|
|
8d15520939 | ||
|
|
350a9cd997 | ||
|
|
a67ead6a1b | ||
|
|
512a5ef079 | ||
|
|
d748f7e459 | ||
|
|
bd2238dbb8 | ||
|
|
bed8db7466 | ||
|
|
e4f75409c9 | ||
|
|
98ad9b7e7c | ||
|
|
9579a7317e | ||
|
|
3e9ba71baa | ||
|
|
23e261244c | ||
|
|
65f94fdf84 | ||
|
|
e588860c21 | ||
|
|
dccc338622 | ||
|
|
ba788eb678 | ||
|
|
01beff741d | ||
|
|
5b156595fb | ||
|
|
19deb7c65e | ||
|
|
58b66b788f | ||
|
|
0447d857fd | ||
|
|
42b00ae080 | ||
|
|
eb8d02a691 | ||
|
|
1ba6a27d0e | ||
|
|
ee9c87877f | ||
|
|
38670df49a | ||
|
|
54e745c6c9 | ||
|
|
d730da757e | ||
|
|
fbca45d336 | ||
|
|
ff66d471e2 | ||
|
|
c65a156939 | ||
|
|
def40448ac | ||
|
|
90fc721315 | ||
|
|
ed97a9b6f3 | ||
|
|
8ea057a122 | ||
|
|
5167b623d5 | ||
|
|
1e1bf97ffc | ||
|
|
ef1f34d933 | ||
|
|
57a14627e1 | ||
|
|
1cba5e5428 | ||
|
|
974c5615af | ||
|
|
861e44b9c8 | ||
|
|
c1a8e692d0 | ||
|
|
2a9304eb79 | ||
|
|
e8932bbea0 | ||
|
|
63ad95a766 | ||
|
|
b6d861fea6 | ||
|
|
96424536a7 | ||
|
|
b86f15f32a | ||
|
|
be578684b9 | ||
|
|
bccc5fa7b1 | ||
|
|
d98cd4235c | ||
|
|
08a0862369 | ||
|
|
f2dd556323 | ||
|
|
6dc1194247 | ||
|
|
a7c956bf10 | ||
|
|
0907feb508 | ||
|
|
30e2709bd0 | ||
|
|
f585f5dfc9 | ||
|
|
61ae15cf56 | ||
|
|
139e19514b | ||
|
|
19b82f00d4 | ||
|
|
623ec6c510 | ||
|
|
a1b6310777 | ||
|
|
d395a9e318 | ||
|
|
729d73bb17 | ||
|
|
2694e003c4 | ||
|
|
5854e44391 | ||
|
|
361f0b5048 | ||
|
|
8de8446cb5 | ||
|
|
6ccf3549ad | ||
|
|
c469109d22 | ||
|
|
8164c05cb8 | ||
|
|
b8c08d05bf | ||
|
|
0419bb2232 | ||
|
|
72371e5d76 | ||
|
|
f0b168c18f | ||
|
|
3d5055d920 | ||
|
|
0fc4ca0d12 | ||
|
|
4db73942e6 | ||
|
|
0c829cd605 | ||
|
|
e876a3c988 | ||
|
|
91cbfbd37f | ||
|
|
4a285bae9b | ||
|
|
804dd13abd | ||
|
|
0d6c72a378 | ||
|
|
17b84df631 | ||
|
|
784b724fe0 | ||
|
|
65dc7ddb44 | ||
|
|
120b880427 | ||
|
|
a4335c3eb8 | ||
|
|
373ded9761 | ||
|
|
3e9ae7aa82 | ||
|
|
21a2a47172 | ||
|
|
92c1e01d0e | ||
|
|
3dbf5ee389 | ||
|
|
55c5e9a381 | ||
|
|
1f26765546 | ||
|
|
c4a11cd52a | ||
|
|
b924cf9e9f | ||
|
|
1714422b10 | ||
|
|
7cea4f6695 | ||
|
|
85622ac522 | ||
|
|
48551d362a | ||
|
|
cee8fee504 | ||
|
|
9a4308e092 | ||
|
|
93b3f448d3 | ||
|
|
8beff63dc0 | ||
|
|
6b5f26ab23 | ||
|
|
4ed1061487 | ||
|
|
3a8b450575 | ||
|
|
e6df30602e | ||
|
|
72299b4844 | ||
|
|
4e4559fdc2 | ||
|
|
dcdef4daa4 | ||
|
|
9299591272 | ||
|
|
571a4389e9 | ||
|
|
78b11bca84 | ||
|
|
ff73e1a36a | ||
|
|
134c9b11c0 | ||
|
|
dfa7c76331 | ||
|
|
f21cede378 | ||
|
|
80dfc63768 | ||
|
|
29ffe96388 | ||
|
|
4d5ae5dd68 | ||
|
|
6a3b5cd4c0 | ||
|
|
afd3a36ee4 | ||
|
|
e02d0ed629 | ||
|
|
a4d9aaf916 | ||
|
|
f83b67cdf5 | ||
|
|
46f6b97c26 | ||
|
|
5d7b549323 | ||
|
|
7f88e769f2 | ||
|
|
36bc97e3cd | ||
|
|
3b55bd08a6 | ||
|
|
d35ccd9a5e | ||
|
|
0fea54ebb5 | ||
|
|
7c9dbdfc91 | ||
|
|
4ce8b2431b | ||
|
|
e506e385f1 | ||
|
|
c52500ce88 | ||
|
|
6ab411b2de | ||
|
|
e7f7a5f6c8 | ||
|
|
d1552cd6e8 | ||
|
|
545fb8b849 | ||
|
|
f71a6ec904 | ||
|
|
e4fcaad243 | ||
|
|
9cf788c590 | ||
|
|
3d3d526e30 | ||
|
|
020d090aee | ||
|
|
1d2fef5b9b | ||
|
|
2cc3588be5 | ||
|
|
12bf4b498e | ||
|
|
6b6a673642 | ||
|
|
ad7a6c4854 | ||
|
|
47773371e3 | ||
|
|
d0aeb25f14 | ||
|
|
89158c9dcf | ||
|
|
dfc4cf36a2 | ||
|
|
0f52768064 | ||
|
|
2641a93943 | ||
|
|
1dd4bdf0b7 | ||
|
|
73d74f69c3 | ||
|
|
28c70fa8ab | ||
|
|
17fcc38157 | ||
|
|
add41ea92b | ||
|
|
e497eb0950 | ||
|
|
cb4f95416c | ||
|
|
b253fe9532 | ||
|
|
e2436c4722 | ||
|
|
76a4db5d54 | ||
|
|
790a068bdb | ||
|
|
95d222348d | ||
|
|
7bdf9f4ba1 | ||
|
|
c9545e16f7 | ||
|
|
f6dea6752c | ||
|
|
0f5caffbab | ||
|
|
16061ec727 | ||
|
|
6030a65d1b | ||
|
|
29c7cb9061 | ||
|
|
a9c7bbd44c | ||
|
|
c973dc9eee | ||
|
|
16ec76e864 | ||
|
|
20fe2b75b5 | ||
|
|
d7fbfc05e8 | ||
|
|
8f1292494f | ||
|
|
0ef36096fb | ||
|
|
5181e95776 | ||
|
|
cc02b16742 | ||
|
|
50f8846c3d | ||
|
|
22ce20ff6b | ||
|
|
0dea5eac46 | ||
|
|
f9595fc6c5 | ||
|
|
4c6dd10e48 | ||
|
|
25363a91f9 | ||
|
|
756cea5fb2 | ||
|
|
7ffd6dc7a7 | ||
|
|
4a06d7590e | ||
|
|
18b07190bf | ||
|
|
1cfe58bccd | ||
|
|
705a598ce6 | ||
|
|
23091ec738 | ||
|
|
2c9172f707 | ||
|
|
741fa16ec7 | ||
|
|
5c912d3501 | ||
|
|
e2e96f7183 | ||
|
|
4193856fbb | ||
|
|
e56759a6c0 | ||
|
|
ce717fa20e | ||
|
|
204d226267 | ||
|
|
ca67e8a764 | ||
|
|
60667e65e7 | ||
|
|
3b7101650e | ||
|
|
82300a90f8 | ||
|
|
49c5610971 | ||
|
|
75ef3efd15 | ||
|
|
e7ec454f81 | ||
|
|
a80a65c525 | ||
|
|
973619d7c0 | ||
|
|
50ed089980 | ||
|
|
3e208c10aa | ||
|
|
fa8af64098 | ||
|
|
c61b457c02 | ||
|
|
5c98b8c6ae | ||
|
|
3e6b6507f4 | ||
|
|
d285721063 | ||
|
|
95a1fc32eb | ||
|
|
13d2f872ff | ||
|
|
7a05078030 | ||
|
|
5c3159c83d | ||
|
|
f63655edf3 | ||
|
|
23aa490c72 | ||
|
|
1691ae7e29 | ||
|
|
804fb2dc96 | ||
|
|
975dcb68a9 | ||
|
|
e24ce91e81 | ||
|
|
2d188068c4 | ||
|
|
214cd2ac22 | ||
|
|
1142ed5583 | ||
|
|
85ccd64e01 | ||
|
|
73c3534e7a | ||
|
|
c6a7d0b772 | ||
|
|
4f0d827aca | ||
|
|
7342261dbe | ||
|
|
8f2c5af7d1 | ||
|
|
018b104cbf | ||
|
|
b3bb68aed7 | ||
|
|
bdfc9b8efc | ||
|
|
3b1987cb3a | ||
|
|
18c0e8946a | ||
|
|
25f965aa2e | ||
|
|
50e5d203b3 | ||
|
|
41358eea4d | ||
|
|
4b69cb4551 | ||
|
|
b0264d528d | ||
|
|
82ad26189f | ||
|
|
cd299c995b | ||
|
|
2ab8f320c3 | ||
|
|
36f2febde1 | ||
|
|
dbc49dd742 | ||
|
|
55e9f2e21b | ||
|
|
dd3e6b29bf | ||
|
|
81f1baff33 | ||
|
|
e3f24f58df | ||
|
|
1939f25a71 | ||
|
|
76df229967 | ||
|
|
3ce6deaa1b | ||
|
|
428b85f1f8 | ||
|
|
89a1141f8d | ||
|
|
12e5dfa3e5 | ||
|
|
4ff4f69343 | ||
|
|
06545f2543 | ||
|
|
9e836162b7 | ||
|
|
42f4e7fef8 | ||
|
|
0708bceea7 | ||
|
|
f72b0f3f6d | ||
|
|
a27fd7ae1f | ||
|
|
768a3ab941 | ||
|
|
5790dd44a4 | ||
|
|
3b44ebb6d9 | ||
|
|
d2b5cf08c6 | ||
|
|
f8ec178fae | ||
|
|
062c8a06ab | ||
|
|
6a48e00039 | ||
|
|
6d0e5d6ad1 | ||
|
|
001ae5146a | ||
|
|
23175bf925 | ||
|
|
cf10a09008 | ||
|
|
8a4a1660a4 | ||
|
|
4e0e4df630 | ||
|
|
0519f0fed5 | ||
|
|
36f64b2e23 | ||
|
|
94a2c3ab0e | ||
|
|
28fe13a800 | ||
|
|
2d7424c697 | ||
|
|
ddc0792733 | ||
|
|
cd063c6a36 | ||
|
|
a35aa47f27 |
No files matched your search
+8
-1
@@ -1,5 +1,12 @@
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.m2
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- MAVEN_SKIP_RC=true
|
- MAVEN_SKIP_RC=true
|
||||||
@@ -12,7 +19,7 @@ before_script:
|
|||||||
- export MAVEN_SKIP_RC=true
|
- export MAVEN_SKIP_RC=true
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- mvn install -Pdistribution -DskipTests=true -B -V -q
|
- travis_wait 30 mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- mvn test -B
|
- mvn test -B
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ Contributing
|
|||||||
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
|
||||||
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
|
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
|
||||||
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
|
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
|
||||||
|
* [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config
|
||||||
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -65,6 +65,11 @@
|
|||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-core</artifactId>
|
<artifactId>jackson-core</artifactId>
|
||||||
|
|||||||
+11
-5
@@ -169,11 +169,7 @@ public class AdapterDeploymentContext {
|
|||||||
public void setAuthServerBaseUrl(String authServerBaseUrl) {
|
public void setAuthServerBaseUrl(String authServerBaseUrl) {
|
||||||
this.authServerBaseUrl = authServerBaseUrl;
|
this.authServerBaseUrl = authServerBaseUrl;
|
||||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
||||||
resolveBrowserUrls(serverBuilder);
|
resolveUrls(serverBuilder);
|
||||||
|
|
||||||
if (delegate.getRelativeUrls() == RelativeUrlsUsed.ALL_REQUESTS) {
|
|
||||||
resolveNonBrowserUrls(serverBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -460,6 +456,16 @@ public class AdapterDeploymentContext {
|
|||||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||||
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTokenMinimumTimeToLive() {
|
||||||
|
return delegate.getTokenMinimumTimeToLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||||
|
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||||
|
|||||||
@@ -38,37 +38,6 @@ public class AdapterUtils {
|
|||||||
return UUID.randomUUID().toString();
|
return UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Best effort to find origin for REST request calls from web UI application to REST application. In case of relative or absolute
|
|
||||||
* "auth-server-url" is returned the URL from request. In case of "auth-server-url-for-backend-request" used in configuration, it returns
|
|
||||||
* the origin of auth server.
|
|
||||||
*
|
|
||||||
* This may be the optimization in cluster, so if you have keycloak and applications on same host, the REST request doesn't need to
|
|
||||||
* go through loadbalancer, but can be sent directly to same host.
|
|
||||||
*
|
|
||||||
* @param browserRequestURL
|
|
||||||
* @param session
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getOriginForRestCalls(String browserRequestURL, KeycloakSecurityContext session) {
|
|
||||||
if (session instanceof RefreshableKeycloakSecurityContext) {
|
|
||||||
KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
|
|
||||||
switch (deployment.getRelativeUrls()) {
|
|
||||||
case ALL_REQUESTS:
|
|
||||||
case NEVER:
|
|
||||||
// Resolve baseURI from the request
|
|
||||||
return UriUtils.getOrigin(browserRequestURL);
|
|
||||||
case BROWSER_ONLY:
|
|
||||||
// Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
|
|
||||||
return UriUtils.getOrigin(deployment.getTokenUrl());
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return UriUtils.getOrigin(browserRequestURL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
|
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
|
||||||
Set<String> roles = null;
|
Set<String> roles = null;
|
||||||
AccessToken accessToken = session.getToken();
|
AccessToken accessToken = session.getToken();
|
||||||
|
|||||||
+26
-1
@@ -18,10 +18,12 @@
|
|||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.AuthorizationContext;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.common.util.UriUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -55,6 +57,9 @@ public class AuthenticatedActionsHandler {
|
|||||||
queryBearerToken();
|
queryBearerToken();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!isAuthorized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,4 +129,24 @@ public class AuthenticatedActionsHandler {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAuthorized() {
|
||||||
|
PolicyEnforcer policyEnforcer = this.deployment.getPolicyEnforcer();
|
||||||
|
|
||||||
|
if (policyEnforcer == null) {
|
||||||
|
log.debugv("Policy enforcement is disabled.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
OIDCHttpFacade facade = (OIDCHttpFacade) this.facade;
|
||||||
|
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
||||||
|
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
||||||
|
|
||||||
|
session.setAuthorizationContext(authorizationContext);
|
||||||
|
|
||||||
|
return authorizationContext.isGranted();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
@@ -24,6 +24,7 @@ import org.apache.http.client.HttpClient;
|
|||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
@@ -104,6 +105,7 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
|
|||||||
int status = response.getStatusLine().getStatusCode();
|
int status = response.getStatusLine().getStatusCode();
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
if (status != 200) {
|
if (status != 200) {
|
||||||
|
EntityUtils.consumeQuietly(entity);
|
||||||
throw new java.io.IOException("Bad status: " + status);
|
throw new java.io.IOException("Bad status: " + status);
|
||||||
}
|
}
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
|
|||||||
+38
-10
@@ -17,9 +17,11 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
import org.apache.http.client.CookieStore;
|
import org.apache.http.client.CookieStore;
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
|
import org.apache.http.conn.params.ConnRoutePNames;
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
import org.apache.http.conn.scheme.Scheme;
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||||
@@ -34,9 +36,9 @@ import org.apache.http.impl.conn.SingleClientConnManager;
|
|||||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
import org.apache.http.params.BasicHttpParams;
|
import org.apache.http.params.BasicHttpParams;
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
|
||||||
import org.keycloak.common.util.EnvUtil;
|
import org.keycloak.common.util.EnvUtil;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.common.util.KeystoreUtil;
|
||||||
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
@@ -46,6 +48,7 @@ import javax.net.ssl.SSLSocket;
|
|||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
@@ -112,6 +115,7 @@ public class HttpClientBuilder {
|
|||||||
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
protected long establishConnectionTimeout = -1;
|
protected long establishConnectionTimeout = -1;
|
||||||
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
|
protected HttpHost proxyHost;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,8 +125,7 @@ public class HttpClientBuilder {
|
|||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
|
||||||
{
|
|
||||||
this.socketTimeout = timeout;
|
this.socketTimeout = timeout;
|
||||||
this.socketTimeoutUnits = unit;
|
this.socketTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
@@ -135,8 +138,7 @@ public class HttpClientBuilder {
|
|||||||
* @param unit
|
* @param unit
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
|
||||||
{
|
|
||||||
this.establishConnectionTimeout = timeout;
|
this.establishConnectionTimeout = timeout;
|
||||||
this.establishConnectionTimeoutUnits = unit;
|
this.establishConnectionTimeoutUnits = unit;
|
||||||
return this;
|
return this;
|
||||||
@@ -287,16 +289,20 @@ public class HttpClientBuilder {
|
|||||||
cm = new SingleClientConnManager(registry);
|
cm = new SingleClientConnManager(registry);
|
||||||
}
|
}
|
||||||
BasicHttpParams params = new BasicHttpParams();
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
if (socketTimeout > -1)
|
|
||||||
{
|
if (proxyHost != null) {
|
||||||
|
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketTimeout > -1) {
|
||||||
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||||
|
|
||||||
}
|
}
|
||||||
if (establishConnectionTimeout > -1)
|
if (establishConnectionTimeout > -1) {
|
||||||
{
|
HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||||
HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
|
||||||
}
|
}
|
||||||
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
DefaultHttpClient client = new DefaultHttpClient(cm, params);
|
||||||
|
|
||||||
if (disableCookieCache) {
|
if (disableCookieCache) {
|
||||||
client.setCookieStore(new CookieStore() {
|
client.setCookieStore(new CookieStore() {
|
||||||
@Override
|
@Override
|
||||||
@@ -364,6 +370,28 @@ public class HttpClientBuilder {
|
|||||||
} else {
|
} else {
|
||||||
trustStore(truststore);
|
trustStore(truststore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureProxyForAuthServerIfProvided(adapterConfig);
|
||||||
|
|
||||||
return build();
|
return build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a the proxy to use for auth-server requests if provided.
|
||||||
|
* <p>
|
||||||
|
* If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
|
||||||
|
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param adapterConfig
|
||||||
|
*/
|
||||||
|
private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
|
||||||
|
|
||||||
|
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
URI uri = URI.create(adapterConfig.getProxyUrl());
|
||||||
|
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+26
-38
@@ -20,6 +20,7 @@ package org.keycloak.adapters;
|
|||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||||
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
@@ -77,6 +78,8 @@ public class KeycloakDeployment {
|
|||||||
protected boolean turnOffChangeSessionIdOnLogin;
|
protected boolean turnOffChangeSessionIdOnLogin;
|
||||||
|
|
||||||
protected volatile int notBefore;
|
protected volatile int notBefore;
|
||||||
|
protected int tokenMinimumTimeToLive;
|
||||||
|
private PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
public KeycloakDeployment() {
|
public KeycloakDeployment() {
|
||||||
}
|
}
|
||||||
@@ -111,39 +114,17 @@ public class KeycloakDeployment {
|
|||||||
|
|
||||||
public void setAuthServerBaseUrl(AdapterConfig config) {
|
public void setAuthServerBaseUrl(AdapterConfig config) {
|
||||||
this.authServerBaseUrl = config.getAuthServerUrl();
|
this.authServerBaseUrl = config.getAuthServerUrl();
|
||||||
String authServerURLForBackendReqs = config.getAuthServerUrlForBackendRequests();
|
if (authServerBaseUrl == null) return;
|
||||||
if (authServerBaseUrl == null && authServerURLForBackendReqs == null) return;
|
|
||||||
|
|
||||||
URI authServerUri = null;
|
URI authServerUri = URI.create(authServerBaseUrl);
|
||||||
if (authServerBaseUrl != null) {
|
|
||||||
authServerUri = URI.create(authServerBaseUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authServerUri == null || authServerUri.getHost() == null) {
|
if (authServerUri.getHost() == null) {
|
||||||
if (authServerURLForBackendReqs != null) {
|
relativeUrls = RelativeUrlsUsed.ALWAYS;
|
||||||
relativeUrls = RelativeUrlsUsed.BROWSER_ONLY;
|
|
||||||
|
|
||||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
|
|
||||||
if (serverBuilder.getHost() == null || serverBuilder.getScheme() == null) {
|
|
||||||
throw new IllegalStateException("Relative URL not supported for auth-server-url-for-backend-requests option. URL used: "
|
|
||||||
+ authServerURLForBackendReqs + ", Client: " + config.getResource());
|
|
||||||
}
|
|
||||||
resolveNonBrowserUrls(serverBuilder);
|
|
||||||
} else {
|
|
||||||
relativeUrls = RelativeUrlsUsed.ALL_REQUESTS;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// We have absolute URI in config
|
// We have absolute URI in config
|
||||||
relativeUrls = RelativeUrlsUsed.NEVER;
|
relativeUrls = RelativeUrlsUsed.NEVER;
|
||||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
||||||
resolveBrowserUrls(serverBuilder);
|
resolveUrls(serverBuilder);
|
||||||
|
|
||||||
if (authServerURLForBackendReqs == null) {
|
|
||||||
resolveNonBrowserUrls(serverBuilder);
|
|
||||||
} else {
|
|
||||||
serverBuilder = KeycloakUriBuilder.fromUri(authServerURLForBackendReqs);
|
|
||||||
resolveNonBrowserUrls(serverBuilder);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,23 +133,14 @@ public class KeycloakDeployment {
|
|||||||
/**
|
/**
|
||||||
* @param authUrlBuilder absolute URI
|
* @param authUrlBuilder absolute URI
|
||||||
*/
|
*/
|
||||||
protected void resolveBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
|
protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("resolveBrowserUrls");
|
log.debug("resolveUrls");
|
||||||
}
|
}
|
||||||
|
|
||||||
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
|
||||||
authUrl = KeycloakUriBuilder.fromUri(login);
|
authUrl = KeycloakUriBuilder.fromUri(login);
|
||||||
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param authUrlBuilder absolute URI
|
|
||||||
*/
|
|
||||||
protected void resolveNonBrowserUrls(KeycloakUriBuilder authUrlBuilder) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("resolveNonBrowserUrls");
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
|
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
|
||||||
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
|
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
|
||||||
@@ -388,4 +360,20 @@ public class KeycloakDeployment {
|
|||||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||||
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTokenMinimumTimeToLive() {
|
||||||
|
return tokenMinimumTimeToLive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||||
|
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
|
this.policyEnforcer = policyEnforcer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PolicyEnforcer getPolicyEnforcer() {
|
||||||
|
return policyEnforcer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+10
-1
@@ -21,10 +21,12 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
|
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.enums.TokenStore;
|
import org.keycloak.enums.TokenStore;
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||||
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -94,6 +96,7 @@ public class KeycloakDeploymentBuilder {
|
|||||||
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
||||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||||
|
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||||
|
|
||||||
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
|
||||||
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
|
||||||
@@ -109,6 +112,12 @@ public class KeycloakDeploymentBuilder {
|
|||||||
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PolicyEnforcerConfig policyEnforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||||
|
|
||||||
|
if (policyEnforcerConfig != null) {
|
||||||
|
deployment.setPolicyEnforcer(new PolicyEnforcer(deployment, adapterConfig));
|
||||||
|
}
|
||||||
|
|
||||||
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
|
||||||
return deployment;
|
return deployment;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-2
@@ -34,6 +34,7 @@ import org.keycloak.representations.AccessTokenResponse;
|
|||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
@@ -159,6 +160,12 @@ public class OAuthRequestAuthenticator {
|
|||||||
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
|
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
|
||||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
|
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
|
||||||
|
|
||||||
|
String prompt = getQueryParamValue(OAuth2Constants.PROMPT);
|
||||||
|
url = UriUtils.stripQueryParam(url, OAuth2Constants.PROMPT);
|
||||||
|
|
||||||
|
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
|
||||||
|
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
|
||||||
|
|
||||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||||
@@ -171,9 +178,15 @@ public class OAuthRequestAuthenticator {
|
|||||||
if (idpHint != null && idpHint.length() > 0) {
|
if (idpHint != null && idpHint.length() > 0) {
|
||||||
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
||||||
}
|
}
|
||||||
if (scope != null && scope.length() > 0) {
|
if (prompt != null && prompt.length() > 0) {
|
||||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
||||||
}
|
}
|
||||||
|
if (maxAge != null && maxAge.length() > 0) {
|
||||||
|
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
|
scope = TokenUtil.attachOIDCScope(scope);
|
||||||
|
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||||
|
|
||||||
return redirectUriBuilder.build().toString();
|
return redirectUriBuilder.build().toString();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ public class OIDCAuthenticationError implements AuthenticationError {
|
|||||||
CODE_TO_TOKEN_FAILURE,
|
CODE_TO_TOKEN_FAILURE,
|
||||||
INVALID_TOKEN,
|
INVALID_TOKEN,
|
||||||
STALE_TOKEN,
|
STALE_TOKEN,
|
||||||
NO_AUTHORIZATION_HEADER
|
NO_AUTHORIZATION_HEADER,
|
||||||
|
NO_QUERY_PARAMETER_ACCESS_TOKEN
|
||||||
}
|
}
|
||||||
|
|
||||||
private Reason reason;
|
private Reason reason;
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.jboss.logging.Logger;
|
||||||
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||||
|
public static final String ACCESS_TOKEN = "access_token";
|
||||||
|
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
|
||||||
|
|
||||||
|
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
||||||
|
super(deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||||
|
tokenString = null;
|
||||||
|
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||||
|
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||||
|
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
||||||
|
return AuthOutcome.NOT_ATTEMPTED;
|
||||||
|
}
|
||||||
|
return (authenticateToken(exchange, tokenString));
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
|
||||||
|
try {
|
||||||
|
if (exchange != null && exchange.getRequest() != null) {
|
||||||
|
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-2
@@ -18,9 +18,11 @@
|
|||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.AuthorizationContext;
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
@@ -74,7 +76,11 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return token != null && this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore();
|
return token != null && this.token.isActive() && deployment!=null && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTokenTimeToLiveSufficient(AccessToken token) {
|
||||||
|
return token != null && (token.getExpiration() - this.deployment.getTokenMinimumTimeToLive()) > Time.currentTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeycloakDeployment getDeployment() {
|
public KeycloakDeployment getDeployment() {
|
||||||
@@ -95,7 +101,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("checking whether to refresh.");
|
log.trace("checking whether to refresh.");
|
||||||
}
|
}
|
||||||
if (isActive()) return true;
|
if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
|
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
|
||||||
@@ -130,6 +136,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
log.error("failed verification of token");
|
log.error("failed verification of token");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the TTL is greater-or-equal to the expire time on the refreshed token, have to abort or go into an infinite refresh loop
|
||||||
|
if (!isTokenTimeToLiveSufficient(token)) {
|
||||||
|
log.error("failed to refresh the token with a longer time-to-live than the minimum");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||||
deployment.setNotBefore(response.getNotBeforePolicy());
|
deployment.setNotBefore(response.getNotBeforePolicy());
|
||||||
}
|
}
|
||||||
@@ -145,4 +158,8 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
|
|||||||
tokenStore.refreshCallback(this);
|
tokenStore.refreshCallback(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAuthorizationContext(AuthorizationContext authorizationContext) {
|
||||||
|
this.authorizationContext = authorizationContext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+26
-2
@@ -61,7 +61,7 @@ public abstract class RequestAuthenticator {
|
|||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try bearer");
|
log.trace("try bearer");
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthOutcome outcome = bearer.authenticate(facade);
|
AuthOutcome outcome = bearer.authenticate(facade);
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
challenge = bearer.getChallenge();
|
challenge = bearer.getChallenge();
|
||||||
@@ -74,18 +74,36 @@ public abstract class RequestAuthenticator {
|
|||||||
return AuthOutcome.AUTHENTICATED;
|
return AuthOutcome.AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.trace("try query paramter auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome = queryParamAuth.authenticate(facade);
|
||||||
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
|
challenge = queryParamAuth.getChallenge();
|
||||||
|
log.debug("QueryParamAuth auth FAILED");
|
||||||
|
return AuthOutcome.FAILED;
|
||||||
|
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||||
|
if (verifySSL()) return AuthOutcome.FAILED;
|
||||||
|
log.debug("QueryParamAuth AUTHENTICATED");
|
||||||
|
completeAuthentication(queryParamAuth, "KEYCLOAK");
|
||||||
|
return AuthOutcome.AUTHENTICATED;
|
||||||
|
}
|
||||||
|
|
||||||
if (deployment.isEnableBasicAuth()) {
|
if (deployment.isEnableBasicAuth()) {
|
||||||
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("try basic auth");
|
log.trace("try basic auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome = basicAuth.authenticate(facade);
|
outcome = basicAuth.authenticate(facade);
|
||||||
if (outcome == AuthOutcome.FAILED) {
|
if (outcome == AuthOutcome.FAILED) {
|
||||||
challenge = basicAuth.getChallenge();
|
challenge = basicAuth.getChallenge();
|
||||||
log.debug("BasicAuth FAILED");
|
log.debug("BasicAuth FAILED");
|
||||||
return AuthOutcome.FAILED;
|
return AuthOutcome.FAILED;
|
||||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||||
|
if (verifySSL()) return AuthOutcome.FAILED;
|
||||||
log.debug("BasicAuth AUTHENTICATED");
|
log.debug("BasicAuth AUTHENTICATED");
|
||||||
completeAuthentication(basicAuth, "BASIC");
|
completeAuthentication(basicAuth, "BASIC");
|
||||||
return AuthOutcome.AUTHENTICATED;
|
return AuthOutcome.AUTHENTICATED;
|
||||||
@@ -150,6 +168,10 @@ public abstract class RequestAuthenticator {
|
|||||||
return new BasicAuthRequestAuthenticator(deployment);
|
return new BasicAuthRequestAuthenticator(deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
|
||||||
|
return new QueryParamterTokenRequestAuthenticator(deployment);
|
||||||
|
}
|
||||||
|
|
||||||
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
|
||||||
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
|
||||||
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
|
||||||
@@ -158,10 +180,12 @@ public abstract class RequestAuthenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
||||||
|
|
||||||
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
|
||||||
|
*
|
||||||
* @param create
|
* @param create
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
@@ -54,6 +54,10 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
|
|||||||
this.tokenTimeout = tokenTimeout;
|
this.tokenTimeout = tokenTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int getTokenTimeout() {
|
||||||
|
return tokenTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(KeycloakDeployment deployment, Object config) {
|
public void init(KeycloakDeployment deployment, Object config) {
|
||||||
if (config == null || !(config instanceof Map)) {
|
if (config == null || !(config instanceof Map)) {
|
||||||
|
|||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.AuthorizationContext;
|
||||||
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||||
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
|
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||||
|
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractPolicyEnforcer {
|
||||||
|
|
||||||
|
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
||||||
|
private final PolicyEnforcerConfig enforcerConfig;
|
||||||
|
private final PolicyEnforcer policyEnforcer;
|
||||||
|
|
||||||
|
private List<PathConfig> paths;
|
||||||
|
private AuthzClient authzClient;
|
||||||
|
private PathMatcher pathMatcher;
|
||||||
|
|
||||||
|
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
|
this.policyEnforcer = policyEnforcer;
|
||||||
|
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||||
|
this.authzClient = policyEnforcer.getClient();
|
||||||
|
this.pathMatcher = new PathMatcher();
|
||||||
|
this.paths = new ArrayList<>(policyEnforcer.getPaths());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||||
|
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
|
||||||
|
|
||||||
|
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||||
|
return createEmptyAuthorizationContext(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken accessToken = httpFacade.getSecurityContext().getToken();
|
||||||
|
Request request = httpFacade.getRequest();
|
||||||
|
Response response = httpFacade.getResponse();
|
||||||
|
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
||||||
|
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
||||||
|
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||||
|
|
||||||
|
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||||
|
|
||||||
|
if (pathConfig == null) {
|
||||||
|
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||||
|
return createAuthorizationContext(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||||
|
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||||
|
|
||||||
|
return createEmptyAuthorizationContext(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||||
|
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||||
|
|
||||||
|
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||||
|
try {
|
||||||
|
return createAuthorizationContext(accessToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||||
|
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||||
|
response.sendError(403, "Authorization failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return createEmptyAuthorizationContext(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade);
|
||||||
|
|
||||||
|
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||||
|
Request request = httpFacade.getRequest();
|
||||||
|
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
||||||
|
String accessDeniedPath = enforcerConfig.getAccessDeniedPath();
|
||||||
|
|
||||||
|
if (accessDeniedPath != null) {
|
||||||
|
if (request.getURI().contains(accessDeniedPath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||||
|
|
||||||
|
if (authorization == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Permission> permissions = authorization.getPermissions();
|
||||||
|
|
||||||
|
for (Permission permission : permissions) {
|
||||||
|
if (permission.getResourceSetId() != null) {
|
||||||
|
if (isResourcePermission(actualPathConfig, permission)) {
|
||||||
|
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||||
|
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||||
|
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||||
|
this.paths.remove(actualPathConfig);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
||||||
|
Set<String> allowedScopes = permission.getScopes();
|
||||||
|
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.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 AuthorizationContext() {
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(String resourceName, String scopeName) {
|
||||||
|
return granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasResourcePermission(String resourceName) {
|
||||||
|
return granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasScopePermission(String scopeName) {
|
||||||
|
return granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Permission> getPermissions() {
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGranted() {
|
||||||
|
return granted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
||||||
|
if (originalConfig.hasPattern()) {
|
||||||
|
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
|
||||||
|
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
|
||||||
|
ProtectedResource resource = this.authzClient.protection().resource();
|
||||||
|
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 = new PathConfig();
|
||||||
|
|
||||||
|
config.setId(targetResource.getId());
|
||||||
|
config.setName(targetResource.getName());
|
||||||
|
config.setType(targetResource.getType());
|
||||||
|
config.setPath(targetResource.getUri());
|
||||||
|
config.setScopes(originalConfig.getScopes());
|
||||||
|
config.setMethods(originalConfig.getMethods());
|
||||||
|
config.setParentConfig(originalConfig);
|
||||||
|
|
||||||
|
this.paths.add(config);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
|
||||||
|
Set<String> requiredScopes = new HashSet<>();
|
||||||
|
|
||||||
|
requiredScopes.addAll(pathConfig.getScopes());
|
||||||
|
|
||||||
|
String method = request.getMethod();
|
||||||
|
|
||||||
|
for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||||
|
if (methodConfig.getMethod().equals(method)) {
|
||||||
|
requiredScopes.addAll(methodConfig.getScopes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requiredScopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||||
|
return new AuthorizationContext(accessToken, this.paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||||
|
// first we try a match using resource id
|
||||||
|
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||||
|
|
||||||
|
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||||
|
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||||
|
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||||
|
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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 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.PathConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||||
|
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
|
||||||
|
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
|
||||||
|
} else {
|
||||||
|
challengeEntitlementAuthentication(facade);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||||
|
HttpFacade.Response response = facade.getResponse();
|
||||||
|
AuthzClient authzClient = getAuthzClient();
|
||||||
|
String clientId = authzClient.getConfiguration().getClientId();
|
||||||
|
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||||
|
HttpFacade.Response response = facade.getResponse();
|
||||||
|
AuthzClient authzClient = getAuthzClient();
|
||||||
|
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
|
||||||
|
String clientId = authzClient.getConfiguration().getClientId();
|
||||||
|
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
|
||||||
|
ProtectionResource protection = authzClient.protection();
|
||||||
|
PermissionResource permission = protection.permission();
|
||||||
|
PermissionRequest permissionRequest = new PermissionRequest();
|
||||||
|
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||||
|
permissionRequest.setScopes(requiredScopes);
|
||||||
|
return permission.forResource(permissionRequest).getTicket();
|
||||||
|
}
|
||||||
|
}
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.RSATokenVerifier;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
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.representations.AccessToken;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||||
|
|
||||||
|
private static Logger LOGGER = Logger.getLogger(KeycloakAdapterPolicyEnforcer.class);
|
||||||
|
|
||||||
|
public KeycloakAdapterPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||||
|
super(policyEnforcer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||||
|
int retry = 2;
|
||||||
|
AccessToken original = accessToken;
|
||||||
|
|
||||||
|
while (retry > 0) {
|
||||||
|
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
|
||||||
|
|
||||||
|
if (accessToken == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Authorization authorization = original.getAuthorization();
|
||||||
|
|
||||||
|
if (authorization == null) {
|
||||||
|
authorization = new AccessToken.Authorization();
|
||||||
|
authorization.setPermissions(new ArrayList<Permission>());
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||||
|
|
||||||
|
if (newAuthorization != null) {
|
||||||
|
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
||||||
|
}
|
||||||
|
|
||||||
|
original.setAuthorization(authorization);
|
||||||
|
|
||||||
|
retry--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||||
|
String accessDeniedPath = getEnforcerConfig().getAccessDeniedPath();
|
||||||
|
HttpFacade.Response response = facade.getResponse();
|
||||||
|
|
||||||
|
if (accessDeniedPath != null) {
|
||||||
|
response.setStatus(302);
|
||||||
|
response.setHeader("Location", accessDeniedPath);
|
||||||
|
} else {
|
||||||
|
response.sendError(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
|
||||||
|
try {
|
||||||
|
String accessToken = httpFacade.getSecurityContext().getTokenString();
|
||||||
|
AuthzClient authzClient = getAuthzClient();
|
||||||
|
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||||
|
|
||||||
|
if (getEnforcerConfig().getUmaProtocolConfig() != null) {
|
||||||
|
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||||
|
PermissionRequest permissionRequest = new PermissionRequest();
|
||||||
|
|
||||||
|
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||||
|
permissionRequest.setScopes(requiredScopes);
|
||||||
|
|
||||||
|
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
|
||||||
|
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
|
||||||
|
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||||
|
|
||||||
|
if (authzResponse != null) {
|
||||||
|
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
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().getClientId());
|
||||||
|
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
|
} else {
|
||||||
|
EntitlementRequest request = new EntitlementRequest();
|
||||||
|
PermissionRequest permissionRequest = new PermissionRequest();
|
||||||
|
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||||
|
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||||
|
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||||
|
request.addPermission(permissionRequest);
|
||||||
|
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||||
|
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (AuthorizationDeniedException e) {
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unexpected error during authorization request.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
class PathMatcher {
|
||||||
|
|
||||||
|
private static final String ANY_RESOURCE_PATTERN = "/*";
|
||||||
|
|
||||||
|
PathConfig matches(final String requestedUri, List<PathConfig> paths) {
|
||||||
|
PathConfig actualConfig = null;
|
||||||
|
|
||||||
|
for (PathConfig entry : paths) {
|
||||||
|
String protectedUri = entry.getPath();
|
||||||
|
String selectedUri = null;
|
||||||
|
|
||||||
|
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
|
||||||
|
|
||||||
|
if (suffixIndex != -1) {
|
||||||
|
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
|
||||||
|
|
||||||
|
if (requestedUri.endsWith(protectedSuffix)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protectedUri.equals(requestedUri)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
|
||||||
|
String formattedPattern = removeWildCardsFromUri(protectedUri);
|
||||||
|
|
||||||
|
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int startRegex = protectedUri.indexOf('{');
|
||||||
|
|
||||||
|
if (startRegex != -1) {
|
||||||
|
String prefix = protectedUri.substring(0, startRegex);
|
||||||
|
|
||||||
|
if (requestedUri.startsWith(prefix)) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedUri != null) {
|
||||||
|
selectedUri = protectedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedUri != null) {
|
||||||
|
if (actualConfig == null) {
|
||||||
|
actualConfig = entry;
|
||||||
|
} else {
|
||||||
|
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
|
||||||
|
actualConfig = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
|
||||||
|
actualConfig = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actualConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String removeWildCardsFromUri(String protectedUri) {
|
||||||
|
return protectedUri.replaceAll("/[*]", "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.AuthorizationContext;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
import org.keycloak.authorization.client.AuthzClient;
|
||||||
|
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.representations.adapters.config.AdapterConfig;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||||
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||||
|
import org.keycloak.representations.idm.authorization.Permission;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class PolicyEnforcer {
|
||||||
|
|
||||||
|
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||||
|
|
||||||
|
private final KeycloakDeployment deployment;
|
||||||
|
private final AuthzClient authzClient;
|
||||||
|
private final PolicyEnforcerConfig enforcerConfig;
|
||||||
|
private final List<PathConfig> paths;
|
||||||
|
|
||||||
|
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()));
|
||||||
|
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debug("Initialization complete. Path configurations:");
|
||||||
|
for (PathConfig pathConfig : this.paths) {
|
||||||
|
LOGGER.debug(pathConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debugv("Policy enforcement is enable. 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled()) {
|
||||||
|
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||||
|
LOGGER.debugv("Returning authorization context with permissions:");
|
||||||
|
for (Permission permission : context.getPermissions()) {
|
||||||
|
LOGGER.debug(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
PolicyEnforcerConfig getEnforcerConfig() {
|
||||||
|
return enforcerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthzClient getClient() {
|
||||||
|
return authzClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PathConfig> getPaths() {
|
||||||
|
return Collections.unmodifiableList(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakDeployment getDeployment() {
|
||||||
|
return deployment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||||
|
if (enforcerConfig.getPaths().isEmpty()) {
|
||||||
|
LOGGER.info("No path provided in configuration.");
|
||||||
|
return configureAllPathsForResourceServer(protectedResource);
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Paths provided in configuration.");
|
||||||
|
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||||
|
List<PathConfig> paths = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||||
|
Set<String> search;
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||||
|
search = protectedResource.findByFilter("uri=" + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
PathConfig existingPath = null;
|
||||||
|
|
||||||
|
for (PathConfig current : paths) {
|
||||||
|
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
||||||
|
existingPath = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingPath == null) {
|
||||||
|
paths.add(pathConfig);
|
||||||
|
} else {
|
||||||
|
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||||
|
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||||
|
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||||
|
List<PathConfig> paths = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String id : protectedResource.findAll()) {
|
||||||
|
RegistrationResponse response = protectedResource.findById(id);
|
||||||
|
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
||||||
|
|
||||||
|
if (resourceDescription.getUri() != null) {
|
||||||
|
paths.add(createPathConfig(resourceDescription));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
||||||
|
PathConfig pathConfig = new PathConfig();
|
||||||
|
|
||||||
|
pathConfig.setId(resourceDescription.getId());
|
||||||
|
pathConfig.setName(resourceDescription.getName());
|
||||||
|
pathConfig.setPath(resourceDescription.getUri());
|
||||||
|
|
||||||
|
List<String> scopeNames = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
||||||
|
scopeNames.add(scope.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
pathConfig.setScopes(scopeNames);
|
||||||
|
pathConfig.setType(resourceDescription.getType());
|
||||||
|
|
||||||
|
return pathConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -38,13 +38,13 @@ import javax.security.auth.spi.LoginModule;
|
|||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.KeycloakPrincipal;
|
import org.keycloak.KeycloakPrincipal;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
import org.keycloak.adapters.AdapterUtils;
|
import org.keycloak.adapters.AdapterUtils;
|
||||||
import org.keycloak.common.util.FindFile;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
|
import org.keycloak.common.VerificationException;
|
||||||
|
import org.keycloak.common.util.FindFile;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.common.util.reflections.Reflections;
|
import org.keycloak.common.util.reflections.Reflections;
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+7
-6
@@ -43,6 +43,7 @@ import org.keycloak.common.VerificationException;
|
|||||||
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
@@ -102,9 +103,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||||||
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||||
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
.append(", Error description: " + errorRep.getErrorDescription());
|
||||||
}
|
}
|
||||||
String error = errorBuilder.toString();
|
String error = errorBuilder.toString();
|
||||||
log.warn(error);
|
log.warn(error);
|
||||||
@@ -161,9 +162,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
|
|||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
if (status == 400) {
|
if (status == 400) {
|
||||||
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
|
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||||
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
|
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||||
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
|
.append(", Error description: " + errorRep.getErrorDescription());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (is != null) is.close();
|
if (is != null) is.close();
|
||||||
|
|||||||
Regular → Executable
File mode changed.
+2
-1
@@ -54,13 +54,14 @@ public class KeycloakDeploymentBuilderTest {
|
|||||||
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
|
||||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||||
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
|
||||||
assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
|
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
|
||||||
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
|
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
|
||||||
assertTrue(deployment.isAlwaysRefreshToken());
|
assertTrue(deployment.isAlwaysRefreshToken());
|
||||||
assertTrue(deployment.isRegisterNodeAtStartup());
|
assertTrue(deployment.isRegisterNodeAtStartup());
|
||||||
assertEquals(1000, deployment.getRegisterNodePeriod());
|
assertEquals(1000, deployment.getRegisterNodePeriod());
|
||||||
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
|
||||||
assertEquals("email", deployment.getPrincipalAttribute());
|
assertEquals("email", deployment.getPrincipalAttribute());
|
||||||
|
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||||
|
/**
|
||||||
|
* @author github.com/tubbynl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RefreshableKeycloakSecurityContextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isActive() {
|
||||||
|
TokenMetadataRepresentation token = new TokenMetadataRepresentation();
|
||||||
|
token.setActive(true);
|
||||||
|
token.issuedNow();
|
||||||
|
RefreshableKeycloakSecurityContext sut = new RefreshableKeycloakSecurityContext(null,null,null,token,null, null, null);
|
||||||
|
|
||||||
|
// verify false if null deployment (KEYCLOAK-3050; yielded a npe)
|
||||||
|
assertFalse(sut.isActive());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,10 +24,10 @@
|
|||||||
"client-keystore": "classpath:/keystore.jks",
|
"client-keystore": "classpath:/keystore.jks",
|
||||||
"client-keystore-password": "storepass",
|
"client-keystore-password": "storepass",
|
||||||
"client-key-password": "keypass",
|
"client-key-password": "keypass",
|
||||||
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
|
|
||||||
"always-refresh-token": true,
|
"always-refresh-token": true,
|
||||||
"register-node-at-startup": true,
|
"register-node-at-startup": true,
|
||||||
"register-node-period": 1000,
|
"register-node-period": 1000,
|
||||||
"token-store": "cookie",
|
"token-store": "cookie",
|
||||||
"principal-attribute": "email"
|
"principal-attribute": "email",
|
||||||
|
"token-minimum-time-to-live": 10
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -49,7 +49,6 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
|
|
||||||
// if it's not a web-app there's nothing to secure
|
// if it's not a web-app there's nothing to secure
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
@@ -67,24 +66,24 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
// otherwise
|
// otherwise
|
||||||
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
|
||||||
|
|
||||||
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentName);
|
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentUnit);
|
||||||
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
||||||
|
|
||||||
if (hasSubsystemConfig || webRequiresKC) {
|
if (hasSubsystemConfig || webRequiresKC) {
|
||||||
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentName);
|
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentUnit.getName());
|
||||||
|
|
||||||
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
||||||
if (hasSubsystemConfig) {
|
if (hasSubsystemConfig) {
|
||||||
addJSONData(service.getJSON(deploymentName), warMetaData);
|
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
||||||
if (loginConfig != null) {
|
if (loginConfig != null) {
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
loginConfig.setRealmName(service.getRealmName(deploymentName));
|
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||||
} else {
|
} else {
|
||||||
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentName + " (loginConfig == null)");
|
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addValve(webMetaData);
|
addValve(webMetaData);
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-3
@@ -25,6 +25,9 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
||||||
|
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||||
|
import org.jboss.as.web.deployment.WarMetaData;
|
||||||
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service keeps track of the entire Keycloak management model so as to provide
|
* This service keeps track of the entire Keycloak management model so as to provide
|
||||||
@@ -154,13 +157,15 @@ public final class KeycloakAdapterConfigService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealmName(String deploymentName) {
|
public String getRealmName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJSON(String deploymentName) {
|
public String getJSON(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
ModelNode realm = this.realms.get(realmName);
|
ModelNode realm = this.realms.get(realmName);
|
||||||
@@ -184,9 +189,29 @@ public final class KeycloakAdapterConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureDeployment(String deploymentName) {
|
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||||
|
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
return this.secureDeployments.containsKey(deploymentName);
|
return this.secureDeployments.containsKey(deploymentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-3273: prefer module name if available
|
||||||
|
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
|
if (warMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
|
if (webMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String moduleName = webMetaData.getModuleName();
|
||||||
|
if (moduleName != null) return moduleName + ".war";
|
||||||
|
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -46,8 +46,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
||||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+2
@@ -119,6 +119,7 @@ public class KeycloakInstalled {
|
|||||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
.queryParam(OAuth2Constants.STATE, state)
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
|
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
|
||||||
Desktop.getDesktop().browse(new URI(authUrl));
|
Desktop.getDesktop().browse(new URI(authUrl));
|
||||||
@@ -175,6 +176,7 @@ public class KeycloakInstalled {
|
|||||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
|
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
|
||||||
.build().toString();
|
.build().toString();
|
||||||
|
|
||||||
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
|
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+4
-4
@@ -20,6 +20,7 @@ package org.keycloak.jaxrs;
|
|||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
import javax.ws.rs.InternalServerErrorException;
|
||||||
@@ -85,14 +86,13 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
|||||||
}
|
}
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
|
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||||
|
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
.queryParam(OAuth2Constants.CLIENT_ID, clientId)
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
.queryParam(OAuth2Constants.STATE, state);
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
if (scope != null) {
|
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||||
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
URI url = uriBuilder.build();
|
URI url = uriBuilder.build();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-jetty-adapter-spi</artifactId>
|
<artifactId>keycloak-jetty-adapter-spi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
+2
-2
@@ -340,13 +340,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||||||
Authentication authentication = request.getAuthentication();
|
Authentication authentication = request.getAuthentication();
|
||||||
if (!(authentication instanceof KeycloakAuthentication)) {
|
if (!(authentication instanceof KeycloakAuthentication)) {
|
||||||
UserIdentity userIdentity = createIdentity(principal);
|
UserIdentity userIdentity = createIdentity(principal);
|
||||||
authentication = createAuthentication(userIdentity);
|
authentication = createAuthentication(userIdentity, request);
|
||||||
request.setAuthentication(authentication);
|
request.setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
return authentication;
|
return authentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Authentication createAuthentication(UserIdentity userIdentity);
|
protected abstract Authentication createAuthentication(UserIdentity userIdentity, Request request);
|
||||||
|
|
||||||
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
||||||
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Authentication createAuthentication(UserIdentity userIdentity) {
|
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) {
|
||||||
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
@Override
|
@Override
|
||||||
public void logout() {
|
public void logout() {
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<?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>2.2.1.Final</version>
|
||||||
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
org.keycloak.adapters.jetty.*
|
||||||
|
</keycloak.osgi.export>
|
||||||
|
<keycloak.osgi.import>
|
||||||
|
org.eclipse.jetty.*;resolution:=optional,
|
||||||
|
javax.servlet.*;version="[3.0,4)";resolution:=optional,
|
||||||
|
org.keycloak.*;version="${project.version}",
|
||||||
|
*;resolution:=optional
|
||||||
|
</keycloak.osgi.import>
|
||||||
|
</properties>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<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-adapter-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<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>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>${jetty9.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>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>bundle-manifest</id>
|
||||||
|
<phase>process-classes</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>manifest</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Bundle-ClassPath>.</Bundle-ClassPath>
|
||||||
|
<Bundle-Name>${project.name}</Bundle-Name>
|
||||||
|
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
||||||
|
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
||||||
|
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.jetty;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class Jetty93RequestAuthenticator extends JettyRequestAuthenticator {
|
||||||
|
public Jetty93RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
|
||||||
|
super(facade, deployment, tokenStore, sslRedirectPort, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String changeHttpSessionId(boolean create) {
|
||||||
|
Request request = this.request;
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) {
|
||||||
|
return request.getSession(true).getId();
|
||||||
|
}
|
||||||
|
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
|
||||||
|
else return session.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.jetty;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.security.authentication.FormAuthenticator;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
|
import org.keycloak.adapters.spi.AdapterSessionStore;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class JettyAdapterSessionStore implements AdapterSessionStore {
|
||||||
|
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
|
||||||
|
protected Request myRequest;
|
||||||
|
|
||||||
|
public JettyAdapterSessionStore(Request request) {
|
||||||
|
this.myRequest = request; // for IDE/compilation purposes
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MultiMap<String> extractFormParameters(Request base_request) {
|
||||||
|
MultiMap<String> formParameters = new MultiMap<String>();
|
||||||
|
base_request.extractFormParameters(formParameters);
|
||||||
|
return formParameters;
|
||||||
|
}
|
||||||
|
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
|
||||||
|
base_request.setContentParameters(j_post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean restoreRequest() {
|
||||||
|
HttpSession session = myRequest.getSession(false);
|
||||||
|
if (session == null) return false;
|
||||||
|
synchronized (session) {
|
||||||
|
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
|
||||||
|
if (j_uri != null) {
|
||||||
|
// check if the request is for the same url as the original and restore
|
||||||
|
// params if it was a post
|
||||||
|
StringBuffer buf = myRequest.getRequestURL();
|
||||||
|
if (myRequest.getQueryString() != null)
|
||||||
|
buf.append("?").append(myRequest.getQueryString());
|
||||||
|
if (j_uri.equals(buf.toString())) {
|
||||||
|
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
|
||||||
|
myRequest.setMethod(method);
|
||||||
|
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
|
||||||
|
if (j_post != null) {
|
||||||
|
myRequest.setContentType("application/x-www-form-urlencoded");
|
||||||
|
MultiMap<String> map = new MultiMap<String>();
|
||||||
|
for (String key : j_post.keySet()) {
|
||||||
|
for (String val : j_post.getList(key)) {
|
||||||
|
map.add(key, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
restoreFormParameters(map, myRequest);
|
||||||
|
}
|
||||||
|
session.removeAttribute(FormAuthenticator.__J_URI);
|
||||||
|
session.removeAttribute(JettyHttpFacade.__J_METHOD);
|
||||||
|
session.removeAttribute(FormAuthenticator.__J_POST);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveRequest() {
|
||||||
|
// remember the current URI
|
||||||
|
HttpSession session = myRequest.getSession();
|
||||||
|
synchronized (session) {
|
||||||
|
// But only if it is not set already, or we save every uri that leads to a login form redirect
|
||||||
|
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
|
||||||
|
StringBuffer buf = myRequest.getRequestURL();
|
||||||
|
if (myRequest.getQueryString() != null)
|
||||||
|
buf.append("?").append(myRequest.getQueryString());
|
||||||
|
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
|
||||||
|
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
|
||||||
|
|
||||||
|
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
|
||||||
|
MultiMap<String> formParameters = extractFormParameters(myRequest);
|
||||||
|
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
|
||||||
|
for (String key : formParameters.keySet()) {
|
||||||
|
for (Object value : formParameters.getValues(key)) {
|
||||||
|
map.add(key, (String) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.setAttribute(CACHED_FORM_PARAMETERS, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.jetty;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Authentication;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.UserIdentity;
|
||||||
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
|
||||||
|
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
|
||||||
|
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
|
||||||
|
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
|
||||||
|
|
||||||
|
public KeycloakJettyAuthenticator() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Request resolveRequest(ServletRequest req) {
|
||||||
|
return Request.getBaseRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Authentication createAuthentication(UserIdentity userIdentity, final Request request) {
|
||||||
|
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
|
||||||
|
@Override
|
||||||
|
public void logout() {
|
||||||
|
logoutCurrent(request);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
|
||||||
|
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
|
||||||
|
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||||
|
return new Jetty93RequestAuthenticator(facade, deployment, tokenStore, -1, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Jetty Integration</name>
|
<name>Keycloak Jetty Integration</name>
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>jetty-core</module>
|
<module>jetty-core</module>
|
||||||
<module>jetty8.1</module>
|
<module>jetty8.1</module>
|
||||||
<module>jetty9.2</module>
|
|
||||||
<module>jetty9.1</module>
|
<module>jetty9.1</module>
|
||||||
|
<module>jetty9.2</module>
|
||||||
|
<module>jetty9.3</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -58,6 +58,25 @@
|
|||||||
<goal>minify</goal>
|
<goal>minify</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>min-authz-js</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<configuration>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
|
||||||
|
<jsSourceDir>.</jsSourceDir>
|
||||||
|
<jsSourceFiles>
|
||||||
|
<jsSourceFile>keycloak-authz.js</jsSourceFile>
|
||||||
|
</jsSourceFiles>
|
||||||
|
|
||||||
|
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
|
||||||
|
<jsTargetDir>.</jsTargetDir>
|
||||||
|
<jsFinalFile>keycloak-authz.js</jsFinalFile>
|
||||||
|
</configuration>
|
||||||
|
<goals>
|
||||||
|
<goal>minify</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function( window, undefined ) {
|
||||||
|
|
||||||
|
var KeycloakAuthorization = function (keycloak) {
|
||||||
|
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.onreadystatechange = function () {
|
||||||
|
if (request.readyState == 4) {
|
||||||
|
if (request.status == 200) {
|
||||||
|
_instance.config = JSON.parse(request.responseText);
|
||||||
|
} else {
|
||||||
|
console.error('Could not obtain configuration from server.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
this.authorize = function (wwwAuthenticateHeader) {
|
||||||
|
this.then = function (onGrant, onDeny, onError) {
|
||||||
|
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
|
||||||
|
var params = wwwAuthenticateHeader.split(',');
|
||||||
|
|
||||||
|
for (i = 0; i < params.length; i++) {
|
||||||
|
var param = params[i].split('=');
|
||||||
|
|
||||||
|
if (param[0] == 'ticket') {
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var ticket = param[1].substring(1, param[1].length - 1).trim();
|
||||||
|
|
||||||
|
request.send(JSON.stringify(
|
||||||
|
{
|
||||||
|
ticket: ticket,
|
||||||
|
rpt: _instance.rpt
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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('=');
|
||||||
|
|
||||||
|
if (param[0] == 'realm') {
|
||||||
|
clientId = param[1].substring(1, param[1].length - 1).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
|
||||||
|
*/
|
||||||
|
this.entitlement = function (resourceSeververId) {
|
||||||
|
this.then = function (onGrant, onDeny, onError) {
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
|
||||||
|
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
|
||||||
|
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
|
||||||
|
|
||||||
|
request.onreadystatechange = function () {
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.send(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
|
||||||
|
module.exports = KeycloakAuthorization;
|
||||||
|
} else {
|
||||||
|
window.KeycloakAuthorization = KeycloakAuthorization;
|
||||||
|
|
||||||
|
if ( typeof define === "function" && define.amd ) {
|
||||||
|
define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})( window );
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
var kc = this;
|
var kc = this;
|
||||||
var adapter;
|
var adapter;
|
||||||
var refreshQueue = [];
|
var refreshQueue = [];
|
||||||
|
var callbackStorage;
|
||||||
|
|
||||||
var loginIframe = {
|
var loginIframe = {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -35,10 +36,18 @@
|
|||||||
kc.init = function (initOptions) {
|
kc.init = function (initOptions) {
|
||||||
kc.authenticated = false;
|
kc.authenticated = false;
|
||||||
|
|
||||||
if (window.Cordova) {
|
callbackStorage = createCallbackStorage();
|
||||||
|
|
||||||
|
if (initOptions && initOptions.adapter === 'cordova') {
|
||||||
adapter = loadAdapter('cordova');
|
adapter = loadAdapter('cordova');
|
||||||
} else {
|
} else if (initOptions && initOptions.adapter === 'default') {
|
||||||
adapter = loadAdapter();
|
adapter = loadAdapter();
|
||||||
|
} else {
|
||||||
|
if (window.Cordova) {
|
||||||
|
adapter = loadAdapter('cordova');
|
||||||
|
} else {
|
||||||
|
adapter = loadAdapter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initOptions) {
|
if (initOptions) {
|
||||||
@@ -94,8 +103,8 @@
|
|||||||
initPromise.promise.success(function() {
|
initPromise.promise.success(function() {
|
||||||
kc.onReady && kc.onReady(kc.authenticated);
|
kc.onReady && kc.onReady(kc.authenticated);
|
||||||
promise.setSuccess(kc.authenticated);
|
promise.setSuccess(kc.authenticated);
|
||||||
}).error(function() {
|
}).error(function(errorData) {
|
||||||
promise.setError();
|
promise.setError(errorData);
|
||||||
});
|
});
|
||||||
|
|
||||||
var configPromise = loadConfig(config);
|
var configPromise = loadConfig(config);
|
||||||
@@ -145,24 +154,35 @@
|
|||||||
return;
|
return;
|
||||||
} else if (initOptions) {
|
} else if (initOptions) {
|
||||||
if (initOptions.token || initOptions.refreshToken) {
|
if (initOptions.token || initOptions.refreshToken) {
|
||||||
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
|
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
|
||||||
kc.timeSkew = initOptions.timeSkew || 0;
|
|
||||||
|
|
||||||
if (loginIframe.enable) {
|
if (loginIframe.enable) {
|
||||||
setupCheckLoginIframe().success(function() {
|
setupCheckLoginIframe().success(function() {
|
||||||
checkLoginIframe().success(function () {
|
checkLoginIframe().success(function () {
|
||||||
|
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||||
initPromise.setSuccess();
|
initPromise.setSuccess();
|
||||||
}).error(function () {
|
}).error(function () {
|
||||||
|
kc.onAuthError && kc.onAuthError();
|
||||||
if (initOptions.onLoad) {
|
if (initOptions.onLoad) {
|
||||||
onLoad();
|
onLoad();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
initPromise.setSuccess();
|
kc.updateToken(-1).success(function() {
|
||||||
|
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||||
|
initPromise.setSuccess();
|
||||||
|
}).error(function() {
|
||||||
|
kc.onAuthError && kc.onAuthError();
|
||||||
|
if (initOptions.onLoad) {
|
||||||
|
onLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (initOptions.onLoad) {
|
} else if (initOptions.onLoad) {
|
||||||
onLoad();
|
onLoad();
|
||||||
|
} else {
|
||||||
|
initPromise.setSuccess();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
initPromise.setSuccess();
|
initPromise.setSuccess();
|
||||||
@@ -190,13 +210,15 @@
|
|||||||
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
|
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
|
callbackStorage.add({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
|
||||||
|
|
||||||
var action = 'auth';
|
var action = 'auth';
|
||||||
if (options && options.action == 'register') {
|
if (options && options.action == 'register') {
|
||||||
action = 'registrations';
|
action = 'registrations';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scope = (options && options.scope) ? "openid " + options.scope : "openid";
|
||||||
|
|
||||||
var url = getRealmUrl()
|
var url = getRealmUrl()
|
||||||
+ '/protocol/openid-connect/' + action
|
+ '/protocol/openid-connect/' + action
|
||||||
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
+ '?client_id=' + encodeURIComponent(kc.clientId)
|
||||||
@@ -204,12 +226,17 @@
|
|||||||
+ '&state=' + encodeURIComponent(state)
|
+ '&state=' + encodeURIComponent(state)
|
||||||
+ '&nonce=' + encodeURIComponent(nonce)
|
+ '&nonce=' + encodeURIComponent(nonce)
|
||||||
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
|
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
|
||||||
+ '&response_type=' + encodeURIComponent(kc.responseType);
|
+ '&response_type=' + encodeURIComponent(kc.responseType)
|
||||||
|
+ '&scope=' + encodeURIComponent(scope);
|
||||||
|
|
||||||
if (options && options.prompt) {
|
if (options && options.prompt) {
|
||||||
url += '&prompt=' + encodeURIComponent(options.prompt);
|
url += '&prompt=' + encodeURIComponent(options.prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.maxAge) {
|
||||||
|
url += '&max_age=' + encodeURIComponent(options.maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
if (options && options.loginHint) {
|
if (options && options.loginHint) {
|
||||||
url += '&login_hint=' + encodeURIComponent(options.loginHint);
|
url += '&login_hint=' + encodeURIComponent(options.loginHint);
|
||||||
}
|
}
|
||||||
@@ -218,10 +245,6 @@
|
|||||||
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options && options.scope) {
|
|
||||||
url += '&scope=' + encodeURIComponent(options.scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options && options.locale) {
|
if (options && options.locale) {
|
||||||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||||
}
|
}
|
||||||
@@ -335,11 +358,10 @@
|
|||||||
throw 'Not authenticated';
|
throw 'Not authenticated';
|
||||||
}
|
}
|
||||||
|
|
||||||
var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
|
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
|
||||||
if (minValidity) {
|
if (minValidity) {
|
||||||
expiresIn -= minValidity;
|
expiresIn -= minValidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return expiresIn < 0;
|
return expiresIn < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +376,20 @@
|
|||||||
minValidity = minValidity || 5;
|
minValidity = minValidity || 5;
|
||||||
|
|
||||||
var exec = function() {
|
var exec = function() {
|
||||||
if (!kc.isTokenExpired(minValidity)) {
|
var refreshToken = false;
|
||||||
|
if (kc.timeSkew == -1) {
|
||||||
|
console.info('Skew ' + kc.timeSkew);
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: time skew not set');
|
||||||
|
} else if (minValidity == -1) {
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: forced refresh');
|
||||||
|
} else if (kc.isTokenExpired(minValidity)) {
|
||||||
|
refreshToken = true;
|
||||||
|
console.info('[KEYCLOAK] Refreshing token: token expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
promise.setSuccess(false);
|
promise.setSuccess(false);
|
||||||
} else {
|
} else {
|
||||||
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
|
||||||
@@ -366,6 +401,7 @@
|
|||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
req.open('POST', url, true);
|
req.open('POST', url, true);
|
||||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
req.withCredentials = true;
|
||||||
|
|
||||||
if (kc.clientId && kc.clientSecret) {
|
if (kc.clientId && kc.clientSecret) {
|
||||||
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
|
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
|
||||||
@@ -378,18 +414,21 @@
|
|||||||
req.onreadystatechange = function () {
|
req.onreadystatechange = function () {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
|
console.info('[KEYCLOAK] Token refreshed');
|
||||||
|
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
var tokenResponse = JSON.parse(req.responseText);
|
var tokenResponse = JSON.parse(req.responseText);
|
||||||
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
|
|
||||||
|
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
|
||||||
|
|
||||||
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setSuccess(true);
|
p.setSuccess(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.warn('[KEYCLOAK] Failed to refresh token');
|
||||||
|
|
||||||
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
kc.onAuthRefreshError && kc.onAuthRefreshError();
|
||||||
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
|
||||||
p.setError(true);
|
p.setError(true);
|
||||||
@@ -419,7 +458,7 @@
|
|||||||
|
|
||||||
kc.clearToken = function() {
|
kc.clearToken = function() {
|
||||||
if (kc.token) {
|
if (kc.token) {
|
||||||
setToken(null, null, null, true);
|
setToken(null, null, null);
|
||||||
kc.onAuthLogout && kc.onAuthLogout();
|
kc.onAuthLogout && kc.onAuthLogout();
|
||||||
if (kc.loginRequired) {
|
if (kc.loginRequired) {
|
||||||
kc.login();
|
kc.login();
|
||||||
@@ -452,8 +491,9 @@
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (prompt != 'none') {
|
if (prompt != 'none') {
|
||||||
kc.onAuthError && kc.onAuthError();
|
var errorData = { error: error, error_description: oauth.error_description };
|
||||||
promise && promise.setError();
|
kc.onAuthError && kc.onAuthError(errorData);
|
||||||
|
promise && promise.setError(errorData);
|
||||||
} else {
|
} else {
|
||||||
promise && promise.setSuccess();
|
promise && promise.setSuccess();
|
||||||
}
|
}
|
||||||
@@ -499,18 +539,16 @@
|
|||||||
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
|
||||||
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
timeLocal = (timeLocal + new Date().getTime()) / 2;
|
||||||
|
|
||||||
setToken(accessToken, refreshToken, idToken, true);
|
setToken(accessToken, refreshToken, idToken, timeLocal);
|
||||||
|
|
||||||
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
|
||||||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
|
||||||
|
|
||||||
console.log('invalid nonce!');
|
console.info('[KEYCLOAK] Invalid nonce, clearing token');
|
||||||
kc.clearToken();
|
kc.clearToken();
|
||||||
promise && promise.setError();
|
promise && promise.setError();
|
||||||
} else {
|
} else {
|
||||||
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
|
||||||
|
|
||||||
if (fulfillPromise) {
|
if (fulfillPromise) {
|
||||||
kc.onAuthSuccess && kc.onAuthSuccess();
|
kc.onAuthSuccess && kc.onAuthSuccess();
|
||||||
promise && promise.setSuccess();
|
promise && promise.setSuccess();
|
||||||
@@ -583,7 +621,7 @@
|
|||||||
return promise.promise;
|
return promise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setToken(token, refreshToken, idToken, useTokenTime) {
|
function setToken(token, refreshToken, idToken, timeLocal) {
|
||||||
if (kc.tokenTimeoutHandle) {
|
if (kc.tokenTimeoutHandle) {
|
||||||
clearTimeout(kc.tokenTimeoutHandle);
|
clearTimeout(kc.tokenTimeoutHandle);
|
||||||
kc.tokenTimeoutHandle = null;
|
kc.tokenTimeoutHandle = null;
|
||||||
@@ -602,10 +640,24 @@
|
|||||||
kc.realmAccess = kc.tokenParsed.realm_access;
|
kc.realmAccess = kc.tokenParsed.realm_access;
|
||||||
kc.resourceAccess = kc.tokenParsed.resource_access;
|
kc.resourceAccess = kc.tokenParsed.resource_access;
|
||||||
|
|
||||||
|
if (timeLocal) {
|
||||||
|
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
|
||||||
|
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
|
||||||
|
} else {
|
||||||
|
kc.timeSkew = -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (kc.onTokenExpired) {
|
if (kc.onTokenExpired) {
|
||||||
var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
|
if (kc.timeSkew == -1) {
|
||||||
var expiresIn = kc.tokenParsed.exp - start;
|
kc.onTokenExpired();
|
||||||
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
|
} else {
|
||||||
|
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
|
||||||
|
if (expiresIn <= 0) {
|
||||||
|
kc.onTokenExpired();
|
||||||
|
} else {
|
||||||
|
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -686,14 +738,11 @@
|
|||||||
|
|
||||||
function parseCallback(url) {
|
function parseCallback(url) {
|
||||||
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
|
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
|
||||||
|
var oauthState = callbackStorage.get(oauth.state);
|
||||||
|
|
||||||
var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
|
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
|
||||||
|
oauth.redirectUri = oauthState.redirectUri;
|
||||||
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
|
oauth.storedNonce = oauthState.nonce;
|
||||||
delete sessionStorage.oauthState;
|
|
||||||
|
|
||||||
oauth.redirectUri = sessionState.redirectUri;
|
|
||||||
oauth.storedNonce = sessionState.nonce;
|
|
||||||
|
|
||||||
if (oauth.fragment) {
|
if (oauth.fragment) {
|
||||||
oauth.newUrl += '#' + oauth.fragment;
|
oauth.newUrl += '#' + oauth.fragment;
|
||||||
@@ -780,8 +829,22 @@
|
|||||||
if (event.origin !== loginIframe.iframeOrigin) {
|
if (event.origin !== loginIframe.iframeOrigin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var data = JSON.parse(event.data);
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.callbackId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var promise = loginIframe.callbackMap[data.callbackId];
|
var promise = loginIframe.callbackMap[data.callbackId];
|
||||||
|
if (!promise) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
delete loginIframe.callbackMap[data.callbackId];
|
delete loginIframe.callbackMap[data.callbackId];
|
||||||
|
|
||||||
if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
|
if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
|
||||||
@@ -970,6 +1033,124 @@
|
|||||||
throw 'invalid adapter type: ' + type;
|
throw 'invalid adapter type: ' + type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var LocalStorage = function() {
|
||||||
|
if (!(this instanceof LocalStorage)) {
|
||||||
|
return new LocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('kc-test', 'test');
|
||||||
|
localStorage.removeItem('kc-test');
|
||||||
|
|
||||||
|
var cs = this;
|
||||||
|
|
||||||
|
function clearExpired() {
|
||||||
|
var time = new Date().getTime();
|
||||||
|
for (var i = 1; i <= localStorage.length; i++) {
|
||||||
|
var key = localStorage.key(i);
|
||||||
|
if (key && key.indexOf('kc-callback-') == 0) {
|
||||||
|
var value = localStorage.getItem(key);
|
||||||
|
if (value) {
|
||||||
|
try {
|
||||||
|
var expires = JSON.parse(value).expires;
|
||||||
|
if (!expires || expires < time) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.get = function(state) {
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = 'kc-callback-' + state;
|
||||||
|
var value = localStorage.getItem(key);
|
||||||
|
if (value) {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
value = JSON.parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearExpired();
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
cs.add = function(state) {
|
||||||
|
clearExpired();
|
||||||
|
|
||||||
|
var key = 'kc-callback-' + state.state;
|
||||||
|
state.expires = new Date().getTime() + (60 * 60 * 1000);
|
||||||
|
localStorage.setItem(key, JSON.stringify(state));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var CookieStorage = function() {
|
||||||
|
if (!(this instanceof CookieStorage)) {
|
||||||
|
return new CookieStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs = this;
|
||||||
|
|
||||||
|
cs.get = function(state) {
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = getCookie('kc-callback-' + state);
|
||||||
|
setCookie('kc-callback-' + state, '', cookieExpiration(-100));
|
||||||
|
if (value) {
|
||||||
|
return JSON.parse(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cs.add = function(state) {
|
||||||
|
setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60));
|
||||||
|
};
|
||||||
|
|
||||||
|
cs.removeItem = function(key) {
|
||||||
|
setCookie(key, '', cookieExpiration(-100));
|
||||||
|
};
|
||||||
|
|
||||||
|
var cookieExpiration = function (minutes) {
|
||||||
|
var exp = new Date();
|
||||||
|
exp.setTime(exp.getTime() + (minutes*60*1000));
|
||||||
|
return exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getCookie = function (key) {
|
||||||
|
var name = key + '=';
|
||||||
|
var ca = document.cookie.split(';');
|
||||||
|
for (var i = 0; i < ca.length; i++) {
|
||||||
|
var c = ca[i];
|
||||||
|
while (c.charAt(0) == ' ') {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
var setCookie = function (key, value, expirationDate) {
|
||||||
|
var cookie = key + '=' + value + '; '
|
||||||
|
+ 'expires=' + expirationDate.toUTCString() + '; ';
|
||||||
|
document.cookie = cookie;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function createCallbackStorage() {
|
||||||
|
try {
|
||||||
|
return new LocalStorage();
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CookieStorage();
|
||||||
|
}
|
||||||
|
|
||||||
var CallbackParser = function(uriToParse, responseMode) {
|
var CallbackParser = function(uriToParse, responseMode) {
|
||||||
if (!(this instanceof CallbackParser)) {
|
if (!(this instanceof CallbackParser)) {
|
||||||
@@ -1015,7 +1196,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var handleQueryParam = function(paramName, paramValue, oauth) {
|
var handleQueryParam = function(paramName, paramValue, oauth) {
|
||||||
var supportedOAuthParams = [ 'code', 'error', 'state' ];
|
var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ];
|
||||||
|
|
||||||
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
|
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
|
||||||
if (paramName === supportedOAuthParams[i]) {
|
if (paramName === supportedOAuthParams[i]) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -36,15 +36,32 @@
|
|||||||
org.keycloak.adapters.osgi.*
|
org.keycloak.adapters.osgi.*
|
||||||
</keycloak.osgi.export>
|
</keycloak.osgi.export>
|
||||||
<keycloak.osgi.import>
|
<keycloak.osgi.import>
|
||||||
org.ops4j.pax.web.*;version="[3.0,4)",
|
org.ops4j.pax.web.*;version="[3.0,5)",
|
||||||
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
javax.servlet.*;version="[2.5,4)";resolution:=optional,
|
||||||
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
|
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
|
||||||
org.keycloak.*;version="${project.version}",
|
org.keycloak.*;version="${project.version}",
|
||||||
|
org.apache.cxf.transport.http;resolution:=optional;version="[3,4)",
|
||||||
|
org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)",
|
||||||
*;resolution:=optional
|
*;resolution:=optional
|
||||||
</keycloak.osgi.import>
|
</keycloak.osgi.import>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
@@ -65,12 +82,23 @@
|
|||||||
<artifactId>pax-web-runtime</artifactId>
|
<artifactId>pax-web-runtime</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ops4j.pax.web</groupId>
|
||||||
|
<artifactId>pax-web-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.cxf</groupId>
|
||||||
|
<artifactId>cxf-rt-transports-http</artifactId>
|
||||||
|
<version>${cxf.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||||
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||||
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
|
||||||
|
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
|
||||||
|
|
||||||
|
private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
|
||||||
|
String path = request.getURI();
|
||||||
|
String[] urlTokens = path.split("/");
|
||||||
|
if (urlTokens.length < 4) {
|
||||||
|
throw new IllegalStateException("Not able to determine the web-context to load the correspondent keycloak.json file");
|
||||||
|
}
|
||||||
|
|
||||||
|
String webContext = urlTokens[3];
|
||||||
|
|
||||||
|
KeycloakDeployment deployment = cache.get(webContext);
|
||||||
|
if (null == deployment) {
|
||||||
|
// not found on the simple cache, try to load it from the file system
|
||||||
|
String keycloakConfig = (String) System.getProperties().get("keycloak.config");
|
||||||
|
if(keycloakConfig == null || "".equals(keycloakConfig.trim())){
|
||||||
|
String karafEtc = (String) System.getProperties().get("karaf.etc");
|
||||||
|
if(karafEtc == null || "".equals(karafEtc.trim())){
|
||||||
|
throw new IllegalStateException("Neither \"keycloak.config\" nor \"karaf.etc\" java properties are set. Please set one of them.");
|
||||||
|
}
|
||||||
|
keycloakConfig = karafEtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
String absolutePath = keycloakConfig + File.separator + webContext + "-keycloak.json";
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
is = new FileInputStream(absolutePath);
|
||||||
|
} catch (FileNotFoundException e){
|
||||||
|
throw new IllegalStateException("Not able to find the file " + absolutePath);
|
||||||
|
}
|
||||||
|
deployment = KeycloakDeploymentBuilder.build(is);
|
||||||
|
cache.put(webContext, deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deployment;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+113
-27
@@ -18,15 +18,22 @@
|
|||||||
package org.keycloak.adapters.osgi;
|
package org.keycloak.adapters.osgi;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Dictionary;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.servlet.Servlet;
|
import javax.servlet.Servlet;
|
||||||
|
|
||||||
|
import org.apache.cxf.transport.http.DestinationRegistry;
|
||||||
|
import org.apache.cxf.transport.servlet.CXFNonSpringServlet;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.ops4j.pax.web.service.WebContainer;
|
import org.ops4j.pax.web.service.WebContainer;
|
||||||
import org.osgi.framework.BundleContext;
|
import org.osgi.framework.BundleContext;
|
||||||
import org.osgi.framework.ServiceReference;
|
import org.osgi.framework.ServiceReference;
|
||||||
|
import org.osgi.service.cm.Configuration;
|
||||||
|
import org.osgi.service.cm.ConfigurationAdmin;
|
||||||
import org.osgi.service.http.HttpContext;
|
import org.osgi.service.http.HttpContext;
|
||||||
import org.osgi.util.tracker.ServiceTracker;
|
import org.osgi.util.tracker.ServiceTracker;
|
||||||
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
||||||
@@ -42,13 +49,15 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer;
|
|||||||
*/
|
*/
|
||||||
public class ServletReregistrationService {
|
public class ServletReregistrationService {
|
||||||
|
|
||||||
|
private static final String CXF_SERVLET_PREFIX = "org.apache.cxf.servlet.";
|
||||||
protected static final Logger log = Logger.getLogger(ServletReregistrationService.class);
|
protected static final Logger log = Logger.getLogger(ServletReregistrationService.class);
|
||||||
|
|
||||||
private static final List<String> FILTERED_PROPERTIES = Arrays.asList("objectClass", "service.id");
|
private static final List<String> FILTERED_PROPERTIES = Arrays.asList("objectClass", "service.id");
|
||||||
|
|
||||||
private BundleContext bundleContext;
|
private BundleContext bundleContext;
|
||||||
private ServiceReference servletReference;
|
private ServiceReference managedServiceReference;
|
||||||
private ServiceTracker webContainerTracker;
|
private ServiceTracker webContainerTracker;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
public BundleContext getBundleContext() {
|
public BundleContext getBundleContext() {
|
||||||
return bundleContext;
|
return bundleContext;
|
||||||
@@ -58,24 +67,18 @@ public class ServletReregistrationService {
|
|||||||
this.bundleContext = bundleContext;
|
this.bundleContext = bundleContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServiceReference getServletReference() {
|
|
||||||
return servletReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServletReference(ServiceReference servletReference) {
|
|
||||||
this.servletReference = servletReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ServiceTracker getWebContainerTracker() {
|
|
||||||
return webContainerTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
if (servletReference == null) {
|
if ( managedServiceReference == null) {
|
||||||
throw new IllegalStateException("No servlet reference provided");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary properties = obtainProperties();
|
||||||
|
alias = (String)getProp(properties, CXF_SERVLET_PREFIX + "context", "/cxf");
|
||||||
|
if(alias == null){
|
||||||
|
alias = "/cxf";
|
||||||
}
|
}
|
||||||
|
|
||||||
final Servlet servlet = (Servlet) bundleContext.getService(servletReference);
|
|
||||||
WebContainer externalWebContainer = findExternalWebContainer();
|
WebContainer externalWebContainer = findExternalWebContainer();
|
||||||
if (externalWebContainer == null) {
|
if (externalWebContainer == null) {
|
||||||
return;
|
return;
|
||||||
@@ -83,18 +86,19 @@ public class ServletReregistrationService {
|
|||||||
|
|
||||||
// Unregister servlet from external container now
|
// Unregister servlet from external container now
|
||||||
try {
|
try {
|
||||||
externalWebContainer.unregisterServlet(servlet);
|
externalWebContainer.unregister(alias);
|
||||||
log.debug("Original servlet with alias " + getAlias() + " unregistered successfully from external web container.");
|
log.debug("Original servlet with alias " + getAlias() + " unregistered successfully from external web container.");
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
log.warn("Can't unregister servlet due to: " + e.getMessage());
|
log.warn("Can't unregister servlet due to: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Dictionary finalProperties = properties;
|
||||||
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object addingService(ServiceReference webContainerServiceReference) {
|
public Object addingService(ServiceReference webContainerServiceReference) {
|
||||||
WebContainer ourWebContainer = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
WebContainer ourWebContainer = (WebContainer) bundleContext.getService(webContainerServiceReference);
|
||||||
registerServlet(ourWebContainer, servlet);
|
registerServlet(ourWebContainer, finalProperties);
|
||||||
log.debugv("Servlet with alias " + getAlias() + " registered to secured web container");
|
log.debugv("Servlet with alias " + getAlias() + " registered to secured web container");
|
||||||
return ourWebContainer;
|
return ourWebContainer;
|
||||||
}
|
}
|
||||||
@@ -122,28 +126,82 @@ public class ServletReregistrationService {
|
|||||||
|
|
||||||
// Re-register servlet back to original context
|
// Re-register servlet back to original context
|
||||||
WebContainer externalWebContainer = findExternalWebContainer();
|
WebContainer externalWebContainer = findExternalWebContainer();
|
||||||
Servlet servlet = (Servlet) bundleContext.getService(servletReference);
|
registerServlet(externalWebContainer, obtainProperties());
|
||||||
registerServlet(externalWebContainer, servlet);
|
|
||||||
log.debug("Servlet with alias " + getAlias() + " registered back to external web container");
|
log.debug("Servlet with alias " + getAlias() + " registered back to external web container");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAlias() {
|
private String getAlias() {
|
||||||
return (String) servletReference.getProperty("alias");
|
return alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void registerServlet(WebContainer webContainer, Servlet servlet) {
|
/**
|
||||||
|
* Code comes from org.apache.cxf.transport.http.osgi.ServletExporter#updated(java.util.Dictionary)
|
||||||
|
* @param webContainer
|
||||||
|
* @param properties
|
||||||
|
*/
|
||||||
|
protected void registerServlet(WebContainer webContainer, Dictionary properties) {
|
||||||
|
HttpContext httpContext = webContainer.createDefaultHttpContext();
|
||||||
|
|
||||||
|
ServiceReference destinationServiceServiceReference = bundleContext.getServiceReference("org.apache.cxf.transport.http.DestinationRegistry");
|
||||||
|
DestinationRegistry destinationRegistry = (DestinationRegistry) bundleContext.getService(destinationServiceServiceReference);
|
||||||
|
|
||||||
|
Servlet servlet = new CXFNonSpringServlet(destinationRegistry, false);
|
||||||
try {
|
try {
|
||||||
|
if (properties == null) {
|
||||||
|
properties = new Properties();
|
||||||
|
}
|
||||||
|
Properties sprops = new Properties();
|
||||||
|
sprops.put("init-prefix",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "init-prefix", ""));
|
||||||
|
sprops.put("servlet-name",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "name", "cxf-osgi-transport-servlet"));
|
||||||
|
sprops.put("hide-service-list-page",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "hide-service-list-page", "false"));
|
||||||
|
sprops.put("disable-address-updates",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "disable-address-updates", "true"));
|
||||||
|
sprops.put("base-address",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "base-address", ""));
|
||||||
|
sprops.put("service-list-path",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "service-list-path", ""));
|
||||||
|
sprops.put("static-resources-list",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "static-resources-list", ""));
|
||||||
|
sprops.put("redirects-list",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "redirects-list", ""));
|
||||||
|
sprops.put("redirect-servlet-name",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "redirect-servlet-name", ""));
|
||||||
|
sprops.put("redirect-servlet-path",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "redirect-servlet-path", ""));
|
||||||
|
sprops.put("service-list-all-contexts",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "service-list-all-contexts", ""));
|
||||||
|
sprops.put("service-list-page-authenticate",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "service-list-page-authenticate", "false"));
|
||||||
|
sprops.put("service-list-page-authenticate-realm",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "service-list-page-authenticate-realm", "karaf"));
|
||||||
|
sprops.put("use-x-forwarded-headers",
|
||||||
|
getProp(properties, CXF_SERVLET_PREFIX + "use-x-forwarded-headers", "false"));
|
||||||
|
|
||||||
|
// Accept extra properties by default, can be disabled if it is really needed
|
||||||
|
if (Boolean.valueOf(getProp(properties, CXF_SERVLET_PREFIX + "support.extra.properties", "true").toString())) {
|
||||||
|
Enumeration keys = properties.keys();
|
||||||
|
while (keys.hasMoreElements()) {
|
||||||
|
String nextKey = keys.nextElement().toString();
|
||||||
|
if (!nextKey.startsWith(CXF_SERVLET_PREFIX)) {
|
||||||
|
sprops.put(nextKey, properties.get(nextKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Hashtable<String, Object> servletInitParams = new Hashtable<String, Object>();
|
Hashtable<String, Object> servletInitParams = new Hashtable<String, Object>();
|
||||||
String[] propNames = servletReference.getPropertyKeys();
|
Enumeration keys = sprops.keys();
|
||||||
for (String propName : propNames) {
|
|
||||||
|
while(keys.hasMoreElements()){
|
||||||
|
String propName = (String) keys.nextElement();
|
||||||
if (!FILTERED_PROPERTIES.contains(propName)) {
|
if (!FILTERED_PROPERTIES.contains(propName)) {
|
||||||
servletInitParams.put(propName, servletReference.getProperty(propName));
|
servletInitParams.put(propName, sprops.getProperty(propName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to register servlet in given web container now
|
// Try to register servlet in given web container now
|
||||||
HttpContext httpContext = webContainer.createDefaultHttpContext();
|
|
||||||
String alias = (String) servletReference.getProperty("alias");
|
|
||||||
webContainer.registerServlet(alias, servlet, servletInitParams, httpContext);
|
webContainer.registerServlet(alias, servlet, servletInitParams, httpContext);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Can't register servlet in web container", e);
|
log.error("Can't register servlet in web container", e);
|
||||||
@@ -156,7 +214,7 @@ public class ServletReregistrationService {
|
|||||||
* @return web container or null
|
* @return web container or null
|
||||||
*/
|
*/
|
||||||
protected WebContainer findExternalWebContainer() {
|
protected WebContainer findExternalWebContainer() {
|
||||||
BundleContext servletBundleContext = servletReference.getBundle().getBundleContext();
|
BundleContext servletBundleContext = managedServiceReference.getBundle().getBundleContext();
|
||||||
ServiceReference webContainerReference = servletBundleContext.getServiceReference(WebContainer.class.getName());
|
ServiceReference webContainerReference = servletBundleContext.getServiceReference(WebContainer.class.getName());
|
||||||
if (webContainerReference == null) {
|
if (webContainerReference == null) {
|
||||||
log.warn("Not found webContainer reference for bundle " + servletBundleContext);
|
log.warn("Not found webContainer reference for bundle " + servletBundleContext);
|
||||||
@@ -166,4 +224,32 @@ public class ServletReregistrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary obtainProperties(){
|
||||||
|
Dictionary properties = null;
|
||||||
|
ServiceReference reference = bundleContext.getServiceReference(ConfigurationAdmin.class.getName());
|
||||||
|
ConfigurationAdmin admin = (ConfigurationAdmin) bundleContext.getService(reference);
|
||||||
|
try {
|
||||||
|
Configuration configuration = admin.getConfiguration("org.apache.cxf.osgi");
|
||||||
|
properties = configuration.getProperties();
|
||||||
|
} catch (Exception e){
|
||||||
|
log.warn("Unable to obtain cxf osgi configadmin reference.", e);
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getProp(Dictionary properties, String key, Object defaultValue) {
|
||||||
|
Object value = null;
|
||||||
|
if(properties != null){
|
||||||
|
value = properties.get(key);
|
||||||
|
}
|
||||||
|
return value == null ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceReference getManagedServiceReference() {
|
||||||
|
return managedServiceReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setManagedServiceReference(ServiceReference managedServiceReference) {
|
||||||
|
this.managedServiceReference = managedServiceReference;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak OIDC Client Adapter Modules</name>
|
<name>Keycloak OIDC Client Adapter Modules</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+47
-7
@@ -47,6 +47,7 @@ import java.io.InputStream;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -56,10 +57,18 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
protected AdapterDeploymentContext deploymentContext;
|
protected AdapterDeploymentContext deploymentContext;
|
||||||
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||||
protected NodesRegistrationManagement nodesRegistrationManagement;
|
protected NodesRegistrationManagement nodesRegistrationManagement;
|
||||||
|
protected Pattern skipPattern;
|
||||||
|
|
||||||
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||||
|
|
||||||
|
String skipPatternDefinition = filterConfig.getInitParameter("keycloak.config.skipPattern");
|
||||||
|
if (skipPatternDefinition != null) {
|
||||||
|
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
||||||
|
}
|
||||||
|
|
||||||
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||||
if (configResolverClass != null) {
|
if (configResolverClass != null) {
|
||||||
try {
|
try {
|
||||||
@@ -85,13 +94,7 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
if (pathParam != null) path = pathParam;
|
if (pathParam != null) path = pathParam;
|
||||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||||
}
|
}
|
||||||
KeycloakDeployment kd;
|
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||||
if (is == null) {
|
|
||||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
|
||||||
kd = new KeycloakDeployment();
|
|
||||||
} else {
|
|
||||||
kd = KeycloakDeploymentBuilder.build(is);
|
|
||||||
}
|
|
||||||
deploymentContext = new AdapterDeploymentContext(kd);
|
deploymentContext = new AdapterDeploymentContext(kd);
|
||||||
log.fine("Keycloak is using a per-deployment configuration.");
|
log.fine("Keycloak is using a per-deployment configuration.");
|
||||||
}
|
}
|
||||||
@@ -99,13 +102,30 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
|
||||||
|
|
||||||
|
if (is == null) {
|
||||||
|
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||||
|
return new KeycloakDeployment();
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeycloakDeploymentBuilder.build(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||||
|
|
||||||
log.fine("Keycloak OIDC Filter");
|
log.fine("Keycloak OIDC Filter");
|
||||||
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
|
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
|
||||||
HttpServletRequest request = (HttpServletRequest) req;
|
HttpServletRequest request = (HttpServletRequest) req;
|
||||||
HttpServletResponse response = (HttpServletResponse) res;
|
HttpServletResponse response = (HttpServletResponse) res;
|
||||||
|
|
||||||
|
if (shouldSkip(request)) {
|
||||||
|
chain.doFilter(req, res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
|
||||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||||
if (deployment == null || !deployment.isConfigured()) {
|
if (deployment == null || !deployment.isConfigured()) {
|
||||||
@@ -171,6 +191,26 @@ public class KeycloakOIDCFilter implements Filter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether this {@link Filter} should skip the given {@link HttpServletRequest} based on the configured {@link KeycloakOIDCFilter#skipPattern}.
|
||||||
|
* Patterns are matched against the {@link HttpServletRequest#getRequestURI() requestURI} of a request without the context-path.
|
||||||
|
* A request for {@code /myapp/index.html} would be tested with {@code /index.html} against the skip pattern.
|
||||||
|
* Skipped requests will not be processed further by {@link KeycloakOIDCFilter} and immediately delegated to the {@link FilterChain}.
|
||||||
|
*
|
||||||
|
* @param request the request to check
|
||||||
|
* @return {@code true} if the request should not be handled,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
private boolean shouldSkip(HttpServletRequest request) {
|
||||||
|
|
||||||
|
if (skipPattern == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String requestPath = request.getRequestURI().substring(request.getContextPath().length());
|
||||||
|
return skipPattern.matcher(requestPath).matches();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -19,12 +19,12 @@ package org.keycloak.adapters.servlet;
|
|||||||
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
|
||||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.RequestAuthenticator;
|
import org.keycloak.adapters.RequestAuthenticator;
|
||||||
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
|
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||||
import org.keycloak.adapters.spi.SessionIdMapper;
|
import org.keycloak.adapters.spi.SessionIdMapper;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -51,10 +51,16 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HttpServletRequestWrapper buildWrapper() {
|
public HttpServletRequestWrapper buildWrapper() {
|
||||||
HttpSession session = request.getSession();
|
HttpSession session = request.getSession(false);
|
||||||
KeycloakAccount account = (KeycloakAccount)session.getAttribute(KeycloakAccount.class.getName());
|
KeycloakAccount account = null;
|
||||||
|
if (session != null) {
|
||||||
|
account = (KeycloakAccount) session.getAttribute(KeycloakAccount.class.getName());
|
||||||
|
if (account == null) {
|
||||||
|
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
account = (KeycloakAccount)request.getAttribute(KeycloakAccount.class.getName());
|
account = (KeycloakAccount) request.getAttribute(KeycloakAccount.class.getName());
|
||||||
}
|
}
|
||||||
return buildWrapper(session, account);
|
return buildWrapper(session, account);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+5
-4
@@ -30,6 +30,7 @@ import org.keycloak.jose.jws.JWSInputException;
|
|||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
import javax.security.cert.X509Certificate;
|
import javax.security.cert.X509Certificate;
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
@@ -91,15 +92,15 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
|
|||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
|
||||||
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
|
String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
|
||||||
|
String scopeParam = TokenUtil.attachOIDCScope(scope);
|
||||||
|
|
||||||
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
|
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl)
|
||||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||||
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
|
.queryParam(OAuth2Constants.CLIENT_ID, getClientId())
|
||||||
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
|
||||||
.queryParam(OAuth2Constants.STATE, state);
|
.queryParam(OAuth2Constants.STATE, state)
|
||||||
if (scope != null) {
|
.queryParam(OAuth2Constants.SCOPE, scopeParam);
|
||||||
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
|
||||||
}
|
|
||||||
URI url = uriBuilder.build();
|
URI url = uriBuilder.build();
|
||||||
|
|
||||||
String stateCookiePath = this.stateCookiePath;
|
String stateCookiePath = this.stateCookiePath;
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ public class ServletOAuthClientBuilderTest {
|
|||||||
public void testBuilder() {
|
public void testBuilder() {
|
||||||
ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
|
||||||
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
|
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
|
||||||
Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
|
Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
|
||||||
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
|
assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
|
||||||
Assert.assertEquals("customer-portal", oauthClient.getClientId());
|
Assert.assertEquals("customer-portal", oauthClient.getClientId());
|
||||||
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
|
Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
"connection-pool-size": 20,
|
"connection-pool-size": 20,
|
||||||
"disable-trust-manager": true,
|
"disable-trust-manager": true,
|
||||||
"allow-any-hostname": true,
|
"allow-any-hostname": true,
|
||||||
"auth-server-url-for-backend-requests": "https://backend:8443/auth",
|
|
||||||
"always-refresh-token": true,
|
"always-refresh-token": true,
|
||||||
"register-node-at-startup": true,
|
"register-node-at-startup": true,
|
||||||
"register-node-period": 1000,
|
"register-node-period": 1000,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -67,6 +67,45 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-webapp</artifactId>
|
||||||
|
<version>${jetty9.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.undertow</groupId>
|
||||||
|
<artifactId>undertow-servlet</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.undertow</groupId>
|
||||||
|
<artifactId>undertow-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-undertow-adapter-spi</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|||||||
+208
-62
@@ -17,24 +17,38 @@
|
|||||||
|
|
||||||
package org.keycloak.adapters.springboot;
|
package org.keycloak.adapters.springboot;
|
||||||
|
|
||||||
|
import io.undertow.servlet.api.DeploymentInfo;
|
||||||
|
import io.undertow.servlet.api.WebResourceCollection;
|
||||||
import org.apache.catalina.Context;
|
import org.apache.catalina.Context;
|
||||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||||
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
|
||||||
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
|
||||||
|
import org.eclipse.jetty.security.ConstraintMapping;
|
||||||
|
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
import org.keycloak.adapters.jetty.KeycloakJettyAuthenticator;
|
||||||
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
||||||
|
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||||
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.context.embedded.undertow.UndertowDeploymentInfoCustomizer;
|
||||||
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
|
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,80 +75,212 @@ public class KeycloakSpringBootConfiguration {
|
|||||||
return new EmbeddedServletContainerCustomizer() {
|
return new EmbeddedServletContainerCustomizer() {
|
||||||
@Override
|
@Override
|
||||||
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
|
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {
|
||||||
|
|
||||||
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
|
if (configurableEmbeddedServletContainer instanceof TomcatEmbeddedServletContainerFactory) {
|
||||||
|
|
||||||
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
TomcatEmbeddedServletContainerFactory container = (TomcatEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
|
||||||
container.addContextValves(new KeycloakAuthenticatorValve());
|
container.addContextValves(new KeycloakAuthenticatorValve());
|
||||||
|
container.addContextCustomizers(tomcatKeycloakContextCustomizer());
|
||||||
|
|
||||||
container.addContextCustomizers(getTomcatKeycloakContextCustomizer());
|
|
||||||
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
|
} else if (configurableEmbeddedServletContainer instanceof UndertowEmbeddedServletContainerFactory) {
|
||||||
throw new IllegalArgumentException("Undertow Keycloak integration is not yet implemented");
|
|
||||||
|
UndertowEmbeddedServletContainerFactory container = (UndertowEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
container.addDeploymentInfoCustomizers(undertowKeycloakContextCustomizer());
|
||||||
|
|
||||||
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
|
} else if (configurableEmbeddedServletContainer instanceof JettyEmbeddedServletContainerFactory) {
|
||||||
throw new IllegalArgumentException("Jetty Keycloak integration is not yet implemented");
|
|
||||||
|
JettyEmbeddedServletContainerFactory container = (JettyEmbeddedServletContainerFactory) configurableEmbeddedServletContainer;
|
||||||
|
container.addServerCustomizers(jettyKeycloakServerCustomizer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public TomcatContextCustomizer getTomcatKeycloakContextCustomizer() {
|
@ConditionalOnClass(name = {"org.eclipse.jetty.webapp.WebAppContext"})
|
||||||
return new TomcatContextCustomizer() {
|
public JettyServerCustomizer jettyKeycloakServerCustomizer() {
|
||||||
@Override
|
return new KeycloakJettyServerCustomizer(keycloakProperties);
|
||||||
public void customize(Context context) {
|
|
||||||
LoginConfig loginConfig = new LoginConfig();
|
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
|
||||||
context.setLoginConfig(loginConfig);
|
|
||||||
|
|
||||||
Set<String> authRoles = new HashSet<String>();
|
|
||||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
|
||||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
|
||||||
for (String authRole : collection.getAuthRoles()) {
|
|
||||||
if (!authRoles.contains(authRole)) {
|
|
||||||
context.addSecurityRole(authRole);
|
|
||||||
authRoles.add(authRole);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
|
||||||
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
|
||||||
|
|
||||||
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
|
||||||
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
|
||||||
|
|
||||||
if (collection.getName() != null) {
|
|
||||||
tomcatSecCollection.setName(collection.getName());
|
|
||||||
}
|
|
||||||
if (collection.getDescription() != null) {
|
|
||||||
tomcatSecCollection.setDescription(collection.getDescription());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String authRole : collection.getAuthRoles()) {
|
|
||||||
tomcatConstraint.addAuthRole(authRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String pattern : collection.getPatterns()) {
|
|
||||||
tomcatSecCollection.addPattern(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String method : collection.getMethods()) {
|
|
||||||
tomcatSecCollection.addMethod(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String method : collection.getOmittedMethods()) {
|
|
||||||
tomcatSecCollection.addOmittedMethod(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
tomcatConstraint.addCollection(tomcatSecCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.addConstraint(tomcatConstraint);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})
|
||||||
|
public TomcatContextCustomizer tomcatKeycloakContextCustomizer() {
|
||||||
|
return new KeycloakTomcatContextCustomizer(keycloakProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnClass(name = {"io.undertow.Undertow"})
|
||||||
|
public UndertowDeploymentInfoCustomizer undertowKeycloakContextCustomizer() {
|
||||||
|
return new KeycloakUndertowDeploymentInfoCustomizer(keycloakProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeycloakUndertowDeploymentInfoCustomizer implements UndertowDeploymentInfoCustomizer {
|
||||||
|
|
||||||
|
private final KeycloakSpringBootProperties keycloakProperties;
|
||||||
|
|
||||||
|
public KeycloakUndertowDeploymentInfoCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||||
|
this.keycloakProperties = keycloakProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(DeploymentInfo deploymentInfo) {
|
||||||
|
|
||||||
|
io.undertow.servlet.api.LoginConfig loginConfig = new io.undertow.servlet.api.LoginConfig(keycloakProperties.getRealm());
|
||||||
|
loginConfig.addFirstAuthMethod("KEYCLOAK");
|
||||||
|
|
||||||
|
deploymentInfo.setLoginConfig(loginConfig);
|
||||||
|
|
||||||
|
deploymentInfo.addInitParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||||
|
deploymentInfo.addSecurityConstraints(getSecurityConstraints());
|
||||||
|
|
||||||
|
deploymentInfo.addServletExtension(new KeycloakServletExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<io.undertow.servlet.api.SecurityConstraint> getSecurityConstraints() {
|
||||||
|
|
||||||
|
List<io.undertow.servlet.api.SecurityConstraint> undertowSecurityConstraints = new ArrayList<io.undertow.servlet.api.SecurityConstraint>();
|
||||||
|
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||||
|
|
||||||
|
for (KeycloakSpringBootProperties.SecurityCollection collectionDefinition : constraintDefinition.getSecurityCollections()) {
|
||||||
|
|
||||||
|
io.undertow.servlet.api.SecurityConstraint undertowSecurityConstraint = new io.undertow.servlet.api.SecurityConstraint();
|
||||||
|
undertowSecurityConstraint.addRolesAllowed(collectionDefinition.getAuthRoles());
|
||||||
|
|
||||||
|
WebResourceCollection webResourceCollection = new WebResourceCollection();
|
||||||
|
webResourceCollection.addHttpMethods(collectionDefinition.getMethods());
|
||||||
|
webResourceCollection.addHttpMethodOmissions(collectionDefinition.getOmittedMethods());
|
||||||
|
webResourceCollection.addUrlPatterns(collectionDefinition.getPatterns());
|
||||||
|
|
||||||
|
undertowSecurityConstraint.addWebResourceCollections(webResourceCollection);
|
||||||
|
|
||||||
|
undertowSecurityConstraints.add(undertowSecurityConstraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undertowSecurityConstraints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeycloakJettyServerCustomizer implements JettyServerCustomizer {
|
||||||
|
|
||||||
|
private final KeycloakSpringBootProperties keycloakProperties;
|
||||||
|
|
||||||
|
public KeycloakJettyServerCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||||
|
this.keycloakProperties = keycloakProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Server server) {
|
||||||
|
|
||||||
|
KeycloakJettyAuthenticator keycloakJettyAuthenticator = new KeycloakJettyAuthenticator();
|
||||||
|
keycloakJettyAuthenticator.setConfigResolver(new KeycloakSpringBootConfigResolver());
|
||||||
|
|
||||||
|
List<ConstraintMapping> jettyConstraintMappings = new ArrayList<ConstraintMapping>();
|
||||||
|
for (KeycloakSpringBootProperties.SecurityConstraint constraintDefinition : keycloakProperties.getSecurityConstraints()) {
|
||||||
|
|
||||||
|
for (KeycloakSpringBootProperties.SecurityCollection securityCollectionDefinition : constraintDefinition
|
||||||
|
.getSecurityCollections()) {
|
||||||
|
|
||||||
|
Constraint jettyConstraint = new Constraint();
|
||||||
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
jettyConstraint.setAuthenticate(true);
|
||||||
|
|
||||||
|
if (securityCollectionDefinition.getName() != null) {
|
||||||
|
jettyConstraint.setName(securityCollectionDefinition.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
jettyConstraint.setRoles(securityCollectionDefinition.getAuthRoles().toArray(new String[0]));
|
||||||
|
|
||||||
|
ConstraintMapping jettyConstraintMapping = new ConstraintMapping();
|
||||||
|
if (securityCollectionDefinition.getPatterns().size() > 0) {
|
||||||
|
//First pattern wins
|
||||||
|
jettyConstraintMapping.setPathSpec(securityCollectionDefinition.getPatterns().get(0));
|
||||||
|
jettyConstraintMapping.setConstraint(jettyConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (securityCollectionDefinition.getMethods().size() > 0) {
|
||||||
|
//First method wins
|
||||||
|
jettyConstraintMapping.setMethod(securityCollectionDefinition.getMethods().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
jettyConstraintMapping.setMethodOmissions(
|
||||||
|
securityCollectionDefinition.getOmittedMethods().toArray(new String[0]));
|
||||||
|
|
||||||
|
jettyConstraintMappings.add(jettyConstraintMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebAppContext webAppContext = server.getBean(WebAppContext.class);
|
||||||
|
|
||||||
|
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||||
|
securityHandler.setConstraintMappings(jettyConstraintMappings);
|
||||||
|
securityHandler.setAuthenticator(keycloakJettyAuthenticator);
|
||||||
|
|
||||||
|
webAppContext.setHandler(securityHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeycloakTomcatContextCustomizer implements TomcatContextCustomizer {
|
||||||
|
|
||||||
|
private final KeycloakSpringBootProperties keycloakProperties;
|
||||||
|
|
||||||
|
public KeycloakTomcatContextCustomizer(KeycloakSpringBootProperties keycloakProperties) {
|
||||||
|
this.keycloakProperties = keycloakProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(Context context) {
|
||||||
|
LoginConfig loginConfig = new LoginConfig();
|
||||||
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
|
context.setLoginConfig(loginConfig);
|
||||||
|
|
||||||
|
Set<String> authRoles = new HashSet<String>();
|
||||||
|
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||||
|
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||||
|
for (String authRole : collection.getAuthRoles()) {
|
||||||
|
if (!authRoles.contains(authRole)) {
|
||||||
|
context.addSecurityRole(authRole);
|
||||||
|
authRoles.add(authRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KeycloakSpringBootProperties.SecurityConstraint constraint : keycloakProperties.getSecurityConstraints()) {
|
||||||
|
SecurityConstraint tomcatConstraint = new SecurityConstraint();
|
||||||
|
|
||||||
|
for (KeycloakSpringBootProperties.SecurityCollection collection : constraint.getSecurityCollections()) {
|
||||||
|
SecurityCollection tomcatSecCollection = new SecurityCollection();
|
||||||
|
|
||||||
|
if (collection.getName() != null) {
|
||||||
|
tomcatSecCollection.setName(collection.getName());
|
||||||
|
}
|
||||||
|
if (collection.getDescription() != null) {
|
||||||
|
tomcatSecCollection.setDescription(collection.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String authRole : collection.getAuthRoles()) {
|
||||||
|
tomcatConstraint.addAuthRole(authRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String pattern : collection.getPatterns()) {
|
||||||
|
tomcatSecCollection.addPattern(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String method : collection.getMethods()) {
|
||||||
|
tomcatSecCollection.addMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String method : collection.getOmittedMethods()) {
|
||||||
|
tomcatSecCollection.addOmittedMethod(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
tomcatConstraint.addCollection(tomcatSecCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.addConstraint(tomcatConstraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.addParameter("keycloak.config.resolver", KeycloakSpringBootConfigResolver.class.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -71,6 +71,13 @@
|
|||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<version>${jboss.logging.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
@@ -121,12 +128,6 @@
|
|||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.logging</groupId>
|
|
||||||
<artifactId>jboss-logging</artifactId>
|
|
||||||
<version>${jboss.logging.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
-1
@@ -69,6 +69,5 @@ public class HttpSessionManager implements HttpSessionListener, UserSessionManag
|
|||||||
session.invalidate();
|
session.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sessions.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Tomcat Integration</name>
|
<name>Keycloak Tomcat Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -77,6 +77,13 @@
|
|||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-annotations</artifactId>
|
<artifactId>jackson-annotations</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Authorization -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authz-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.tomcat</groupId>
|
<groupId>org.apache.tomcat</groupId>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+2
-2
@@ -17,16 +17,16 @@
|
|||||||
package org.keycloak.adapters.undertow;
|
package org.keycloak.adapters.undertow;
|
||||||
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.AttachmentKey;
|
|
||||||
import org.keycloak.KeycloakSecurityContext;
|
import org.keycloak.KeycloakSecurityContext;
|
||||||
import org.keycloak.adapters.OIDCHttpFacade;
|
import org.keycloak.adapters.OIDCHttpFacade;
|
||||||
|
|
||||||
|
import static org.keycloak.adapters.undertow.OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
|
public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
|
||||||
public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
|
|
||||||
|
|
||||||
public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) {
|
public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) {
|
||||||
super(exchange);
|
super(exchange);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak WildFly Integration</name>
|
<name>Keycloak WildFly Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+8
-10
@@ -46,8 +46,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
|
|
||||||
// not sure if we need this yet, keeping here just in case
|
// not sure if we need this yet, keeping here just in case
|
||||||
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
|
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
|
||||||
String deploymentName = deploymentUnit.getName();
|
if (!service.isSecureDeployment(deploymentUnit)) {
|
||||||
if (!service.isSecureDeployment(deploymentName)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
@@ -67,10 +66,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
||||||
if (service.isSecureDeployment(deploymentName)) {
|
if (service.isSecureDeployment(deploymentUnit)) {
|
||||||
addKeycloakAuthData(phaseContext, deploymentName, service);
|
addKeycloakAuthData(phaseContext, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
||||||
@@ -79,14 +77,14 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
// addSecurityDomain(deploymentUnit, service);
|
// addSecurityDomain(deploymentUnit, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
|
private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
|
throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentUnit.getName() + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
|
||||||
}
|
}
|
||||||
|
|
||||||
addJSONData(service.getJSON(deploymentName), warMetaData);
|
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
||||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
if (webMetaData == null) {
|
if (webMetaData == null) {
|
||||||
webMetaData = new JBossWebMetaData();
|
webMetaData = new JBossWebMetaData();
|
||||||
@@ -99,8 +97,8 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
webMetaData.setLoginConfig(loginConfig);
|
webMetaData.setLoginConfig(loginConfig);
|
||||||
}
|
}
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
loginConfig.setRealmName(service.getRealmName(deploymentName));
|
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addJSONData(String json, WarMetaData warMetaData) {
|
private void addJSONData(String json, WarMetaData warMetaData) {
|
||||||
|
|||||||
+28
-3
@@ -24,6 +24,9 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
||||||
|
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||||
|
import org.jboss.as.web.common.WarMetaData;
|
||||||
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service keeps track of the entire Keycloak management model so as to provide
|
* This service keeps track of the entire Keycloak management model so as to provide
|
||||||
@@ -153,13 +156,15 @@ public final class KeycloakAdapterConfigService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealmName(String deploymentName) {
|
public String getRealmName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJSON(String deploymentName) {
|
public String getJSON(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
ModelNode realm = this.realms.get(realmName);
|
ModelNode realm = this.realms.get(realmName);
|
||||||
@@ -183,9 +188,29 @@ public final class KeycloakAdapterConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureDeployment(String deploymentName) {
|
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||||
|
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
return this.secureDeployments.containsKey(deploymentName);
|
return this.secureDeployments.containsKey(deploymentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-3273: prefer module name if available
|
||||||
|
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
|
if (warMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
|
if (webMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String moduleName = webMetaData.getModuleName();
|
||||||
|
if (moduleName != null) return moduleName + ".war";
|
||||||
|
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-2
@@ -46,8 +46,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
||||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
-2
@@ -78,8 +78,6 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
|
|||||||
addCredential(addr, service, "secret", "secret1");
|
addCredential(addr, service, "secret", "secret1");
|
||||||
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
||||||
addCredential(addr, service, "jwt.token-timeout", "10");
|
addCredential(addr, service, "jwt.token-timeout", "10");
|
||||||
|
|
||||||
System.out.println("Deployment: " + service.getJSON("foo"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
|
private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../../pom.xml</relativePath>
|
<relativePath>../../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+8
-10
@@ -46,8 +46,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
|
|
||||||
// not sure if we need this yet, keeping here just in case
|
// not sure if we need this yet, keeping here just in case
|
||||||
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
|
protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
|
||||||
String deploymentName = deploymentUnit.getName();
|
if (!service.isSecureDeployment(deploymentUnit)) {
|
||||||
if (!service.isSecureDeployment(deploymentName)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
@@ -67,10 +66,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
|
||||||
if (service.isSecureDeployment(deploymentName)) {
|
if (service.isSecureDeployment(deploymentUnit)) {
|
||||||
addKeycloakAuthData(phaseContext, deploymentName, service);
|
addKeycloakAuthData(phaseContext, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
// FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
|
||||||
@@ -79,14 +77,14 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
// addSecurityDomain(deploymentUnit, service);
|
// addSecurityDomain(deploymentUnit, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
|
private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
|
throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentUnit.getName() + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
|
||||||
}
|
}
|
||||||
|
|
||||||
addJSONData(service.getJSON(deploymentName), warMetaData);
|
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
||||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
if (webMetaData == null) {
|
if (webMetaData == null) {
|
||||||
webMetaData = new JBossWebMetaData();
|
webMetaData = new JBossWebMetaData();
|
||||||
@@ -99,8 +97,8 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
webMetaData.setLoginConfig(loginConfig);
|
webMetaData.setLoginConfig(loginConfig);
|
||||||
}
|
}
|
||||||
loginConfig.setAuthMethod("KEYCLOAK");
|
loginConfig.setAuthMethod("KEYCLOAK");
|
||||||
loginConfig.setRealmName(service.getRealmName(deploymentName));
|
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addJSONData(String json, WarMetaData warMetaData) {
|
private void addJSONData(String json, WarMetaData warMetaData) {
|
||||||
|
|||||||
+28
-3
@@ -24,6 +24,9 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
|
||||||
|
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||||
|
import org.jboss.as.web.common.WarMetaData;
|
||||||
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service keeps track of the entire Keycloak management model so as to provide
|
* This service keeps track of the entire Keycloak management model so as to provide
|
||||||
@@ -153,13 +156,15 @@ public final class KeycloakAdapterConfigService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRealmName(String deploymentName) {
|
public String getRealmName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getJSON(String deploymentName) {
|
public String getJSON(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
ModelNode deployment = this.secureDeployments.get(deploymentName);
|
||||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||||
ModelNode realm = this.realms.get(realmName);
|
ModelNode realm = this.realms.get(realmName);
|
||||||
@@ -183,9 +188,29 @@ public final class KeycloakAdapterConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureDeployment(String deploymentName) {
|
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||||
|
|
||||||
|
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||||
return this.secureDeployments.containsKey(deploymentName);
|
return this.secureDeployments.containsKey(deploymentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-3273: prefer module name if available
|
||||||
|
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
|
if (warMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
|
if (webMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String moduleName = webMetaData.getModuleName();
|
||||||
|
if (moduleName != null) return moduleName + ".war";
|
||||||
|
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-4
@@ -45,9 +45,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
return;
|
return;
|
||||||
@@ -67,7 +65,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
addCommonModules(moduleSpecification, moduleLoader);
|
addCommonModules(moduleSpecification, moduleLoader);
|
||||||
addPlatformSpecificModules(moduleSpecification, moduleLoader);
|
addPlatformSpecificModules(moduleSpecification, moduleLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
|
private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
|
||||||
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
|
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
||||||
|
|||||||
-2
@@ -78,8 +78,6 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
|
|||||||
addCredential(addr, service, "secret", "secret1");
|
addCredential(addr, service, "secret", "secret1");
|
||||||
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
|
||||||
addCredential(addr, service, "jwt.token-timeout", "10");
|
addCredential(addr, service, "jwt.token-timeout", "10");
|
||||||
|
|
||||||
System.out.println("Deployment: " + service.getJSON("foo"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
|
private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak Integration</name>
|
<name>Keycloak Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -43,6 +43,10 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-spi</artifactId>
|
<artifactId>keycloak-adapter-spi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-saml-adapter-api-public</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-adapter-core</artifactId>
|
<artifactId>keycloak-saml-adapter-core</artifactId>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../../../pom.xml</relativePath>
|
<relativePath>../../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Keycloak SAML EAP Integration</name>
|
<name>Keycloak SAML EAP Integration</name>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
|
||||||
<version>1.9.2.Final-SNAPSHOT</version>
|
<version>2.2.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|||||||
+26
-3
@@ -16,8 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.keycloak.subsystem.saml.as7;
|
package org.keycloak.subsystem.saml.as7;
|
||||||
|
|
||||||
|
import org.jboss.as.server.deployment.DeploymentUnit;
|
||||||
|
import org.jboss.as.web.deployment.WarMetaData;
|
||||||
import org.jboss.dmr.ModelNode;
|
import org.jboss.dmr.ModelNode;
|
||||||
import org.jboss.dmr.Property;
|
import org.jboss.dmr.Property;
|
||||||
|
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
@@ -46,7 +49,8 @@ public class Configuration {
|
|||||||
return keymodel.get(key);
|
return keymodel.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModelNode getSecureDeployment(String name) {
|
public ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
|
String name = preferredDeploymentName(deploymentUnit);
|
||||||
ModelNode secureDeployment = config.get("subsystem").get("keycloak-saml").get(Constants.Model.SECURE_DEPLOYMENT);
|
ModelNode secureDeployment = config.get("subsystem").get("keycloak-saml").get(Constants.Model.SECURE_DEPLOYMENT);
|
||||||
if (secureDeployment.hasDefined(name)) {
|
if (secureDeployment.hasDefined(name)) {
|
||||||
return secureDeployment.get(name);
|
return secureDeployment.get(name);
|
||||||
@@ -54,7 +58,26 @@ public class Configuration {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSecureDeployment(String name) {
|
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||||
return getSecureDeployment(name) != null;
|
return getSecureDeployment(deploymentUnit) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-3273: prefer module name if available
|
||||||
|
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||||
|
String deploymentName = deploymentUnit.getName();
|
||||||
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
|
if (warMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||||
|
if (webMetaData == null) {
|
||||||
|
return deploymentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String moduleName = webMetaData.getModuleName();
|
||||||
|
if (moduleName != null) return moduleName + ".war";
|
||||||
|
|
||||||
|
return deploymentName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
@@ -45,6 +45,7 @@ public class Constants {
|
|||||||
static final String KEY_STORE = "KeyStore";
|
static final String KEY_STORE = "KeyStore";
|
||||||
static final String SIGN_REQUEST = "signRequest";
|
static final String SIGN_REQUEST = "signRequest";
|
||||||
static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
|
static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
|
||||||
|
static final String VALIDATE_ASSERTION_SIGNATURE = "validateAssertionSignature";
|
||||||
static final String REQUEST_BINDING = "requestBinding";
|
static final String REQUEST_BINDING = "requestBinding";
|
||||||
static final String BINDING_URL = "bindingUrl";
|
static final String BINDING_URL = "bindingUrl";
|
||||||
static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
|
static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
|
||||||
@@ -97,6 +98,7 @@ public class Constants {
|
|||||||
static final String CERTIFICATE_ALIAS = "alias";
|
static final String CERTIFICATE_ALIAS = "alias";
|
||||||
static final String SIGN_REQUEST = "signRequest";
|
static final String SIGN_REQUEST = "signRequest";
|
||||||
static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
|
static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
|
||||||
|
static final String VALIDATE_ASSERTION_SIGNATURE = "validateAssertionSignature";
|
||||||
static final String REQUEST_BINDING = "requestBinding";
|
static final String REQUEST_BINDING = "requestBinding";
|
||||||
static final String BINDING_URL = "bindingUrl";
|
static final String BINDING_URL = "bindingUrl";
|
||||||
static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
|
static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
|
||||||
|
|||||||
+7
-8
@@ -51,7 +51,6 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
String deploymentName = deploymentUnit.getName();
|
|
||||||
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
@@ -69,30 +68,30 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
boolean webRequiresKC = loginConfig != null && "KEYCLOAK-SAML".equalsIgnoreCase(loginConfig.getAuthMethod());
|
boolean webRequiresKC = loginConfig != null && "KEYCLOAK-SAML".equalsIgnoreCase(loginConfig.getAuthMethod());
|
||||||
boolean hasSubsystemConfig = Configuration.INSTANCE.isSecureDeployment(deploymentName);
|
boolean hasSubsystemConfig = Configuration.INSTANCE.isSecureDeployment(deploymentUnit);
|
||||||
if (hasSubsystemConfig || webRequiresKC) {
|
if (hasSubsystemConfig || webRequiresKC) {
|
||||||
log.debug("Setting up KEYCLOAK-SAML auth method for WAR: " + deploymentName);
|
log.debug("Setting up KEYCLOAK-SAML auth method for WAR: " + deploymentUnit.getName());
|
||||||
|
|
||||||
// if secure-deployment configuration exists for web app, we force KEYCLOAK-SAML auth method on it
|
// if secure-deployment configuration exists for web app, we force KEYCLOAK-SAML auth method on it
|
||||||
if (hasSubsystemConfig) {
|
if (hasSubsystemConfig) {
|
||||||
addXMLData(getXML(deploymentName), warMetaData);
|
addXMLData(getXML(deploymentUnit), warMetaData);
|
||||||
if (loginConfig != null) {
|
if (loginConfig != null) {
|
||||||
loginConfig.setAuthMethod("KEYCLOAK-SAML");
|
loginConfig.setAuthMethod("KEYCLOAK-SAML");
|
||||||
//loginConfig.setRealmName(service.getRealmName(deploymentName));
|
//loginConfig.setRealmName(service.getRealmName(deploymentName));
|
||||||
} else {
|
} else {
|
||||||
log.warn("Failed to set up KEYCLOAK-SAML auth method for WAR: " + deploymentName + " (loginConfig == null)");
|
log.warn("Failed to set up KEYCLOAK-SAML auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addValve(webMetaData);
|
addValve(webMetaData);
|
||||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
|
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new DeploymentUnitProcessingException("Failed to configure KeycloakSamlExtension from subsystem model", e);
|
throw new DeploymentUnitProcessingException("Failed to configure KeycloakSamlExtension from subsystem model", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getXML(String deploymentName) throws XMLStreamException {
|
private String getXML(DeploymentUnit deploymentUnit) throws XMLStreamException {
|
||||||
ModelNode node = Configuration.INSTANCE.getSecureDeployment(deploymentName);
|
ModelNode node = Configuration.INSTANCE.getSecureDeployment(deploymentUnit);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
KeycloakSubsystemParser writer = new KeycloakSubsystemParser();
|
KeycloakSubsystemParser writer = new KeycloakSubsystemParser();
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
|||||||
+3
-2
@@ -39,14 +39,14 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
|
|
||||||
private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
|
private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
|
||||||
private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-adapter-core");
|
private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-adapter-core");
|
||||||
|
private static final ModuleIdentifier KEYCLOAK_API_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-adapter-api-public");
|
||||||
private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
|
private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
|
||||||
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
|
||||||
|
|
||||||
String deploymentName = deploymentUnit.getName();
|
if (Configuration.INSTANCE.getSecureDeployment(deploymentUnit) == null) {
|
||||||
if (Configuration.INSTANCE.getSecureDeployment(deploymentName) == null) {
|
|
||||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||||
if (warMetaData == null) {
|
if (warMetaData == null) {
|
||||||
return;
|
return;
|
||||||
@@ -71,6 +71,7 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
|
|||||||
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
|
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
|
||||||
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_API_ADAPTER, false, false, false, false));
|
||||||
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
|
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -37,6 +37,11 @@ abstract class SingleSignOnDefinition {
|
|||||||
.setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
|
.setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
static final SimpleAttributeDefinition VALIDATE_ASSERTION_SIGNATURE =
|
||||||
|
new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_ASSERTION_SIGNATURE, ModelType.BOOLEAN, true)
|
||||||
|
.setXmlName(Constants.XML.VALIDATE_ASSERTION_SIGNATURE)
|
||||||
|
.build();
|
||||||
|
|
||||||
static final SimpleAttributeDefinition REQUEST_BINDING =
|
static final SimpleAttributeDefinition REQUEST_BINDING =
|
||||||
new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
|
new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
|
||||||
.setXmlName(Constants.XML.REQUEST_BINDING)
|
.setXmlName(Constants.XML.REQUEST_BINDING)
|
||||||
@@ -52,7 +57,7 @@ abstract class SingleSignOnDefinition {
|
|||||||
.setXmlName(Constants.XML.BINDING_URL)
|
.setXmlName(Constants.XML.BINDING_URL)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGN_REQUEST, VALIDATE_RESPONSE_SIGNATURE, REQUEST_BINDING, RESPONSE_BINDING, BINDING_URL};
|
static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGN_REQUEST, VALIDATE_RESPONSE_SIGNATURE, VALIDATE_ASSERTION_SIGNATURE, REQUEST_BINDING, RESPONSE_BINDING, BINDING_URL};
|
||||||
|
|
||||||
static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
|
static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
|||||||
+1
@@ -67,6 +67,7 @@ keycloak-saml.IDP.signatureCanonicalizationMethod=Signature canonicalization met
|
|||||||
keycloak-saml.IDP.SingleSignOnService=Single sign-on configuration
|
keycloak-saml.IDP.SingleSignOnService=Single sign-on configuration
|
||||||
keycloak-saml.IDP.SingleSignOnService.signRequest=Sign SSO requests
|
keycloak-saml.IDP.SingleSignOnService.signRequest=Sign SSO requests
|
||||||
keycloak-saml.IDP.SingleSignOnService.validateResponseSignature=Validate an SSO response signature
|
keycloak-saml.IDP.SingleSignOnService.validateResponseSignature=Validate an SSO response signature
|
||||||
|
keycloak-saml.IDP.SingleSignOnService.validateAssertionSignature=Validate an SSO assertion signature
|
||||||
keycloak-saml.IDP.SingleSignOnService.requestBinding=HTTP method to use for requests
|
keycloak-saml.IDP.SingleSignOnService.requestBinding=HTTP method to use for requests
|
||||||
keycloak-saml.IDP.SingleSignOnService.responseBinding=HTTP method to use for responses
|
keycloak-saml.IDP.SingleSignOnService.responseBinding=HTTP method to use for responses
|
||||||
keycloak-saml.IDP.SingleSignOnService.bindingUrl=SSO endpoint URL
|
keycloak-saml.IDP.SingleSignOnService.bindingUrl=SSO endpoint URL
|
||||||
|
|||||||
+5
@@ -132,6 +132,11 @@
|
|||||||
<xs:documentation>Validate the SSO response signature</xs:documentation>
|
<xs:documentation>Validate the SSO response signature</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Validate the SSO assertion signature</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
<xs:attribute name="requestBinding" type="xs:string" use="optional">
|
<xs:attribute name="requestBinding" type="xs:string" use="optional">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>HTTP method to use for requests</xs:documentation>
|
<xs:documentation>HTTP method to use for requests</xs:documentation>
|
||||||
|
|||||||
+1
@@ -42,6 +42,7 @@
|
|||||||
<IDP entityID="idp">
|
<IDP entityID="idp">
|
||||||
<SingleSignOnService signRequest="true"
|
<SingleSignOnService signRequest="true"
|
||||||
validateResponseSignature="true"
|
validateResponseSignature="true"
|
||||||
|
validateAssertionSignature="true"
|
||||||
requestBinding="POST"
|
requestBinding="POST"
|
||||||
bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
|
bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
|
||||||
<SingleLogoutService
|
<SingleLogoutService
|
||||||
|
|||||||
Loaded 100 of 3156 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user