Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81dc436bcd | ||
|
|
2fc0c1a59c | ||
|
|
1738429e10 | ||
|
|
96e188bcae | ||
|
|
952b2fc83d | ||
|
|
15dfb6180c | ||
|
|
fc18190c5d | ||
|
|
10ced1e908 | ||
|
|
1d56805faf | ||
|
|
41bf97ff40 | ||
|
|
839ce470c2 | ||
|
|
5f0b3ae019 | ||
|
|
bdbb1b891a | ||
|
|
48c7bd1b5e | ||
|
|
41828cd865 | ||
|
|
ded9a9f98c | ||
|
|
42541d74b5 | ||
|
|
a5567ead7a | ||
|
|
58068946e4 | ||
|
|
a2c4a1f760 | ||
|
|
fabb34445f | ||
|
|
a95d36eeb5 | ||
|
|
f9d814598e | ||
|
|
abf8aea3f6 | ||
|
|
03bfca5e41 | ||
|
|
ecb6b2f7e9 | ||
|
|
89f2f64042 | ||
|
|
6e39c3d2f7 | ||
|
|
e3754c472b | ||
|
|
2d7e861086 | ||
|
|
799c9da1c1 | ||
|
|
04773f6aaf | ||
|
|
4c1f2445e3 | ||
|
|
783f11e6b8 | ||
|
|
3256337614 | ||
|
|
969b1bf33c | ||
|
|
fcc2e0a9f0 | ||
|
|
4a650e5417 | ||
|
|
0077741a74 | ||
|
|
7d320392f2 | ||
|
|
ade3b4eb75 | ||
|
|
96b48877b8 | ||
|
|
13639a1bcc | ||
|
|
76309c1dcb | ||
|
|
99b67dfd6c | ||
|
|
b777534411 | ||
|
|
f62f0d4877 | ||
|
|
96dad77c66 | ||
|
|
962b623b4a | ||
|
|
01094663bf | ||
|
|
31a80a973c | ||
|
|
f8b25cad7f | ||
|
|
7b820a4eed | ||
|
|
2c28223115 | ||
|
|
e464947abe | ||
|
|
878ce0761d | ||
|
|
8727aef647 | ||
|
|
48050de1a6 | ||
|
|
ff11a377da | ||
|
|
3c1d8d56e5 | ||
|
|
58db94209a | ||
|
|
2c8c741a0b | ||
|
|
37e732c64d | ||
|
|
be13b88d24 | ||
|
|
b5d5f340ba | ||
|
|
3b15da2fdd | ||
|
|
f6d3e76b67 | ||
|
|
019766fba1 | ||
|
|
9d7db174e1 | ||
|
|
c20ab4a9a6 | ||
|
|
62fddda24d | ||
|
|
b82be5ca29 | ||
|
|
cd1315e839 | ||
|
|
16cb3c31c0 | ||
|
|
720ea7bd76 | ||
|
|
b727087f04 | ||
|
|
d36ee0d0af | ||
|
|
daae3045ae | ||
|
|
5566ad008f | ||
|
|
f482e16998 | ||
|
|
477d8b35e3 | ||
|
|
2599e77491 | ||
|
|
bb6cf95948 | ||
|
|
f64d84b03a | ||
|
|
e0c70e1c47 | ||
|
|
13268c52c6 | ||
|
|
14683882e8 | ||
|
|
82dd3cf8a4 | ||
|
|
88a90111bb | ||
|
|
eb85e6d7c4 | ||
|
|
71de12b446 | ||
|
|
cc784ecb46 | ||
|
|
d1bfb26ebf | ||
|
|
4a0f6eca11 | ||
|
|
be95646061 | ||
|
|
e9aac561d3 | ||
|
|
ee742be811 | ||
|
|
b10b871c3c | ||
|
|
c134ebc0c0 | ||
|
|
4681148b1a | ||
|
|
c3859fa34f | ||
|
|
5a7cefb198 | ||
|
|
410953fc5f | ||
|
|
19c6fe368f | ||
|
|
c2fe63db63 | ||
|
|
a474051869 | ||
|
|
b8b0ecc419 | ||
|
|
f2485bf56b | ||
|
|
12ed875154 | ||
|
|
8d69ef95f5 | ||
|
|
fe8395bff7 | ||
|
|
339a312d69 | ||
|
|
cd748e4482 | ||
|
|
1dadfc9726 | ||
|
|
a3073db6c0 | ||
|
|
97d7fd65c2 | ||
|
|
e35afb66c1 | ||
|
|
c21d110b4c | ||
|
|
acef322ea3 | ||
|
|
f625b92cb2 | ||
|
|
f546358d66 | ||
|
|
1700cb149b | ||
|
|
97d5f4aafc | ||
|
|
06e02e8026 | ||
|
|
90c815055d | ||
|
|
abb22296b7 | ||
|
|
4494c2a731 | ||
|
|
bf9830dae8 | ||
|
|
404faeeaed | ||
|
|
8ed1c475e4 | ||
|
|
4109371b56 | ||
|
|
06a554068f | ||
|
|
dd2a5df654 | ||
|
|
06a45170ef | ||
|
|
f46784380f | ||
|
|
d44cc8639d | ||
|
|
545521137d | ||
|
|
1714ce91d1 | ||
|
|
3efb5d2650 | ||
|
|
93f9706297 | ||
|
|
1d4e8118f0 | ||
|
|
71c7d8e48b | ||
|
|
6e967ab261 | ||
|
|
cd2172df4a | ||
|
|
d8193f0f25 | ||
|
|
de4c05e484 | ||
|
|
a65bac7751 | ||
|
|
82e290e06c | ||
|
|
c0cf537654 | ||
|
|
e1188e0c68 | ||
|
|
f18c0af49c | ||
|
|
8a38597b3a | ||
|
|
0c16bdb31d | ||
|
|
b26277a17c | ||
|
|
8f90509a3c | ||
|
|
3912c60ebc | ||
|
|
cc5efc629d | ||
|
|
b8d5109744 | ||
|
|
24f18609dd | ||
|
|
8c4f45fd38 | ||
|
|
aeb27ff047 | ||
|
|
48adefdbea | ||
|
|
cc0231e5aa | ||
|
|
ce2c4188fb | ||
|
|
68b88b4baf | ||
|
|
ecadc92f40 | ||
|
|
13b22d6644 | ||
|
|
b6b4731f9e | ||
|
|
d3e75084de | ||
|
|
c650a8d52d | ||
|
|
b4af5c4a1e | ||
|
|
2d785b5d5e | ||
|
|
2f6ae96aec | ||
|
|
4a8f34f27f | ||
|
|
adb5f306c2 | ||
|
|
7c72781cb1 | ||
|
|
de2aa8e676 | ||
|
|
b22cd6ddd7 | ||
|
|
4da566ae4d | ||
|
|
1715be2308 | ||
|
|
321041fccf | ||
|
|
90c4185ee1 | ||
|
|
224066f0fd | ||
|
|
a3c3a837ad | ||
|
|
5fe0c38498 | ||
|
|
f13f0730db | ||
|
|
cb17db50c1 | ||
|
|
05fd3032ec | ||
|
|
ec1ba40f4a | ||
|
|
7ab0597989 | ||
|
|
e2b02d414f | ||
|
|
3d05f31202 | ||
|
|
144ee2c56d | ||
|
|
60ecf99aa6 | ||
|
|
7f879fcf62 | ||
|
|
f732332281 | ||
|
|
9b74393add | ||
|
|
d28f3ebaf9 | ||
|
|
7d2d3230d2 | ||
|
|
29030a6d39 | ||
|
|
84eed9ffc6 | ||
|
|
a64ce244d5 | ||
|
|
fbfe552b54 | ||
|
|
444cdac9f5 | ||
|
|
4b48988a73 | ||
|
|
d8b34b68ec | ||
|
|
6dd2959a38 | ||
|
|
9b5b9e3275 | ||
|
|
cc98a3dae0 | ||
|
|
0f23e1e31f | ||
|
|
eeb47f9374 | ||
|
|
7d2a6237b0 | ||
|
|
5b653b4e3e | ||
|
|
43aa61ace3 | ||
|
|
7fce4f1dd9 | ||
|
|
dec8d33af1 | ||
|
|
c8c0449124 | ||
|
|
21b4f9a2f1 | ||
|
|
aad1f3707e | ||
|
|
d296d480e9 | ||
|
|
ffbb2df1f3 | ||
|
|
f8010c21c9 | ||
|
|
c2f2c5ccb6 | ||
|
|
0e0181ced4 | ||
|
|
9984caa2fe | ||
|
|
3af3dd78e5 | ||
|
|
6450c4c5f3 | ||
|
|
f64da6faa0 | ||
|
|
4cd3827976 | ||
|
|
451ce64907 | ||
|
|
a49a2f1dc4 | ||
|
|
b117409531 | ||
|
|
81f88539ff | ||
|
|
90c9d29a45 | ||
|
|
453d29f188 | ||
|
|
f6a2c23314 | ||
|
|
b79160722c | ||
|
|
cb4c2cc5e3 | ||
|
|
b1d341155d | ||
|
|
e0f670768f | ||
|
|
72b3db2322 | ||
|
|
bf27c22dd5 | ||
|
|
28a5e61dff | ||
|
|
5cf64546c8 | ||
|
|
cdf22669f4 | ||
|
|
60313a310c | ||
|
|
d4468913dd | ||
|
|
3bc1d2e124 | ||
|
|
452931bfb8 | ||
|
|
d2188a3f55 | ||
|
|
ce33ce2485 | ||
|
|
6874933724 | ||
|
|
2f64b6aaad | ||
|
|
7ba0681731 | ||
|
|
7bfd55c0de | ||
|
|
34cabf5bdb | ||
|
|
5c6c30fef4 | ||
|
|
868c54b614 | ||
|
|
1de285b724 | ||
|
|
4a4158a4e4 | ||
|
|
ff231f0d19 | ||
|
|
e7cc1c3e58 | ||
|
|
b6b16c62f5 | ||
|
|
db07d79009 | ||
|
|
8cf31bd3f1 | ||
|
|
2b78a3bf4d | ||
|
|
1f5fedb0b0 | ||
|
|
db2c01caa9 | ||
|
|
42eed8145c | ||
|
|
558277f66d | ||
|
|
06c8bf0964 | ||
|
|
4cb3d51781 | ||
|
|
8dea0d50ee | ||
|
|
6cfbd59d80 | ||
|
|
7c445cf983 | ||
|
|
98831ec05a | ||
|
|
453ef808cc | ||
|
|
049dafd3c9 | ||
|
|
51e2507f5d | ||
|
|
94c778f22f | ||
|
|
94980fff29 | ||
|
|
33520e9fec | ||
|
|
226b75e1c5 | ||
|
|
5a37754b62 | ||
|
|
fb745d1dae | ||
|
|
6c7f35c509 | ||
|
|
ad799058a6 | ||
|
|
3d1db7dd91 | ||
|
|
f907a749aa | ||
|
|
d7860420d6 | ||
|
|
b51766e86e | ||
|
|
396e0c6171 | ||
|
|
aee47a80bd | ||
|
|
4430d8efac | ||
|
|
608185f06a | ||
|
|
d74214cf56 | ||
|
|
634ae972c6 | ||
|
|
0559c1df06 | ||
|
|
753feae49e | ||
|
|
eb43d0b779 | ||
|
|
633696ab7d | ||
|
|
5c30093554 | ||
|
|
7dffe82118 | ||
|
|
5e12ee3e7a | ||
|
|
6f88d50d4d | ||
|
|
d1fbe1fd30 | ||
|
|
4741a9a91e | ||
|
|
58a7e549b2 | ||
|
|
80553e0dd8 | ||
|
|
4361ce1d42 | ||
|
|
1684f00922 | ||
|
|
f54da90d71 | ||
|
|
2da925505e | ||
|
|
42b3bc1eea | ||
|
|
2066a7ac89 | ||
|
|
5a7ea3ba5d | ||
|
|
9dd9c9b37f | ||
|
|
724789e114 | ||
|
|
07b038e126 | ||
|
|
ccb3a618b1 | ||
|
|
8533d49dfb | ||
|
|
1c6e90c4ef | ||
|
|
7ea5d411d7 | ||
|
|
ea98d26683 | ||
|
|
aaf3c18a1b | ||
|
|
663560a185 | ||
|
|
b45d6b896a | ||
|
|
cd39d5292a | ||
|
|
17653e7e74 | ||
|
|
69da51cc56 | ||
|
|
8f263a43e3 | ||
|
|
86b5f1b66c | ||
|
|
bccffadc7c | ||
|
|
6e38964838 | ||
|
|
21992ddd31 | ||
|
|
0c893b6175 | ||
|
|
8d4d497063 | ||
|
|
430daf4c39 | ||
|
|
9f759ed77c | ||
|
|
195a5dffdc | ||
|
|
b8e712e10b | ||
|
|
128cd98ce2 | ||
|
|
5a10bbc8ca | ||
|
|
12dc159326 | ||
|
|
b6a512b494 | ||
|
|
1704a6c643 | ||
|
|
c20ad93807 | ||
|
|
e0da63c985 | ||
|
|
427d840441 | ||
|
|
76d154b9ab | ||
|
|
55190d7808 | ||
|
|
0ad3c72c0a | ||
|
|
40a865bf9f | ||
|
|
6c5f35720a | ||
|
|
100dbd5fd7 | ||
|
|
1a43309333 | ||
|
|
f83f76b143 | ||
|
|
d06b7a47ac | ||
|
|
1dde3e1459 | ||
|
|
175b397eb2 | ||
|
|
4b6b48dc34 | ||
|
|
9f8b59dfb6 | ||
|
|
9dd5ecd464 | ||
|
|
58299d3373 | ||
|
|
169e3a50be | ||
|
|
e9f0b8999e | ||
|
|
1ee68bca5b | ||
|
|
69582ee3be | ||
|
|
463e33e232 | ||
|
|
2d56c271af | ||
|
|
4ed2383f72 | ||
|
|
e61ed33469 | ||
|
|
6d224745b2 | ||
|
|
42ffe21f91 | ||
|
|
20f12ea960 | ||
|
|
ef054670eb | ||
|
|
beb3d80afa | ||
|
|
8aa19fe8eb | ||
|
|
d7b48f9ae2 | ||
|
|
d8024b0c6c | ||
|
|
7e4aa09efc | ||
|
|
9541d22ac5 | ||
|
|
bb027a3123 | ||
|
|
8f5010e71a | ||
|
|
4795059e15 | ||
|
|
5da05aa62a | ||
|
|
6097ecb53f | ||
|
|
ebd9bae1ee | ||
|
|
2fb97f058b | ||
|
|
1b4b7ace8b | ||
|
|
48b115a4bb | ||
|
|
955a615a22 | ||
|
|
8586a8d1a5 | ||
|
|
c09b17c2e2 | ||
|
|
45375a9625 | ||
|
|
3ef243dd42 | ||
|
|
205f7c5d6b | ||
|
|
2b7c7f63b6 | ||
|
|
b0fb737de3 | ||
|
|
7df5a832c9 | ||
|
|
ed286ba533 | ||
|
|
4a1397afd8 | ||
|
|
9b053e6918 | ||
|
|
889fe8dd8c | ||
|
|
3ff423c3ad | ||
|
|
a5c1e8e27b | ||
|
|
eed54d6198 | ||
|
|
e63900dac9 | ||
|
|
534187f4d7 | ||
|
|
37a8e295bd | ||
|
|
03d607b022 | ||
|
|
4b637036ac | ||
|
|
07bda93b81 | ||
|
|
c7560cec71 | ||
|
|
40f2fb91bc | ||
|
|
988dcfc83d | ||
|
|
36282d24b5 | ||
|
|
0e9fcf19e0 | ||
|
|
35628174b0 | ||
|
|
f582250b31 | ||
|
|
32aa6cdf9b | ||
|
|
49c3a6308e | ||
|
|
c3d7ef8066 | ||
|
|
0b67437fa7 | ||
|
|
90496f62ac | ||
|
|
d59a28f1cb | ||
|
|
b2c0b90d07 | ||
|
|
0c2795cf7c | ||
|
|
d1c4d9795b | ||
|
|
d523d7f202 | ||
|
|
988832e388 | ||
|
|
735b031f26 | ||
|
|
e84ae5e68d | ||
|
|
02e9c87fa4 | ||
|
|
36079fc6e6 | ||
|
|
ff1f10d7a7 | ||
|
|
98c75f162a | ||
|
|
fc4fee1253 | ||
|
|
5a9fcff0fd | ||
|
|
cb72fa4788 | ||
|
|
916ba57c40 | ||
|
|
4ce2e76a2d | ||
|
|
98d8de00d4 | ||
|
|
2c56daf6fd | ||
|
|
670ef4f820 | ||
|
|
d6c49c4452 | ||
|
|
1e13254598 | ||
|
|
e11ebe9971 | ||
|
|
4b1ba704ec | ||
|
|
9b6254399d | ||
|
|
47f8f44347 | ||
|
|
dae6055d85 | ||
|
|
8b4e724aeb | ||
|
|
9a9f2a17a5 | ||
|
|
2a662ad2a6 | ||
|
|
716d7f6606 | ||
|
|
c94e868c63 | ||
|
|
27d4a33226 | ||
|
|
56579fe0f6 | ||
|
|
d44f9446a2 | ||
|
|
9a1e080025 | ||
|
|
30a36498c9 | ||
|
|
c9d7ed5df6 | ||
|
|
1d8ebd441d | ||
|
|
31f399e52a | ||
|
|
0fccac1c68 | ||
|
|
1375d50a47 | ||
|
|
fcc7afcd26 | ||
|
|
e452165c4a | ||
|
|
99a457c5c1 | ||
|
|
4dfb4a91ea | ||
|
|
34fc92c0d3 | ||
|
|
8c2ffeb601 | ||
|
|
e2998a09b6 | ||
|
|
2d6e1048ef | ||
|
|
0e344e6461 | ||
|
|
5fae76c7e4 | ||
|
|
8fc6368202 | ||
|
|
f013796324 | ||
|
|
1960c608cd | ||
|
|
b41be304e0 | ||
|
|
fd20a25100 | ||
|
|
ea3bba98aa | ||
|
|
6400e9b978 | ||
|
|
1a913b21c1 | ||
|
|
6ecd9fbde7 | ||
|
|
eb695f12f7 | ||
|
|
c5bfaa6051 | ||
|
|
67ba1de56f | ||
|
|
27956d6d2e | ||
|
|
2065815627 | ||
|
|
15feb39ecc | ||
|
|
594cc23a30 | ||
|
|
073b63de9e | ||
|
|
920249b3e8 | ||
|
|
a36c651b10 | ||
|
|
7a7ca59685 | ||
|
|
54c9efaf0d | ||
|
|
c2ba2a70ce | ||
|
|
ee4fbca868 | ||
|
|
71b36ceb07 | ||
|
|
edecda8599 | ||
|
|
31de0112d8 | ||
|
|
eaae4eb986 | ||
|
|
3631635487 | ||
|
|
a634f1a65f | ||
|
|
72b4790c4f | ||
|
|
7be2c9d15c | ||
|
|
d1f38a4236 | ||
|
|
b9a7594113 | ||
|
|
da240b2b36 | ||
|
|
efb6ec8099 | ||
|
|
f31bece1b9 | ||
|
|
e7a792f922 | ||
|
|
715482e371 | ||
|
|
10c9b9b0b9 | ||
|
|
f454e5ae12 | ||
|
|
c5303df4fc | ||
|
|
24de9e932f | ||
|
|
6088fddf94 | ||
|
|
c8d879a82d | ||
|
|
f592e8b499 | ||
|
|
1119bb5b8b | ||
|
|
40ec9da797 | ||
|
|
c0a928e6d0 | ||
|
|
981d50d082 | ||
|
|
42bdb7731d | ||
|
|
8b6a3ad8e4 | ||
|
|
a0ba6d2c16 | ||
|
|
ef2698936b | ||
|
|
4f432775ed | ||
|
|
7be7001f4b | ||
|
|
2e04ac549e | ||
|
|
7483663194 | ||
|
|
81849ae631 | ||
|
|
c4361e5695 | ||
|
|
a5246b8075 | ||
|
|
273e945850 | ||
|
|
ef0201718f | ||
|
|
cb91b862aa | ||
|
|
e4a8ced8b0 | ||
|
|
a5b348dfba | ||
|
|
8828d6e2f1 | ||
|
|
bf6c46da1c | ||
|
|
768898a06e | ||
|
|
ba0dc06252 | ||
|
|
522e24017c | ||
|
|
ad31b3dd2e | ||
|
|
7faee110d5 | ||
|
|
aacbdf5748 | ||
|
|
d928c26e27 | ||
|
|
e4b7dd2df3 | ||
|
|
7ccea2bd10 | ||
|
|
82a7471042 | ||
|
|
c3c6d4cbba | ||
|
|
b16845a09e | ||
|
|
29b8c2c924 | ||
|
|
abd5967be3 | ||
|
|
3be74cda30 | ||
|
|
25daf5c45e | ||
|
|
7cf1161305 | ||
|
|
e796c11b64 | ||
|
|
fa2533ed11 | ||
|
|
959933a227 | ||
|
|
de566ec741 | ||
|
|
9c484b9938 | ||
|
|
7ce1502bc5 | ||
|
|
78d5b09b4b | ||
|
|
fff8ae9f8a | ||
|
|
e62858cefd | ||
|
|
526e25abc7 | ||
|
|
dc94fac9f5 | ||
|
|
110783ff2f | ||
|
|
795b1d2893 | ||
|
|
b7dae5c88f | ||
|
|
37d6a79205 | ||
|
|
c4892428ae | ||
|
|
2b1ae89608 | ||
|
|
2a7fc53300 | ||
|
|
f4c06b7cd0 | ||
|
|
0f4953dcd2 | ||
|
|
c7b0c4fb05 | ||
|
|
6aa0cb5e3d | ||
|
|
181c1c8b5b | ||
|
|
74ba508e4a | ||
|
|
992455e273 | ||
|
|
6c8013182b | ||
|
|
45a1f4361e | ||
|
|
39daeca09c | ||
|
|
0c73e67372 | ||
|
|
628437c146 | ||
|
|
528196f786 | ||
|
|
ec1499cdf6 | ||
|
|
019bb558d1 | ||
|
|
6462da2c72 | ||
|
|
d1119a1c26 | ||
|
|
53e18b3ebe | ||
|
|
f97b71c207 | ||
|
|
a367473386 | ||
|
|
f157dd7915 | ||
|
|
2eb2ec50e3 | ||
|
|
9d63482759 | ||
|
|
ebf85cf04b | ||
|
|
3a9a7976fa | ||
|
|
ec9ce6ef2f | ||
|
|
30cfabb08d | ||
|
|
a280c11897 | ||
|
|
358f968429 | ||
|
|
e39ea2adaf | ||
|
|
8a1b7e39af | ||
|
|
af3efa547c | ||
|
|
fdbe5c879e | ||
|
|
3be930f6cf | ||
|
|
bcbb658ae4 | ||
|
|
512a68c5fa | ||
|
|
1eaafcd3d9 | ||
|
|
2a33b177d5 | ||
|
|
1cbf5a5d99 | ||
|
|
28a56289d9 | ||
|
|
08be04b337 | ||
|
|
519d051eb0 | ||
|
|
fe10913780 | ||
|
|
0082892f7c | ||
|
|
3a9de56f9c | ||
|
|
e174802b0d | ||
|
|
e42fe6fb23 | ||
|
|
6cf42db584 | ||
|
|
2186fe32a9 | ||
|
|
7886d2aecc | ||
|
|
2030ab159d | ||
|
|
ee62f5b1a1 | ||
|
|
0755cb6996 | ||
|
|
1650cb33c6 | ||
|
|
c34b3a8017 | ||
|
|
000a85fe99 | ||
|
|
17a8a92bb3 | ||
|
|
ce1fd8eedc | ||
|
|
919a4c661b | ||
|
|
cf11911f6b | ||
|
|
f620b04656 | ||
|
|
a3f5ace895 | ||
|
|
fbe26e28f1 | ||
|
|
9169a3df76 | ||
|
|
5ee0827a84 | ||
|
|
c4462c6de7 | ||
|
|
4c6f1b6a60 | ||
|
|
6c04e26376 | ||
|
|
2ba266c98a | ||
|
|
c0059a875b | ||
|
|
541f23e1bb | ||
|
|
b768d593a2 | ||
|
|
f5fe5c8d73 | ||
|
|
2350baee14 | ||
|
|
38073d2aaa | ||
|
|
78be1280d3 | ||
|
|
952436f129 | ||
|
|
a3002cfee6 | ||
|
|
85ee8f2d43 | ||
|
|
e94f7ca741 | ||
|
|
7de0c8f78b | ||
|
|
f3adf02eea | ||
|
|
f240b60d4b | ||
|
|
cd8bee8e29 | ||
|
|
2195c35c70 | ||
|
|
090162ce8a | ||
|
|
82c3e4c1bc | ||
|
|
e963f7c905 | ||
|
|
2802ad4ad5 | ||
|
|
8eed641f79 | ||
|
|
bfab7361f4 | ||
|
|
4afcbc0610 | ||
|
|
e069f303a4 | ||
|
|
8e01f8ecf1 | ||
|
|
3e8495f435 | ||
|
|
c5d000cefc | ||
|
|
5c2e7df7de | ||
|
|
c1360270d0 | ||
|
|
a843e31c12 | ||
|
|
6b3e7c76bd | ||
|
|
fde8316693 | ||
|
|
7d4f28f52d | ||
|
|
8c451af3f5 | ||
|
|
e14c5adf55 | ||
|
|
cdd751a38b | ||
|
|
d7dfacabfa | ||
|
|
d0856e024f | ||
|
|
0e58378489 | ||
|
|
d53b01eb2b | ||
|
|
ba9c3c9142 | ||
|
|
2a78d0d4d0 | ||
|
|
34e18c176d | ||
|
|
a6f231abbf | ||
|
|
4bad1fea86 | ||
|
|
bc6e7c249b | ||
|
|
21eb40e2c2 | ||
|
|
f1378a6092 | ||
|
|
e0d884ac1a | ||
|
|
829f9f4386 | ||
|
|
f537661341 | ||
|
|
8abbcca76b | ||
|
|
e3e47a9246 | ||
|
|
96d430cd7e | ||
|
|
b53707a129 | ||
|
|
3a95401086 | ||
|
|
0143d71728 | ||
|
|
21279fc9ed | ||
|
|
0327bc0d5a | ||
|
|
ed1073ca92 | ||
|
|
ca946d4b19 | ||
|
|
7244e2f173 | ||
|
|
5be045c42b | ||
|
|
a991ccf909 | ||
|
|
b08930961f | ||
|
|
e01c634ed7 | ||
|
|
46983d68f3 | ||
|
|
4ebca3ca43 | ||
|
|
68bff44b2e | ||
|
|
aedbb8eb87 | ||
|
|
41c6aacce8 | ||
|
|
a65560c5e6 | ||
|
|
e2045907d4 | ||
|
|
f552057b06 | ||
|
|
81dc076bae | ||
|
|
67c0182a49 | ||
|
|
c85a31be7f | ||
|
|
ac2b8af172 | ||
|
|
8b515ae625 | ||
|
|
9a622a05b1 | ||
|
|
eccb295076 | ||
|
|
dd8c40b0d8 | ||
|
|
25740508b5 | ||
|
|
7f23c0dff8 | ||
|
|
5c6dd8e0c3 | ||
|
|
3b347d5f52 | ||
|
|
0636cd898f | ||
|
|
43cb52b319 | ||
|
|
24a2773524 | ||
|
|
7f29d4c247 | ||
|
|
279a70bcb8 | ||
|
|
2f0498bb7f | ||
|
|
64e777a4f4 | ||
|
|
3fd8cd66d6 | ||
|
|
d72bcebdf0 | ||
|
|
7e9dbce259 | ||
|
|
3b6f10913c | ||
|
|
3fbffc9d7d | ||
|
|
b4e0a1e6f5 | ||
|
|
63198288df | ||
|
|
07fd8ae9d7 | ||
|
|
a94ab5883d | ||
|
|
d1e819cef1 | ||
|
|
ebb795af5a | ||
|
|
b0a5161e6b | ||
|
|
3805510e20 | ||
|
|
31050e0580 | ||
|
|
2e9f1908f0 | ||
|
|
f12cd5fa09 | ||
|
|
b487d6c836 | ||
|
|
47ea0f5e23 | ||
|
|
d77f1f090f | ||
|
|
666851a44a | ||
|
|
cd25d7019b | ||
|
|
7c9e3f4555 | ||
|
|
c0f377c8c7 | ||
|
|
4a38b04e3e | ||
|
|
9343cf2aca | ||
|
|
3d70bae994 | ||
|
|
8895d0b91e | ||
|
|
cd0ad70952 | ||
|
|
5041d6ed80 | ||
|
|
e7625c2bb1 | ||
|
|
63712039e7 | ||
|
|
aa55a86ff6 | ||
|
|
e8e50d2d1c | ||
|
|
3f75ebf029 | ||
|
|
f9215e961d | ||
|
|
4776582a6a | ||
|
|
5091e07555 | ||
|
|
5943af79af | ||
|
|
875e9ba513 | ||
|
|
8c8f82d0fd | ||
|
|
00167ee76a | ||
|
|
9c175bfb5d | ||
|
|
ede59a4441 | ||
|
|
e91129db92 | ||
|
|
ebf04c01c6 | ||
|
|
797c2762c8 | ||
|
|
cfb6fb3d06 | ||
|
|
a147136db0 | ||
|
|
7327423a03 | ||
|
|
91529ef6fd | ||
|
|
431fa29ef1 | ||
|
|
1797f7e704 | ||
|
|
0f8c073354 | ||
|
|
61a538ba29 | ||
|
|
9ae8d6aee1 | ||
|
|
98852e2f3f | ||
|
|
d997e73f2d | ||
|
|
fd08a884ad | ||
|
|
6c02510b92 | ||
|
|
7408600f94 | ||
|
|
229391d48b | ||
|
|
187fbfaf03 | ||
|
|
28547b0620 | ||
|
|
380f5dfb4e | ||
|
|
9c7e297118 | ||
|
|
ce94b7e9de | ||
|
|
f209476a1b | ||
|
|
bd08643f6c | ||
|
|
a9770073b2 | ||
|
|
da5332608c | ||
|
|
c43040a08e | ||
|
|
488351e46f | ||
|
|
bb2de4dc59 | ||
|
|
fe27f5364a | ||
|
|
82af7cbfa2 | ||
|
|
44d4f6eb95 | ||
|
|
8d1548bd4d | ||
|
|
9b0d5acb50 | ||
|
|
39686d5d4e | ||
|
|
78a1df693c | ||
|
|
8adad9dddf | ||
|
|
65362be4dd | ||
|
|
a6ca925340 | ||
|
|
97907cd7ff | ||
|
|
3d11a4746c | ||
|
|
0f25280611 | ||
|
|
99016cdc83 | ||
|
|
8e764e642f | ||
|
|
a56cef47f2 | ||
|
|
cf747dac22 | ||
|
|
0234bdb619 | ||
|
|
b88b520bad | ||
|
|
5446bf77e8 | ||
|
|
c5b72464cb | ||
|
|
917eb112ff | ||
|
|
0183ac43bc | ||
|
|
c660be7bbb | ||
|
|
935045926e | ||
|
|
d289fa7402 | ||
|
|
5831cffc19 | ||
|
|
03b8aa1fc2 | ||
|
|
05b6dc7af4 | ||
|
|
8c2d4812b0 | ||
|
|
fb5482a725 | ||
|
|
e43dd0ad19 | ||
|
|
0e6b25fa8d | ||
|
|
6f147f890d | ||
|
|
22ddf8b9d1 | ||
|
|
3b23101365 | ||
|
|
5e8ca5a140 | ||
|
|
da27f43573 | ||
|
|
c697945d39 | ||
|
|
fcda005d88 | ||
|
|
ca96c071e8 | ||
|
|
b23bf58170 | ||
|
|
27d217c220 | ||
|
|
6e4785e74c | ||
|
|
4eb60a169b | ||
|
|
eaaaf91963 | ||
|
|
12e2a4698d | ||
|
|
e5934e9d54 | ||
|
|
802e184ca9 | ||
|
|
cb110d095f | ||
|
|
e3609cc85b | ||
|
|
e2fd4d3b90 | ||
|
|
b1cfedb488 | ||
|
|
c6fc44bcc2 | ||
|
|
4b87b8f7e3 | ||
|
|
a538e25b9d | ||
|
|
971f0f5c16 | ||
|
|
1be070e34d | ||
|
|
dba7864180 | ||
|
|
ce76270ad8 | ||
|
|
a2e38d8bd4 | ||
|
|
319578f390 | ||
|
|
1e68f63715 | ||
|
|
d9331998c3 | ||
|
|
ad7e2cd12e | ||
|
|
3b30b63bcc | ||
|
|
4417f28c5a | ||
|
|
5fa5379f81 | ||
|
|
f715a026ba | ||
|
|
de29b566fe | ||
|
|
b3fe650e27 | ||
|
|
f38c6d3412 | ||
|
|
ed3d13d398 | ||
|
|
5bfb0e73ac | ||
|
|
3bfa79e210 | ||
|
|
4d3299ae08 | ||
|
|
8ec2b2398c | ||
|
|
f3da9760a3 | ||
|
|
87d4cc59a4 | ||
|
|
a36e43bacc | ||
|
|
668497df4d | ||
|
|
d3375962ad | ||
|
|
b155382523 | ||
|
|
e0730e5517 | ||
|
|
c1694a2791 | ||
|
|
dec82ddf68 | ||
|
|
00834ce3c1 | ||
|
|
2c218c9aa0 | ||
|
|
01c705b4d3 | ||
|
|
9565a785b2 | ||
|
|
dd4b2f811b | ||
|
|
ea6d7e1fac | ||
|
|
6b2a4229e3 | ||
|
|
63b41e2548 | ||
|
|
ed895ce02d | ||
|
|
61b71082f7 | ||
|
|
5478f282dc | ||
|
|
ebaa5bebe5 | ||
|
|
e2de6edff8 | ||
|
|
1d324d6b9a | ||
|
|
b0ce226005 | ||
|
|
a2be747f45 | ||
|
|
f8e68dadb5 | ||
|
|
8b05e2ebf8 | ||
|
|
d0ce72167d | ||
|
|
c91ecc3033 | ||
|
|
4ae6190b79 | ||
|
|
d87a26818e | ||
|
|
0dae2240e5 | ||
|
|
7d8f265789 | ||
|
|
8545457f5a | ||
|
|
ca2223abb8 | ||
|
|
01507fc1dc | ||
|
|
b9389100cd | ||
|
|
91c545534e | ||
|
|
3e5afcde9e | ||
|
|
c90bf2d9e1 | ||
|
|
638ce06f77 | ||
|
|
4750b22b6d | ||
|
|
7d59117fc1 | ||
|
|
425b2199f2 | ||
|
|
a882f95749 | ||
|
|
cdcbafa311 | ||
|
|
a36fd419b5 | ||
|
|
9029ec4f28 | ||
|
|
d013e9869a | ||
|
|
fe5eb1a06d | ||
|
|
582ee8897e | ||
|
|
4e41041469 | ||
|
|
8fbc8fc736 | ||
|
|
7760887ac1 | ||
|
|
7e7ab86821 | ||
|
|
106a0cb48d | ||
|
|
75be776aa1 | ||
|
|
2dbebd164f | ||
|
|
1021e8af5c | ||
|
|
2ad688a663 | ||
|
|
bf7effc6f2 | ||
|
|
d86547620a | ||
|
|
94de88ef3b | ||
|
|
101c7697fd | ||
|
|
cf94f4f84a | ||
|
|
6855b2f0e4 | ||
|
|
4d401224de | ||
|
|
4d007c776a | ||
|
|
eac1fb23cf | ||
|
|
d9ff44b870 | ||
|
|
deffa9b4e1 | ||
|
|
3d7fca885e | ||
|
|
08c5fed1b3 | ||
|
|
23d0361168 | ||
|
|
0ace5cd746 | ||
|
|
94da99bf10 | ||
|
|
3d5d7777f6 | ||
|
|
9954d68a1f | ||
|
|
00716b7bfc | ||
|
|
6d1555262e | ||
|
|
c00c712c39 | ||
|
|
63f4a32f2e | ||
|
|
2445575a04 | ||
|
|
56fe821bef | ||
|
|
36cc298b32 | ||
|
|
a00d36eea2 | ||
|
|
ac8b43358d | ||
|
|
1e33931f23 | ||
|
|
7e7406ddb7 | ||
|
|
84e1ace539 | ||
|
|
a1d1877751 | ||
|
|
390ca0930b | ||
|
|
13a2108846 | ||
|
|
9ffd517b53 | ||
|
|
5f1e6f3a23 | ||
|
|
6d5ab0f66b | ||
|
|
bc85be23b7 | ||
|
|
6522455b44 | ||
|
|
2e76be45fa | ||
|
|
0c3a9be1a1 | ||
|
|
a6bbc492b8 | ||
|
|
3d4ef91bf1 | ||
|
|
1604315e40 | ||
|
|
f702d8f1ff | ||
|
|
87f826c850 | ||
|
|
59bf7e524f | ||
|
|
e01424b815 | ||
|
|
c609ae3496 | ||
|
|
e3a3933390 | ||
|
|
1f40ab407c | ||
|
|
1d66bd9c15 | ||
|
|
777999e0ea | ||
|
|
0d5be568f8 | ||
|
|
17bb42d60e | ||
|
|
d6f030a05a | ||
|
|
18401815af | ||
|
|
10e618dee9 | ||
|
|
98f9c798f0 | ||
|
|
49f3172163 | ||
|
|
53489cd556 | ||
|
|
a5aa1da841 | ||
|
|
e11180a4f0 | ||
|
|
e6121f301f | ||
|
|
4050cf9ac3 | ||
|
|
50f148cd7c | ||
|
|
72b5632cfe | ||
|
|
8855bcf3c1 | ||
|
|
a85b58dccd | ||
|
|
01e6d09d79 | ||
|
|
597ee2b8c9 | ||
|
|
6ff37cfc24 | ||
|
|
95521e8a95 | ||
|
|
4b254475da | ||
|
|
6cf62a2768 | ||
|
|
71216bf2c9 | ||
|
|
cbc383d494 | ||
|
|
a75c9ee04e | ||
|
|
d7bcd41909 | ||
|
|
546d45b009 | ||
|
|
99738ea4ad | ||
|
|
4b399fe05a | ||
|
|
eb920b4993 | ||
|
|
1b6b95bb9a | ||
|
|
bd077a3adb | ||
|
|
fd0a000823 | ||
|
|
a6ae8d08ac | ||
|
|
e32f73880f | ||
|
|
63b65c933b | ||
|
|
72ec9ccb00 | ||
|
|
89dc55a106 | ||
|
|
80a9a8984a | ||
|
|
18fe808ce0 | ||
|
|
337b03fc62 | ||
|
|
617c61034f | ||
|
|
f026772c87 | ||
|
|
0f307afd16 | ||
|
|
a1f223735c | ||
|
|
d1bb872aec | ||
|
|
1d4e842cc0 | ||
|
|
209fdc1255 | ||
|
|
3ded1943e2 | ||
|
|
bdeb59f87e | ||
|
|
994adaf493 | ||
|
|
99c73a9cc7 | ||
|
|
6075ef221f | ||
|
|
fc867e59ea | ||
|
|
6212403569 | ||
|
|
75a3093dda | ||
|
|
6c46af5b3c | ||
|
|
67f10bee24 | ||
|
|
524b423733 | ||
|
|
c05f83c9b3 | ||
|
|
f2e97ff9d6 | ||
|
|
ca4ed6d528 | ||
|
|
8006ea073b | ||
|
|
f8b4a91fa8 | ||
|
|
e17d6c81f9 | ||
|
|
f77de41b2e | ||
|
|
c12577e886 | ||
|
|
5daec20a6e | ||
|
|
6d3aaa0560 | ||
|
|
2e11e71453 | ||
|
|
fb561b8fd6 | ||
|
|
0bf6c36ca7 | ||
|
|
66d58476d0 | ||
|
|
bf42227224 | ||
|
|
99a6713e6f | ||
|
|
a26ac1758c | ||
|
|
ef60e17641 | ||
|
|
53943f8246 | ||
|
|
0b8b31a3ea | ||
|
|
6f3132eafd | ||
|
|
dcb5717154 | ||
|
|
bfdb8d4e2d | ||
|
|
91fced3b87 | ||
|
|
825c475c7f | ||
|
|
89471b7db1 | ||
|
|
95233af485 | ||
|
|
fc37398235 | ||
|
|
16ea18370d | ||
|
|
1049bca433 | ||
|
|
364174fb7e | ||
|
|
5352872b4b | ||
|
|
bd0887d18e | ||
|
|
9a5f42039d | ||
|
|
ce8dd136bd | ||
|
|
1be2bff95f | ||
|
|
485ca9c90f | ||
|
|
811cfd52c9 | ||
|
|
5c1807eb2d | ||
|
|
7c15a70f53 | ||
|
|
c9c525e942 | ||
|
|
5d141d4d8a | ||
|
|
e96d6f0dfd | ||
|
|
423dc1b8a7 | ||
|
|
ab080dcdd8 | ||
|
|
6f173d4554 | ||
|
|
649a40f58c | ||
|
|
d06668d39b | ||
|
|
b23463a1d2 | ||
|
|
7f81032577 | ||
|
|
280cbf2174 | ||
|
|
228bb297c2 | ||
|
|
f96e59d085 | ||
|
|
86300e7e86 | ||
|
|
5dea07793b | ||
|
|
5e51204114 | ||
|
|
4c0751c1a0 | ||
|
|
ec1434c65e | ||
|
|
aa7bbeb11b | ||
|
|
88a39dbaf7 | ||
|
|
52920119bd | ||
|
|
47ac358bb6 | ||
|
|
62aa2d7f65 | ||
|
|
557de7690e | ||
|
|
4c8da3d614 | ||
|
|
3f539c6d5c | ||
|
|
5b1d67f17d | ||
|
|
e3e32982a0 | ||
|
|
b771cd30e7 | ||
|
|
ac29db51e5 | ||
|
|
6e3770f04a | ||
|
|
d0a3a04d34 | ||
|
|
c5662020d8 | ||
|
|
fea0d1af66 | ||
|
|
30cd28d288 | ||
|
|
c475721dab | ||
|
|
839069ca15 | ||
|
|
09860f1ee1 | ||
|
|
a1b6378bef | ||
|
|
3530fa6259 | ||
|
|
d22afd78e6 | ||
|
|
f1986093e6 | ||
|
|
6c5477bc02 | ||
|
|
ae6fd458e8 | ||
|
|
cdda48d895 | ||
|
|
556314ae15 | ||
|
|
a2845fa0b4 | ||
|
|
8c4a3374d3 | ||
|
|
b97106b750 | ||
|
|
7907abd206 | ||
|
|
090871f8b9 | ||
|
|
0baf12c0ef | ||
|
|
1599371ee5 | ||
|
|
3bc27ef366 | ||
|
|
d5c5cc8321 | ||
|
|
aea00fc574 | ||
|
|
e9dc8b2460 | ||
|
|
eaed199b20 | ||
|
|
f7fcefc11c | ||
|
|
1f0dcbddbe | ||
|
|
9af0743883 | ||
|
|
7d4863783c | ||
|
|
d87718f768 | ||
|
|
c7ba6159b5 | ||
|
|
b43909aff1 | ||
|
|
87d9a5422d | ||
|
|
44937d33ad | ||
|
|
b561080f3a | ||
|
|
d905ce12e3 | ||
|
|
7040bd1c51 | ||
|
|
cd91a29a89 | ||
|
|
b1b452815a | ||
|
|
59d66dd20e | ||
|
|
0763d0d9d6 | ||
|
|
8c8c44e50f | ||
|
|
14b1941454 | ||
|
|
e52811ac28 | ||
|
|
4f3a980f8b | ||
|
|
05a1fc1ded | ||
|
|
ff74d1ddc0 | ||
|
|
31b0f5543f | ||
|
|
39c0be5d99 | ||
|
|
fb18789b05 | ||
|
|
bcbf5da4b8 | ||
|
|
e1b9dfe65d | ||
|
|
52f86f2293 | ||
|
|
e413694cf5 | ||
|
|
20ce36dc64 | ||
|
|
a3c982a630 | ||
|
|
e0542b5260 | ||
|
|
7c7025a83d | ||
|
|
3e6a1f47ec | ||
|
|
32703d19a0 | ||
|
|
cdc6588ac1 | ||
|
|
57aea9b51c | ||
|
|
a2171bf777 | ||
|
|
b9d0f64f74 | ||
|
|
55d614fce8 | ||
|
|
46d118207a | ||
|
|
3bfe3d256e | ||
|
|
b50ab14a0a | ||
|
|
08c58cf2f2 | ||
|
|
d26543360b | ||
|
|
fb18004c1d | ||
|
|
49fb452a79 | ||
|
|
55bd889a0f | ||
|
|
269a0dbec1 | ||
|
|
cf51db88e7 | ||
|
|
fc0889cd2e | ||
|
|
b3b480c25f | ||
|
|
892fa04130 | ||
|
|
8d3e51c236 | ||
|
|
fd9d2ba4d5 | ||
|
|
75bc633a33 | ||
|
|
3fdb22cb05 | ||
|
|
f51ba1cb8a | ||
|
|
688f4a07a3 | ||
|
|
6c7d0f27a7 | ||
|
|
1e5ca3062c | ||
|
|
ca97e7ba35 | ||
|
|
e99afec7ec | ||
|
|
a23853a6bc | ||
|
|
7c8edb5d11 | ||
|
|
83ad045410 | ||
|
|
e5d5276135 | ||
|
|
6f38a02aaa | ||
|
|
5dd84f1d9a | ||
|
|
30147ce25a | ||
|
|
dd011f043d | ||
|
|
7dec0319b6 | ||
|
|
06c14df0f3 | ||
|
|
041d0be990 | ||
|
|
f6a76e673c | ||
|
|
47f361a4ab | ||
|
|
6e7924da76 | ||
|
|
df1a0c03b6 | ||
|
|
971bea8a24 | ||
|
|
c8f0a1ab5a | ||
|
|
0ae9ac0848 | ||
|
|
3dd52e8b28 | ||
|
|
0f6915c615 | ||
|
|
74cd23b331 | ||
|
|
cc17895ef3 | ||
|
|
09ada81b27 | ||
|
|
75e0fa87c3 | ||
|
|
6628e226e4 | ||
|
|
89d47a392b | ||
|
|
c38abb2cfe | ||
|
|
faf18dfdfd | ||
|
|
2dc32fb8c9 | ||
|
|
2ec029e8e0 | ||
|
|
80904209b3 | ||
|
|
8b1483d7ef | ||
|
|
bbeff1ef22 | ||
|
|
aac3c2e0ee | ||
|
|
a240a00983 | ||
|
|
c86a6ba3f3 | ||
|
|
bc301baa1f | ||
|
|
edbf26f007 | ||
|
|
5dca2a3588 | ||
|
|
cf5a7d228b | ||
|
|
f7a7c3ca7b | ||
|
|
28fcdf0b8f | ||
|
|
c38ec33881 | ||
|
|
cfee00d4c5 | ||
|
|
46bf0d78fa | ||
|
|
a14be592de | ||
|
|
29691a3167 | ||
|
|
517a2b99d7 | ||
|
|
edf73af508 | ||
|
|
4a5ea08a2a | ||
|
|
7b86cca649 | ||
|
|
c77b7cd7d6 | ||
|
|
6b0a87c57a | ||
|
|
b1c331c5f3 | ||
|
|
7ae342452e | ||
|
|
4438aa6606 | ||
|
|
8cd8472003 | ||
|
|
687aa78567 | ||
|
|
8419e5883d | ||
|
|
4bac0474c6 | ||
|
|
f0b66a2241 | ||
|
|
51b71e8f20 | ||
|
|
8615515b9a | ||
|
|
10cf486d26 | ||
|
|
abb1647672 | ||
|
|
114c1b95fa | ||
|
|
4e88bbc811 | ||
|
|
504a41aec4 | ||
|
|
e9bf1ef618 | ||
|
|
9d6e450c05 | ||
|
|
fb7e95f1e4 | ||
|
|
f95418dfc9 | ||
|
|
94be235a0f | ||
|
|
100de5a55e | ||
|
|
26570de4cd | ||
|
|
7c97e02715 | ||
|
|
e73bdf9d8c | ||
|
|
8d1ff1d49c | ||
|
|
de770d2881 | ||
|
|
7f96770089 | ||
|
|
3f83538fc1 | ||
|
|
3040f4e89f | ||
|
|
c57c264d91 | ||
|
|
8ba7559cac | ||
|
|
c4d5a4138c | ||
|
|
d699404ce7 | ||
|
|
ee79747cb6 | ||
|
|
5cb6c4e77c | ||
|
|
05638a8953 | ||
|
|
48f7efc97f | ||
|
|
9aa6b4cbfa | ||
|
|
cae0a7628d | ||
|
|
82e29dfcb3 | ||
|
|
ad94b94833 | ||
|
|
687e497455 | ||
|
|
67f31648a6 | ||
|
|
d332851a75 | ||
|
|
4a18a32cfc | ||
|
|
428cd54a81 | ||
|
|
dc4e8603d7 | ||
|
|
b96f8aef6d | ||
|
|
dc50c312cb | ||
|
|
1f12a2d67b | ||
|
|
a2bef7dbbe | ||
|
|
c6af13c8cc | ||
|
|
5be5f6475f | ||
|
|
5673fb34b1 | ||
|
|
5214fe2a55 | ||
|
|
76db33829b | ||
|
|
746da8f270 | ||
|
|
f8667cdb38 | ||
|
|
1ec4fe921d | ||
|
|
915701106c | ||
|
|
2309e6f040 | ||
|
|
839d823edd | ||
|
|
99e6f0edad | ||
|
|
bb138bd6e4 | ||
|
|
32a19563a5 | ||
|
|
3b65127399 | ||
|
|
c17a5823e9 | ||
|
|
c9e88c944a | ||
|
|
b93cf4868c | ||
|
|
53ac88ca26 | ||
|
|
aff32f69bb | ||
|
|
4fcf11d0db | ||
|
|
e04af84ac0 | ||
|
|
965c44a43d | ||
|
|
905061e2b7 | ||
|
|
45df3087f2 | ||
|
|
f9f53a3355 | ||
|
|
ab08ac8ad4 | ||
|
|
12c9475bfc | ||
|
|
a0d7fc12db | ||
|
|
5b5fea347a | ||
|
|
d049007412 | ||
|
|
cc14a3b288 | ||
|
|
bcd3eeda49 | ||
|
|
992b365eab | ||
|
|
3882cc4aa8 | ||
|
|
38bd79ab88 | ||
|
|
790fd1d94d | ||
|
|
19e1ddff1d | ||
|
|
0bbc26bd0b | ||
|
|
1f3db436d8 | ||
|
|
7ac663bd5f | ||
|
|
53fbceb49a | ||
|
|
1159dc091d | ||
|
|
38c0b4ad65 | ||
|
|
f4f563d570 | ||
|
|
550ba16a09 | ||
|
|
0289f5dff9 | ||
|
|
ea0ceeb65d | ||
|
|
3746bf28a1 | ||
|
|
a497c69758 | ||
|
|
731d75c756 | ||
|
|
982ba5ab90 | ||
|
|
c2e2a46025 | ||
|
|
8430fb0ed3 | ||
|
|
9a829a38b1 | ||
|
|
04ec750157 | ||
|
|
3655db2a30 | ||
|
|
5edb84ccfb | ||
|
|
95e2da9c9e | ||
|
|
6cb1eb41cf | ||
|
|
46a0caf2e0 | ||
|
|
d96b660686 | ||
|
|
ae66b1b88e | ||
|
|
60a564dcfd | ||
|
|
69656d2775 | ||
|
|
24415f2186 | ||
|
|
0ae3eafe10 | ||
|
|
6cf708f6e1 | ||
|
|
f5a626006c | ||
|
|
101a72cd65 | ||
|
|
312429cb88 | ||
|
|
c9851f1c05 | ||
|
|
73b7fe6169 | ||
|
|
d1c5c55a61 | ||
|
|
bdc31d9459 | ||
|
|
f08c42a37e | ||
|
|
8fbd39934f | ||
|
|
c460630cf8 | ||
|
|
335bc87ad8 | ||
|
|
6cbeeb1b1a | ||
|
|
47c9f1439d | ||
|
|
64589bc807 | ||
|
|
2eae9c17a4 | ||
|
|
9f6c206078 | ||
|
|
2460eecd75 | ||
|
|
a084695978 | ||
|
|
705da88daa | ||
|
|
ce85a42ecf | ||
|
|
2990b75faa | ||
|
|
94c06f3f5d | ||
|
|
b72a7db6fe | ||
|
|
9e4086e501 | ||
|
|
b960f18f4e | ||
|
|
d0dc75c06f | ||
|
|
13a5eccaf8 | ||
|
|
898195e1da | ||
|
|
8ea0d19d2f | ||
|
|
465318b352 | ||
|
|
fa01d53f9e | ||
|
|
ba8fe1ddaf | ||
|
|
50bc53d673 | ||
|
|
d682701232 | ||
|
|
544883ca9f | ||
|
|
54a7761e15 | ||
|
|
ed51b4e1ea | ||
|
|
fcc4781e3a | ||
|
|
24b5bd6862 | ||
|
|
614c718304 | ||
|
|
e2ce33a86a | ||
|
|
e338f1af15 | ||
|
|
eefc5ed1d7 | ||
|
|
7694b130e6 | ||
|
|
32ad7271f3 | ||
|
|
2b012ff238 | ||
|
|
1bd219ed40 | ||
|
|
02a59812e6 | ||
|
|
7f4849e165 | ||
|
|
628c7dd92a | ||
|
|
5764ad84e9 | ||
|
|
f3fde4a8df | ||
|
|
983ccdd121 | ||
|
|
73ed22c52c | ||
|
|
bace16bac6 | ||
|
|
3bb0842eae | ||
|
|
a6b38397ea | ||
|
|
c63467c79f | ||
|
|
b1cd37814a | ||
|
|
07d10ee583 | ||
|
|
614bee3061 | ||
|
|
81dfea4052 | ||
|
|
1b14444eba | ||
|
|
245d83adce | ||
|
|
b5835fc232 | ||
|
|
8a54f133ba | ||
|
|
511cc483cb | ||
|
|
6e18a16f78 | ||
|
|
43d08f2d05 | ||
|
|
171e9b1ce9 | ||
|
|
eec582e704 | ||
|
|
4b05feb6d6 | ||
|
|
d6f5bf6cde | ||
|
|
f1d72d0b6d | ||
|
|
148d494905 | ||
|
|
38b575d91b | ||
|
|
baf99a2280 | ||
|
|
1d80b87bcc | ||
|
|
84754c47d3 | ||
|
|
64b8a990f9 | ||
|
|
0580c2223b | ||
|
|
824aed773b | ||
|
|
9164e69d49 | ||
|
|
670dc39d4f | ||
|
|
b196d0dded | ||
|
|
bfd62b0807 | ||
|
|
3514d5cd78 | ||
|
|
b3b46339a4 | ||
|
|
9ff4947abd | ||
|
|
469eb77486 | ||
|
|
213b685b43 | ||
|
|
42df327104 | ||
|
|
d2b42e3843 | ||
|
|
b999c1e5b1 | ||
|
|
2ecf696a12 | ||
|
|
063a63445a | ||
|
|
b84bdcf464 | ||
|
|
f275ecf6d5 | ||
|
|
b241920802 | ||
|
|
dcd1406a27 | ||
|
|
d2d408e223 | ||
|
|
9699d8aa82 | ||
|
|
6d8d9107c6 | ||
|
|
fb8483dc56 | ||
|
|
85621bdd9f | ||
|
|
573f7fc1fb | ||
|
|
14af840c3a | ||
|
|
aa2d1bcc93 | ||
|
|
20cde9cd00 | ||
|
|
3dff4afe83 | ||
|
|
3edce7f580 | ||
|
|
35d601d795 | ||
|
|
f00957ec3a | ||
|
|
46c04561ff | ||
|
|
9abe357754 | ||
|
|
3c99350247 | ||
|
|
b041f2a857 | ||
|
|
0a15f873af | ||
|
|
08b0aebe03 | ||
|
|
a323fe7992 | ||
|
|
1963e94e33 | ||
|
|
7437af0131 | ||
|
|
60a1fc3020 | ||
|
|
8714193c97 | ||
|
|
a61733cf47 | ||
|
|
1aff8af336 | ||
|
|
c21be01e97 | ||
|
|
72d240a57e | ||
|
|
83769b3d52 | ||
|
|
1a5c2365a1 | ||
|
|
87e40f1820 | ||
|
|
8fbfba907b | ||
|
|
f342a8c7a3 | ||
|
|
866715b752 | ||
|
|
e0ec121012 | ||
|
|
63ff5736a2 | ||
|
|
3451778f48 | ||
|
|
e4b3007ce2 | ||
|
|
a5593469ae | ||
|
|
484a4c1454 | ||
|
|
09a336c708 | ||
|
|
4ac5fb08e3 | ||
|
|
ff86bdc35f | ||
|
|
726ce08700 | ||
|
|
c14840a4c0 | ||
|
|
24a492ffe0 | ||
|
|
c4ff4e030a | ||
|
|
7244944879 | ||
|
|
1f2db2c049 | ||
|
|
d6e8b1ba2c | ||
|
|
7f0a3eebfb | ||
|
|
5d0345c922 | ||
|
|
99a041158c | ||
|
|
8f7efd5b67 | ||
|
|
d2c27daa16 | ||
|
|
2d2041db5e | ||
|
|
e3f260c57f | ||
|
|
8113c3f05e | ||
|
|
e3bc15a4d9 | ||
|
|
3f68180ee7 | ||
|
|
6bb6dc0aaf | ||
|
|
d625fb014c | ||
|
|
8cc7676335 | ||
|
|
626cecba95 | ||
|
|
65348cd5e7 | ||
|
|
5f5316fbdc | ||
|
|
011bac7a1e | ||
|
|
86671a2354 | ||
|
|
8fac39fffa | ||
|
|
a2710e01da | ||
|
|
38abcd4a90 | ||
|
|
62dd6fbc6b | ||
|
|
75c56a2a7c | ||
|
|
7491b1c369 | ||
|
|
a3cca7424a | ||
|
|
387aed6269 | ||
|
|
20bf888d73 | ||
|
|
ad6d52cb0f | ||
|
|
8dc293f629 | ||
|
|
de8819aae3 | ||
|
|
e98ced5d0f | ||
|
|
6775af302a | ||
|
|
621cae61b6 | ||
|
|
922a8f7eef | ||
|
|
48a0e564b9 | ||
|
|
0758d89a59 | ||
|
|
e94b6acd02 | ||
|
|
a1beff7d3d | ||
|
|
ffc30b4c03 | ||
|
|
6d824231e9 | ||
|
|
060a90197e | ||
|
|
df7aac01dd | ||
|
|
69b00d859b | ||
|
|
9c88979040 | ||
|
|
bda218af07 | ||
|
|
15d7568792 | ||
|
|
18b14b54f4 | ||
|
|
38857cf2e6 | ||
|
|
0647590170 | ||
|
|
099743d448 | ||
|
|
4cbc423d67 | ||
|
|
e37b5613ed | ||
|
|
f92ad89a6c | ||
|
|
e99a675c50 | ||
|
|
4593bff76f | ||
|
|
81d6590544 | ||
|
|
2f9597cf51 | ||
|
|
8a246a6444 | ||
|
|
876d493458 | ||
|
|
6f023fcae4 | ||
|
|
0abd93417a | ||
|
|
b4f5fe9977 | ||
|
|
e15b0c7d90 | ||
|
|
3cf52b018c | ||
|
|
cb9f159091 | ||
|
|
64cea5f25a | ||
|
|
b6fd58e644 | ||
|
|
de774a3058 | ||
|
|
9ebe2ff45e | ||
|
|
4ebc575afc | ||
|
|
a1d4d8e13f | ||
|
|
69f7dfbb47 | ||
|
|
99ce0a36e6 | ||
|
|
67c34554db | ||
|
|
9ba61af83b | ||
|
|
436f5f6229 | ||
|
|
270d6108ca | ||
|
|
18094370a6 | ||
|
|
26eac398e0 | ||
|
|
93c896b4f7 | ||
|
|
6b29202f1f | ||
|
|
3b1d8fd8fe | ||
|
|
630d62f014 | ||
|
|
711b8ca747 | ||
|
|
34a1923d18 | ||
|
|
6291936ce9 | ||
|
|
5220c6c288 | ||
|
|
532c82f752 | ||
|
|
61218715af | ||
|
|
8dce5a90bb | ||
|
|
8d794b8151 | ||
|
|
b1cfab34fd | ||
|
|
159a5e20bf | ||
|
|
aea4fd8e30 | ||
|
|
767e6a9783 | ||
|
|
bb8ac0000e | ||
|
|
d73d2e0788 | ||
|
|
714e83198e | ||
|
|
c780918501 | ||
|
|
8bcb01fadb | ||
|
|
f78f5366cf | ||
|
|
f0d02f24b1 | ||
|
|
15bc33f8e6 | ||
|
|
d21a19925b | ||
|
|
a1e9d99d3e | ||
|
|
094cf675c7 | ||
|
|
efcb7ee110 | ||
|
|
c8321fc96e | ||
|
|
56ea29e899 | ||
|
|
df2305eb02 | ||
|
|
5281793078 | ||
|
|
9beb210c23 | ||
|
|
d1c890db18 | ||
|
|
0be4a4b71d | ||
|
|
19a545049b | ||
|
|
34eb34c5fc | ||
|
|
28adc28a56 | ||
|
|
5d8c803da6 | ||
|
|
2fd7fdaa74 | ||
|
|
f9cb99a1ee | ||
|
|
37bfb2c594 | ||
|
|
3b55248e6c | ||
|
|
b35bccdad2 | ||
|
|
6cabf3e57d | ||
|
|
b31b54e17c | ||
|
|
469d044254 | ||
|
|
9a26d7078f | ||
|
|
896c32a660 | ||
|
|
6e23613b9e | ||
|
|
d15324110e | ||
|
|
153de54fe8 | ||
|
|
2866f6fc23 | ||
|
|
9228f0c076 | ||
|
|
1eafb01c8d | ||
|
|
44f446e745 | ||
|
|
c71fdc72c7 | ||
|
|
bbf944a225 | ||
|
|
dbc5641144 | ||
|
|
9080a64fcd | ||
|
|
f50177996d | ||
|
|
b649dd39d9 | ||
|
|
ab87c03732 | ||
|
|
8eaf77b3c1 | ||
|
|
cbea141a54 | ||
|
|
60a040a141 | ||
|
|
4a518c964b | ||
|
|
bb2ad656a3 | ||
|
|
30b95e0a57 | ||
|
|
852c631eff | ||
|
|
211a279248 | ||
|
|
2ae2067841 | ||
|
|
0dd06e3343 | ||
|
|
4e9bec69e4 | ||
|
|
70392d9f1a | ||
|
|
9a3abf6a6a | ||
|
|
0bfcbdb359 | ||
|
|
2f3c8bf079 | ||
|
|
e7bde986aa | ||
|
|
f3211bf34d | ||
|
|
c086e0a502 | ||
|
|
80f4b668b2 | ||
|
|
01606ab5e0 | ||
|
|
91e29d9710 | ||
|
|
23d101f8fa | ||
|
|
e08b3e7dec | ||
|
|
c4e32b7c45 | ||
|
|
14ece40054 | ||
|
|
eaa6be55d7 | ||
|
|
2ae714aea2 | ||
|
|
5f6687ec62 | ||
|
|
06053fad85 | ||
|
|
d0c06ab0ce | ||
|
|
cfb6f927b2 | ||
|
|
8d33894989 | ||
|
|
ba13cbe004 | ||
|
|
9410adf9ce | ||
|
|
7443c78087 | ||
|
|
d5b34a0b41 | ||
|
|
0cd64e297a | ||
|
|
0fcf80dd6e | ||
|
|
56e9e0b4f9 | ||
|
|
dfcd3267e4 | ||
|
|
f689747fd6 | ||
|
|
bb11165568 | ||
|
|
a180c6b896 | ||
|
|
34d3be244d | ||
|
|
4662781213 | ||
|
|
5bd12a708f | ||
|
|
e39149c954 | ||
|
|
b925a97b5d | ||
|
|
3bcf2c81da | ||
|
|
b52801de25 | ||
|
|
4d8c3d7d73 | ||
|
|
ea1f10361a | ||
|
|
5dafad71ac | ||
|
|
df28fb97ed | ||
|
|
5600018a56 | ||
|
|
441c00c06f | ||
|
|
9533bbc47d | ||
|
|
2025d314ec | ||
|
|
772f991545 | ||
|
|
f6b59b03bb | ||
|
|
2ae3775841 | ||
|
|
8573c13b8c | ||
|
|
db1c6e9afd | ||
|
|
429f003578 | ||
|
|
dd684858f2 | ||
|
|
2e82d56634 | ||
|
|
1ede6e70b7 | ||
|
|
8908fbef58 | ||
|
|
09a96b3a2b | ||
|
|
5b7c8b6920 | ||
|
|
409b46963e | ||
|
|
53cf574735 | ||
|
|
fe236cabd3 | ||
|
|
d25536569a | ||
|
|
06b9499805 | ||
|
|
2a2e57467d | ||
|
|
24ac6cbbac | ||
|
|
0b68e52b83 | ||
|
|
cf1393ffe1 | ||
|
|
c4808a3e61 | ||
|
|
f0d37b19e2 | ||
|
|
65206a72b2 | ||
|
|
a91d4c06ee | ||
|
|
e8ad08bd28 | ||
|
|
7a3b1ddd18 | ||
|
|
db939ee076 | ||
|
|
5ede17d8b8 | ||
|
|
0ff992e04e | ||
|
|
34a579821a | ||
|
|
dd30a35e1e | ||
|
|
2074e1423f | ||
|
|
d5476b78ec | ||
|
|
df19700f69 | ||
|
|
3c605a7740 | ||
|
|
a64b35d72f | ||
|
|
e880b77692 | ||
|
|
e8fe26ff1c | ||
|
|
108ccd98d0 | ||
|
|
5fddc28cd6 | ||
|
|
63001d5e16 | ||
|
|
caccf76ea4 | ||
|
|
5120549005 | ||
|
|
e67032a9d7 | ||
|
|
a903eb7605 | ||
|
|
5b431338c2 | ||
|
|
c20d488648 | ||
|
|
57a8f3f5ee | ||
|
|
6dda7fc356 | ||
|
|
9261c1608f | ||
|
|
80cd43390b | ||
|
|
c196849574 | ||
|
|
1007d19860 | ||
|
|
374a7b8848 | ||
|
|
8ae57fd117 | ||
|
|
5a2fd16b92 | ||
|
|
3545ac130b | ||
|
|
199c786e22 | ||
|
|
38b52283a5 | ||
|
|
1f8b2d4c7b | ||
|
|
9c0fa93510 | ||
|
|
ab2932f75e | ||
|
|
81083ef470 | ||
|
|
e4232c73c6 | ||
|
|
29070cec77 | ||
|
|
fbef9f7691 | ||
|
|
97f3a3feef | ||
|
|
54cc095ff5 | ||
|
|
0ad4d5cc47 | ||
|
|
236f3ab768 | ||
|
|
6de7c42ac9 | ||
|
|
bb7a809ab5 | ||
|
|
142b155460 | ||
|
|
0f6bc9c5c7 | ||
|
|
e600d7c449 | ||
|
|
37327be521 | ||
|
|
07d2bf988a | ||
|
|
df06a0c878 | ||
|
|
cee3fea98f | ||
|
|
5c378cbd9d | ||
|
|
37099f3177 | ||
|
|
128cc7eadb | ||
|
|
de36973716 | ||
|
|
08b1996149 | ||
|
|
0574302840 | ||
|
|
fad0a7728c | ||
|
|
86ad7292af | ||
|
|
74e2bfd9bd | ||
|
|
0c60bd80df | ||
|
|
46f81a331a | ||
|
|
b0ba7ed188 | ||
|
|
0d9e14d4c7 | ||
|
|
10ae457237 | ||
|
|
73f59417bd | ||
|
|
6814230193 | ||
|
|
d68131ac00 | ||
|
|
952f098440 | ||
|
|
f08477ea66 | ||
|
|
8544dd66d8 | ||
|
|
a5a2848251 | ||
|
|
7232535729 | ||
|
|
271292dbd4 | ||
|
|
eb47d43497 | ||
|
|
fd9317a295 | ||
|
|
11fc6d5321 | ||
|
|
64a36aee03 | ||
|
|
59440840fe | ||
|
|
c2075af7f7 | ||
|
|
ab85f09608 | ||
|
|
da799b18c3 | ||
|
|
ca6de3e486 | ||
|
|
ae3c4e8459 | ||
|
|
8e79f550b0 | ||
|
|
0137918701 | ||
|
|
79828ddc78 | ||
|
|
fcca9dd90a | ||
|
|
1b4b537043 | ||
|
|
b09e2f697e | ||
|
|
2855baecef | ||
|
|
b7cf1bb215 | ||
|
|
b3dd349342 | ||
|
|
357337047b | ||
|
|
5496f043a5 | ||
|
|
a22aa8055f | ||
|
|
57f0ea0638 | ||
|
|
f723ac79d1 | ||
|
|
3a15b416a9 | ||
|
|
cc97265c06 | ||
|
|
665038d0ce | ||
|
|
8d1149dc07 | ||
|
|
ebea9e9270 | ||
|
|
b5aa9efefb | ||
|
|
95f56e2e62 | ||
|
|
273775d848 | ||
|
|
8606f344a8 | ||
|
|
53c3696cbb | ||
|
|
7203d9ba12 | ||
|
|
26d3e48555 | ||
|
|
a53206995a | ||
|
|
b2c8f48af9 | ||
|
|
11c23a7945 | ||
|
|
3be4579204 | ||
|
|
a3d08e7191 | ||
|
|
7e8b16f975 | ||
|
|
966eb2fe47 | ||
|
|
5826f02ebe | ||
|
|
2b525c6016 | ||
|
|
dc7923c436 | ||
|
|
00a64eeb89 | ||
|
|
76c9f2c4ec | ||
|
|
dafa52ea93 | ||
|
|
c2bf6c3822 | ||
|
|
ab8de6ba25 | ||
|
|
6714e2b87f | ||
|
|
855269f35d | ||
|
|
a3ed02ea16 | ||
|
|
84f13eadc1 | ||
|
|
cedf43c084 | ||
|
|
4c281a39bb | ||
|
|
5533357c46 | ||
|
|
ae4d83107a | ||
|
|
b92494f023 | ||
|
|
d3a2617d3c | ||
|
|
740943290c | ||
|
|
c51c7fe189 | ||
|
|
610eaaaaff | ||
|
|
bde9944449 | ||
|
|
1318ea85be | ||
|
|
67e3e60f28 | ||
|
|
bc2360e985 | ||
|
|
8e7b0022a6 | ||
|
|
27efd3c0a4 | ||
|
|
07a31d3f35 | ||
|
|
07dbb7d7b7 | ||
|
|
26f6da10dd | ||
|
|
7ba8e09aef | ||
|
|
d95372c993 | ||
|
|
643daadf60 | ||
|
|
7bea4af6c9 | ||
|
|
b27776b3c6 | ||
|
|
d9c8dedcfb | ||
|
|
9e0bf9e7dc | ||
|
|
0e0dfb60e0 | ||
|
|
b95fd0d0b0 | ||
|
|
f4f9b1e323 | ||
|
|
f7c3373f75 | ||
|
|
639b7c5610 | ||
|
|
b30809d681 | ||
|
|
0731f9977b | ||
|
|
e8f8829a76 | ||
|
|
663ff30eae | ||
|
|
01d22c3aab | ||
|
|
9b24369b92 | ||
|
|
f6645eea92 | ||
|
|
634f61281d | ||
|
|
c560a739b1 | ||
|
|
ea672e36ae | ||
|
|
eb9335f706 | ||
|
|
d429525620 | ||
|
|
b1a480c20d | ||
|
|
0fcebb8670 | ||
|
|
3e32bd116b | ||
|
|
a4cb268703 | ||
|
|
06b98589eb | ||
|
|
1e1991e285 | ||
|
|
b0446fab0c | ||
|
|
1194e40ef2 | ||
|
|
2a820c73b1 | ||
|
|
624fcf6197 | ||
|
|
59747500aa | ||
|
|
6f0b12174c | ||
|
|
5568c8dc7d | ||
|
|
d957cc4883 | ||
|
|
2d949b63b5 | ||
|
|
abdbef6a51 | ||
|
|
c51011acef | ||
|
|
6b1e0401ba | ||
|
|
d523242dbb | ||
|
|
92058b16c9 | ||
|
|
4424c425a6 | ||
|
|
5f7b0aae06 | ||
|
|
f77e3b5532 | ||
|
|
334b981741 | ||
|
|
957ff7d206 | ||
|
|
5973ccd522 | ||
|
|
1ea1f0af96 | ||
|
|
f5ac81db41 | ||
|
|
9e7f52ccda | ||
|
|
f8173e6a5a | ||
|
|
5edf05d569 | ||
|
|
4666bcbfb2 | ||
|
|
a374e52ea5 | ||
|
|
f987d331b7 | ||
|
|
314f177fe7 | ||
|
|
64f591769a | ||
|
|
0801c9c120 | ||
|
|
e4e02a2615 | ||
|
|
ff69607f52 | ||
|
|
a23b213736 | ||
|
|
36ce4e04d9 | ||
|
|
3b6593b476 | ||
|
|
6ef3102cd1 | ||
|
|
39381c104c | ||
|
|
c04b490f29 | ||
|
|
e1f2d296bf | ||
|
|
1d760388de | ||
|
|
8286d1ce34 | ||
|
|
7b14d29dee | ||
|
|
75224d25b0 | ||
|
|
3a3ae742a4 | ||
|
|
e7c64b7812 | ||
|
|
e55eb73fb9 | ||
|
|
201b35be54 | ||
|
|
e25b2b05ae | ||
|
|
6b470f826e | ||
|
|
4517d9d707 | ||
|
|
7ff2c77a82 | ||
|
|
e5e43173bc | ||
|
|
6f3822e6ea | ||
|
|
95bafc416d | ||
|
|
2576975988 | ||
|
|
8c5ae9d052 | ||
|
|
8ed5282da5 | ||
|
|
9b6d9fda43 | ||
|
|
77c6b391c4 | ||
|
|
c8432f74f6 | ||
|
|
8fa3425886 | ||
|
|
84396d0da1 | ||
|
|
38e6bde07e | ||
|
|
9811aaeecc | ||
|
|
7284aae4df | ||
|
|
316431c4d1 | ||
|
|
bb039576d4 | ||
|
|
646e762cbe | ||
|
|
1d94649b96 | ||
|
|
1fa019e9dd | ||
|
|
364998b34b | ||
|
|
6778fbfa3b | ||
|
|
bb11ad2ec1 | ||
|
|
b6386630fb | ||
|
|
363e5cdf64 | ||
|
|
7594be8014 | ||
|
|
62f8811a46 | ||
|
|
2d1dc4a874 | ||
|
|
0546aebc84 | ||
|
|
c06009d5fb | ||
|
|
3d02fd5d0e | ||
|
|
562f6703b6 | ||
|
|
573c88564b | ||
|
|
dabf101d96 | ||
|
|
292089cee8 | ||
|
|
5594c73ceb | ||
|
|
ce6652efb1 | ||
|
|
f64f55a4c6 | ||
|
|
9a3a8c39f4 | ||
|
|
1de4c31750 | ||
|
|
bda85f700a | ||
|
|
3471ce6c63 | ||
|
|
b8dfac391a | ||
|
|
28546ad031 | ||
|
|
d6e5e376bf | ||
|
|
9b055c6ad6 | ||
|
|
a3c17d1071 | ||
|
|
d57cd1aa91 | ||
|
|
3405330d92 | ||
|
|
541e865fe8 | ||
|
|
c03b97d2d4 | ||
|
|
5d275de8b2 | ||
|
|
80f3a3152c | ||
|
|
54abfb670c | ||
|
|
b41bf9f231 | ||
|
|
2b8d2288fb | ||
|
|
b556ff8a83 | ||
|
|
4c8471ef43 | ||
|
|
1aa58ec580 | ||
|
|
272806a3f3 | ||
|
|
51a718f337 | ||
|
|
cc982cf246 | ||
|
|
0896808625 | ||
|
|
c562e88e75 | ||
|
|
abd7163205 | ||
|
|
1701afb6d4 | ||
|
|
d8937b11cf | ||
|
|
de871585e0 | ||
|
|
b41c45d40a | ||
|
|
ecaa0d1401 | ||
|
|
cdc35d99bc | ||
|
|
f5f96c5021 | ||
|
|
29a139f56b | ||
|
|
daf1f2ed45 | ||
|
|
eb37890924 | ||
|
|
eb3e69d5d7 | ||
|
|
5aefe52ccc | ||
|
|
0d00db281a | ||
|
|
c8c4cfbaae | ||
|
|
d5be56fa21 | ||
|
|
1d55a4f1e9 | ||
|
|
83bc115b63 | ||
|
|
e952ff142d | ||
|
|
b94bad7f58 | ||
|
|
354cf02aaa | ||
|
|
7f0cf3eda8 | ||
|
|
9bc0bd452d | ||
|
|
d124f43423 | ||
|
|
a197bda363 | ||
|
|
8ca46fa35d | ||
|
|
aec8feab49 | ||
|
|
e7c30fff48 | ||
|
|
2916cce6fb | ||
|
|
bf95c2ba85 | ||
|
|
6081572179 | ||
|
|
bbf5fee320 | ||
|
|
d66862fd26 | ||
|
|
1c0ef4621e | ||
|
|
323e032287 | ||
|
|
948960f33f | ||
|
|
abde247e14 | ||
|
|
a0cd2bf5a7 | ||
|
|
58083fbb96 | ||
|
|
ad068a300b | ||
|
|
be320e11a5 | ||
|
|
b7826be103 | ||
|
|
216e24864a | ||
|
|
afea032322 | ||
|
|
88ddc8ebca | ||
|
|
2aac603fad | ||
|
|
8caf3fa83a | ||
|
|
42975f0edf | ||
|
|
3433227fa7 | ||
|
|
e6067c915d | ||
|
|
fcfbf156ce | ||
|
|
d564955cab | ||
|
|
30d523a5c2 | ||
|
|
126b444d77 | ||
|
|
d58870545f | ||
|
|
231e8895a5 | ||
|
|
7d7b9e5b73 | ||
|
|
25bf6d63b4 | ||
|
|
05cd8a82e5 | ||
|
|
1c218c8e9b | ||
|
|
8af81cd290 | ||
|
|
869601d041 | ||
|
|
21239fbab9 | ||
|
|
722a077de7 | ||
|
|
1e15e97d17 | ||
|
|
225307e855 | ||
|
|
84f1e1b56e | ||
|
|
bdf1c1f495 | ||
|
|
5e1da96177 | ||
|
|
3ddb2084cd | ||
|
|
d831e25366 | ||
|
|
37fdc50fb4 | ||
|
|
d8a3025ea1 | ||
|
|
911cf2ae45 | ||
|
|
60ba41e82f | ||
|
|
129eb6a3be | ||
|
|
34fbdf4358 | ||
|
|
51b3de7383 | ||
|
|
0ec19c9ec9 | ||
|
|
4711dd083f | ||
|
|
bcc0bcd219 | ||
|
|
00aaefb926 | ||
|
|
3302c54c43 | ||
|
|
434dec2318 | ||
|
|
96a05a9b77 | ||
|
|
77a44751a6 | ||
|
|
51b95b1b91 | ||
|
|
ab02dea902 | ||
|
|
28a1bd8535 | ||
|
|
a2a8e72e20 | ||
|
|
4788dd8bd2 | ||
|
|
793f69d4b6 | ||
|
|
0879013a90 | ||
|
|
877a552f3b | ||
|
|
2403e1f465 | ||
|
|
119423895d | ||
|
|
702ae0307e | ||
|
|
b5bdeae2a2 | ||
|
|
4159a97881 | ||
|
|
2d5e6520db | ||
|
|
331ab71427 | ||
|
|
f9aaa16cfe | ||
|
|
2ebc32793a | ||
|
|
cec948ffb4 | ||
|
|
466b6762d9 | ||
|
|
e03737c665 | ||
|
|
3f1b7a0335 | ||
|
|
f26f2faa72 | ||
|
|
7763da1273 | ||
|
|
bb6791d6eb | ||
|
|
1c6b867079 | ||
|
|
6b57684c52 | ||
|
|
08769a2daa | ||
|
|
7fff2588b2 | ||
|
|
ee5cab04e0 | ||
|
|
9f429e4150 | ||
|
|
9528dbc4dc | ||
|
|
3ff8d0fd1f | ||
|
|
7b64d9975e | ||
|
|
ba60a0c04b | ||
|
|
6926f02cc3 | ||
|
|
52018b1f81 | ||
|
|
fcc95ef99b | ||
|
|
2f29fab931 | ||
|
|
0c2767afd7 | ||
|
|
4a90133d29 | ||
|
|
3c40e9694f | ||
|
|
30358a7582 | ||
|
|
20880ffd71 | ||
|
|
e48cc006f6 | ||
|
|
accb56be7f | ||
|
|
a1ea79e382 | ||
|
|
a1f10ef54b | ||
|
|
fbc39e82a1 | ||
|
|
0214827492 | ||
|
|
6dc156712e | ||
|
|
53f671e8af | ||
|
|
a15e7d1f6e | ||
|
|
e836371887 | ||
|
|
53b596d435 | ||
|
|
0dbd4417af | ||
|
|
d1b1a98a0a | ||
|
|
ccc4f5a114 | ||
|
|
03a0cce119 | ||
|
|
26b3ec0b2a | ||
|
|
7992509fde | ||
|
|
b208cae333 | ||
|
|
9749c65065 | ||
|
|
9109642821 | ||
|
|
b6503748ca | ||
|
|
0284d040dc | ||
|
|
d8c13c9ac2 | ||
|
|
be7008f253 | ||
|
|
63bc2d6dbb | ||
|
|
3d0d130622 | ||
|
|
8c3225914c | ||
|
|
798e3ce86b | ||
|
|
49c79f156a | ||
|
|
d3634c1180 | ||
|
|
c345ee9e5a | ||
|
|
12f080e803 | ||
|
|
2b01de5ecf | ||
|
|
61102845ed | ||
|
|
ca3b28fda7 | ||
|
|
854158a9c2 | ||
|
|
11bcd84738 | ||
|
|
da3f1a21a2 | ||
|
|
2edb93ef54 | ||
|
|
0f0353674d | ||
|
|
62b0789e5f | ||
|
|
26d0f993ba | ||
|
|
a653e5a406 | ||
|
|
0fd0240be7 | ||
|
|
3ea9df89a9 | ||
|
|
3c463419ca | ||
|
|
dd292c1d52 | ||
|
|
801ef3281a | ||
|
|
8f29bf0a5a | ||
|
|
6f752e68c8 | ||
|
|
12978a4343 | ||
|
|
b543e22eee | ||
|
|
599faa3cb5 | ||
|
|
d7b616773a | ||
|
|
633ddb70e7 | ||
|
|
d0a6a8ae3b | ||
|
|
60bb05e6ca | ||
|
|
0219aa1e4c | ||
|
|
34260b7428 | ||
|
|
d6bd02ea7d | ||
|
|
7699dd2701 | ||
|
|
2d86b29b6c | ||
|
|
67db515fe1 | ||
|
|
df61879fb1 | ||
|
|
77259320cf | ||
|
|
e0dff8104d | ||
|
|
9816fdd842 | ||
|
|
9d4cc19e62 | ||
|
|
06288fa07b | ||
|
|
735486c36e | ||
|
|
d783c6200e | ||
|
|
e307916233 | ||
|
|
01154f18dd | ||
|
|
716972347d | ||
|
|
4b8c6f338a | ||
|
|
170afedd19 | ||
|
|
8881ba2d72 | ||
|
|
1cff76922b | ||
|
|
c0f49763f1 | ||
|
|
87aaaf0b06 | ||
|
|
ec309e7e02 | ||
|
|
22b25a0d9e | ||
|
|
8126110312 | ||
|
|
f8eb56d6b7 | ||
|
|
21b1ad5e0a | ||
|
|
5eddacfee9 | ||
|
|
c8023c6651 | ||
|
|
7b30cc59b8 | ||
|
|
581d64614e | ||
|
|
0d309d058c | ||
|
|
11559cba50 | ||
|
|
e02d3748a9 | ||
|
|
236d67f78d | ||
|
|
4dc4c56921 | ||
|
|
cec3df71ca | ||
|
|
63aa303673 | ||
|
|
3e68cc601a | ||
|
|
918e3d17f6 | ||
|
|
c78d1c90e4 | ||
|
|
3fc273070e | ||
|
|
7aa7df43aa | ||
|
|
d65926f3b8 | ||
|
|
5881ef3e11 | ||
|
|
f8da693fd0 | ||
|
|
b413636bf5 | ||
|
|
a1dca73ad3 | ||
|
|
32b7c464c7 | ||
|
|
d5e0e73369 | ||
|
|
b3375d4279 | ||
|
|
4f56170b4c | ||
|
|
42d30a5a70 | ||
|
|
9dbd19f715 | ||
|
|
48d39bf977 | ||
|
|
a8e588216b | ||
|
|
c02d532001 | ||
|
|
0f67feb9dd | ||
|
|
b2719ff40a | ||
|
|
8613452f4f | ||
|
|
b249809d2e | ||
|
|
4ea7808d5f | ||
|
|
ae1b98e38f | ||
|
|
d0ca9a655d | ||
|
|
8d5d786452 | ||
|
|
deaf2f9273 | ||
|
|
db37053f58 | ||
|
|
dd75864749 | ||
|
|
98629ac6f8 | ||
|
|
fa39ec1c98 | ||
|
|
9a73936002 | ||
|
|
7f33593931 | ||
|
|
88fb1ec747 | ||
|
|
a3e437f82a | ||
|
|
64065a4573 | ||
|
|
1acfe1502a | ||
|
|
4b78857b51 | ||
|
|
735009cdd7 | ||
|
|
166e00f2e8 | ||
|
|
3552f9e4aa | ||
|
|
01ddafa83a | ||
|
|
1bfad2e198 | ||
|
|
3e88cb3b76 | ||
|
|
273e706a42 | ||
|
|
baa0e453b6 | ||
|
|
898fcf8fd5 | ||
|
|
3b59bd92fc | ||
|
|
0a9b82a6f4 | ||
|
|
8a4ef40e9e | ||
|
|
e85c2c9826 | ||
|
|
15968e9783 | ||
|
|
97dd7470ce | ||
|
|
e583dc60ae | ||
|
|
9607acdb6a | ||
|
|
0ee71b09cd | ||
|
|
05bd92d765 | ||
|
|
cd68462cf3 | ||
|
|
9442601e42 | ||
|
|
4b1fc788cf | ||
|
|
94d8dbeeee | ||
|
|
3cb7e7c1bf | ||
|
|
9ce8e16063 | ||
|
|
c2ca1b2731 | ||
|
|
d47bf103b0 | ||
|
|
dfacae9beb | ||
|
|
6794166b58 | ||
|
|
8583d35153 | ||
|
|
2cb37827a8 | ||
|
|
158eec7999 | ||
|
|
00043423c2 |
No files matched your search
@@ -9,6 +9,12 @@
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
language: java
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
sudo: false
|
||||
|
||||
after_script: 'misc/.travis/after.sh'
|
||||
@@ -1,4 +1,39 @@
|
||||
keycloak
|
||||
========
|
||||
|
||||
SSO Service for web apps and REST services.
|
||||
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
|
||||
and video tutorials.
|
||||
|
||||
Keycloak is an SSO Service for web apps and REST services.
|
||||
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with support for SAML 2.0 as well.
|
||||
Here's some of the features:
|
||||
|
||||
* SSO and Single Log Out for browser applications
|
||||
* Social Broker. Enable Google, Facebook, Yahoo, Twitter, GitHub, LinkedIn social login with no code required.
|
||||
* Optional LDAP/Active Directory integration
|
||||
* Optional User Registration
|
||||
* Password and TOTP support (via Google Authenticator or FreeOTP). Client cert auth coming soon.
|
||||
* User session management from both admin and user perspective
|
||||
* Customizable themes for user facing pages: login, grant pages, account management, emails, and admin console all customizable!
|
||||
* OAuth Bearer token auth for REST Services
|
||||
* Integrated Browser App to REST Service token propagation
|
||||
* Admin REST API
|
||||
* OAuth 2.0 Grant requests
|
||||
* CORS Support
|
||||
* CORS Web Origin management and validation
|
||||
* Completely centrally managed user and role mapping metadata. Minimal configuration at the application side
|
||||
* Admin Console for managing users, roles, role mappings, applications, user sessions, allowed CORS web origins, and OAuth clients.
|
||||
* Deployable as a WAR, appliance, or an Openshift cloud service (SaaS).
|
||||
* Supports JBoss AS7, EAP 6.x, Wildfly, Tomcat, and Jetty applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
|
||||
* Javascript/HTML 5 adapter for pure Javascript apps
|
||||
* Session management from admin console
|
||||
* Revocation policies
|
||||
* Password policies
|
||||
* OpenID Connect Support
|
||||
* SAML Support
|
||||
* Token claim and SAML assertion mappings, role name mappings, etc. Ability to configure exactly what information you want in your tokens and SAML documents
|
||||
* IDP brokering or chaining. You can set up Keycloak to be a child IDP to another SAML or OIDC IDP.
|
||||
* Kerberos bridging. Logged in Kerberos users can access Keycloak SAML or OIDC applications via our Kerberos bridge.
|
||||
|
||||
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
|
||||
and video tutorials.
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-admin-ui-styles</artifactId>
|
||||
<name>Keycloak Admin UI Styles</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
</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>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,71 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Clearfix */
|
||||
|
||||
.clearfix:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
line-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
html[xmlns] .clearfix {
|
||||
display: block;
|
||||
}
|
||||
|
||||
* html .clearfix {
|
||||
height: 1%;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="23px" height="14px" viewBox="0 0 23 14" enable-background="new 0 0 23 14" xml:space="preserve">
|
||||
<polyline fill="none" stroke="#009AD8" stroke-miterlimit="10" points="1,13 7,7 1,1 "/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 573 B |
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="22px" height="52px" viewBox="0 0 22 52" enable-background="new 0 0 22 52" xml:space="preserve">
|
||||
<polygon fill="#828487" points="11,16 6.818,11.818 7.525,11.111 11,14.586 14.475,11.111 15.182,11.818 "/>
|
||||
<polygon fill="#DBDADA" points="11,42 6.818,37.818 7.525,37.111 11,40.586 14.475,37.111 15.182,37.818 "/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 268 KiB |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd ">
|
||||
</web-fragment>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.0-alpha-2</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-admin-ui</artifactId>
|
||||
<name>Keycloak Admin UI</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
</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>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,107 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Keycloak Admin Console</title>
|
||||
|
||||
<link rel="icon" href="/auth/admin-ui/img/favicon.ico">
|
||||
|
||||
<!-- Frameworks -->
|
||||
<link rel="stylesheet" href="/auth/admin-ui/patternfly/css/patternfly.css">
|
||||
|
||||
<!-- Page styles -->
|
||||
<link rel="stylesheet" href="/auth/admin-ui/css/admin-console.css">
|
||||
<link rel="stylesheet" href="/auth/admin-ui/css/tables.css">
|
||||
<link rel="stylesheet" href="/auth/admin-ui/css/sprites.css">
|
||||
<link href="lib/select2-3.4.1/select2.css" rel="stylesheet">
|
||||
|
||||
<script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
|
||||
<script src="lib/select2-3.4.1/select2.js" type="text/javascript"></script>
|
||||
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
||||
|
||||
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
|
||||
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
|
||||
<script src="lib/angular/select2.js" type="text/javascript"></script>
|
||||
<script src="lib/fileupload/angular-file-upload.min.js"></script>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers/realm.js"></script>
|
||||
<script src="js/controllers/applications.js"></script>
|
||||
<script src="js/controllers/oauth-clients.js"></script>
|
||||
<script src="js/controllers/users.js"></script>
|
||||
<script src="js/loaders.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
|
||||
<style>
|
||||
[ng\:cloak], [ng-cloak], .ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="admin-console" data-ng-controller="GlobalCtrl" data-ng-cloak>
|
||||
<div id="idletimeout">
|
||||
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
|
||||
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
|
||||
</div>
|
||||
|
||||
<div class="feedback-aligner" data-ng-show="notification" data-ng-click="notification = null">
|
||||
<div class="alert alert-{{notification.type}}">
|
||||
<span class="pficon pficon-ok" ng-show="notification.type == 'success'"></span>
|
||||
<span class="pficon pficon-info" ng-show="notification.type == 'info'"></span>
|
||||
<span class="pficon-layered" ng-show="notification.type == 'danger'">
|
||||
<span class="pficon pficon-error-octagon"></span>
|
||||
<span class="pficon pficon-error-exclamation"></span>
|
||||
</span>
|
||||
<span class="pficon-layered" ng-show="notification.type == 'warning'">
|
||||
<span class="pficon pficon-warning-triangle"></span>
|
||||
<span class="pficon pficon-warning-exclamation"></span>
|
||||
</span>
|
||||
<strong>{{notification.header}}</strong> {{notification.message}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header class="navbar navbar-default navbar-pf navbar-main header">
|
||||
<div class="container">
|
||||
<div data-ng-include data-src="'partials/menu.html'"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div data-ng-view id="view"></div>
|
||||
<div id="loading" class="loading-backdrop">
|
||||
<div class="loading">
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$.idleTimeout('#idletimeout', '#idletimeout a', {
|
||||
idleAfter: 300,
|
||||
pollingInterval: 60,
|
||||
keepAliveURL: '/auth/rest/admin/keepalive',
|
||||
serverResponseEquals: '',
|
||||
failedRequests: 1,
|
||||
onTimeout: function(){
|
||||
$(this).slideUp();
|
||||
window.location = "/auth/rest/admin/logout";
|
||||
},
|
||||
onIdle: function(){
|
||||
$(this).slideDown(); // show the warning bar
|
||||
},
|
||||
onCountdown: function( counter ){
|
||||
$(this).find("span").html( counter ); // update the counter
|
||||
},
|
||||
onResume: function(){
|
||||
$(this).slideUp(); // hide the warning bar
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,191 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
.mock_view {
|
||||
padding: 2em 2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<title>Keycloak Admin Console</title>
|
||||
|
||||
<link rel="icon" href="/auth/admin-ui/img/favicon.ico">
|
||||
|
||||
<!-- Frameworks -->
|
||||
<!--link rel="stylesheet" href="/auth/admin-ui/bootstrap/css/bootstrap.css"-->
|
||||
<link rel="stylesheet" href="/auth/admin-ui/patternfly/css/patternfly.css">
|
||||
|
||||
<!-- Page styles -->
|
||||
<link rel="stylesheet" href="/auth/admin-ui/css/admin-console.css">
|
||||
<link href="lib/select2-3.4.1/select2.css" rel="stylesheet">
|
||||
|
||||
<script src="lib/jquery/jquery-1.10.2.js" type="text/javascript"></script>
|
||||
<script src="lib/select2-3.4.1/select2.js" type="text/javascript"></script>
|
||||
|
||||
<script src="lib/angular/angular.js"></script>
|
||||
<script src="lib/angular/angular-resource.js"></script>
|
||||
<script src="lib/angular/ui-bootstrap-tpls-0.4.0.js"></script>
|
||||
|
||||
<script src="lib/jquery/jquery.idletimer.js" type="text/javascript"></script>
|
||||
<script src="lib/jquery/jquery.idletimeout.js" type="text/javascript"></script>
|
||||
<script src="lib/angular/select2.js" type="text/javascript"></script>
|
||||
<script src="lib/fileupload/angular-file-upload.min.js"></script> <!--
|
||||
<script src="/auth/admin-ui/bootstrap-3.0.0-wip/js/dropdown.js"></script>
|
||||
<script src="/auth/admin-ui/bootstrap-3.0.0-wip/js/tooltip.js"></script>
|
||||
-->
|
||||
<script src="/auth/admin-ui/js/bootstrap-tokenfield.js"></script>
|
||||
|
||||
<script src="/auth/admin-ui/bootstrap/js/bootstrap.js"></script>
|
||||
|
||||
<script src="js/app.js"></script>
|
||||
<script src="js/controllers/realm.js"></script>
|
||||
<script src="js/controllers/applications.js"></script>
|
||||
<script src="js/controllers/oauth-clients.js"></script>
|
||||
<script src="js/controllers/users.js"></script>
|
||||
<script src="js/loaders.js"></script>
|
||||
<script src="js/services.js"></script>
|
||||
|
||||
<style>
|
||||
[ng\:cloak], [ng-cloak], .ng-cloak {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="admin-console" data-ng-controller="GlobalCtrl" ng-cloak>
|
||||
<div id="idletimeout">
|
||||
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
|
||||
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
|
||||
</div>
|
||||
|
||||
|
||||
<div class="feedback-aligner" data-ng-show="notification" data-ng-click="notification = null">
|
||||
<div class="alert alert-{{notification.type}}"><strong>{{notification.header}}</strong> {{notification.message}}</div>
|
||||
</div>
|
||||
|
||||
<header class="navbar navbar-default navbar-pf navbar-main header">
|
||||
<div class="container">
|
||||
<nav id="kc-navigation" class="navbar" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<h1 class="navbar-title"><Strong>KeyCloak</Strong> Central Login</h1>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-collapse-1">
|
||||
<ul class="nav navbar-nav navbar-utility" data-ng-show="auth.loggedIn">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
|
||||
<span class="pficon pficon-user"></span>
|
||||
{{auth.user.displayName}}<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/auth/rest/realms/keycloak-admin/account?referrer=admin-console">Manage Account</a></li>
|
||||
<li class="separator"><a href="/auth/rest/admin/logout">Sign Out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-primary persistent-secondary" data-ng-controller="RealmDropdownCtrl">
|
||||
<li class="dropdown context" data-ng-show="showNav()">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
Realm: {{current.realm.id}}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="realm in current.realms">
|
||||
<a href="" ng-click="changeRealm(realm.id)" >{{realm.id}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
||||
<!--------------------------------------->
|
||||
<li class="active">
|
||||
<a href="#">Settings</a>
|
||||
<ul class="nav navbar-nav navbar-persistent">
|
||||
<li class="active">
|
||||
<a href="#">General</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Default Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Credentials</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Token</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Keys</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Email</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">User</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Applications</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">OAuth Clients</a>
|
||||
</li>
|
||||
<!--------------------------------------->
|
||||
|
||||
|
||||
<li class="active pull-right" data-ng-show="auth.loggedIn">
|
||||
<a class="button primary" href="#/create/realm" data-ng-class="path[0] == 'create' && path[1] == 'realm' && 'active'"
|
||||
data-ng-show="auth.loggedIn">Add Realm</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="mock_view" data-ng-view id="view"></div>
|
||||
<div id="loading" class="loading-backdrop">
|
||||
<div class="loading">
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$.idleTimeout('#idletimeout', '#idletimeout a', {
|
||||
idleAfter: 300,
|
||||
pollingInterval: 60,
|
||||
keepAliveURL: '/auth/rest/admin/keepalive',
|
||||
serverResponseEquals: '',
|
||||
failedRequests: 1,
|
||||
onTimeout: function(){
|
||||
$(this).slideUp();
|
||||
window.location = "/auth/rest/admin/logout";
|
||||
},
|
||||
onIdle: function(){
|
||||
$(this).slideDown(); // show the warning bar
|
||||
},
|
||||
onCountdown: function( counter ){
|
||||
$(this).find("span").html( counter ); // update the counter
|
||||
},
|
||||
onResume: function(){
|
||||
$(this).slideUp(); // hide the warning bar
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,870 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('keycloak', [ 'keycloak.services', 'keycloak.loaders', 'ui.bootstrap', 'ui.select2', 'angularFileUpload' ]);
|
||||
var resourceRequests = 0;
|
||||
var loadingTimer = -1;
|
||||
|
||||
angular.element(document).ready(function ($http) {
|
||||
$http.get('/auth/rest/admin/whoami').success(function(data) {
|
||||
var auth = {};
|
||||
auth.user = data;
|
||||
auth.loggedIn = true;
|
||||
|
||||
module.factory('Auth', function() {
|
||||
return auth;
|
||||
});
|
||||
angular.bootstrap(document, ["keycloak"]);
|
||||
}).error(function() {
|
||||
var path = window.location.hash && window.location.hash.substring(1) || '/';
|
||||
window.location = '/auth/rest/admin/login?path=' + path;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.config([ '$routeProvider', function($routeProvider) {
|
||||
|
||||
$routeProvider
|
||||
/*
|
||||
.when('/create/realm', {
|
||||
templateUrl : 'partials/realm-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'RealmDetailCtrl'
|
||||
})
|
||||
*/
|
||||
.when('/create/realm', {
|
||||
templateUrl : 'partials/realm-create.html',
|
||||
resolve : {
|
||||
|
||||
},
|
||||
controller : 'RealmCreateCtrl'
|
||||
})
|
||||
.when('/realms/:realm', {
|
||||
templateUrl : 'partials/realm-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmDetailCtrl'
|
||||
})
|
||||
.when('/realms', {
|
||||
templateUrl : 'partials/realm-list.html',
|
||||
controller : 'RealmListCtrl'
|
||||
})
|
||||
.when('/realms/:realm/token-settings', {
|
||||
templateUrl : 'partials/realm-tokens.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmTokenDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/keys-settings', {
|
||||
templateUrl : 'partials/realm-keys.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmKeysDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/social-settings', {
|
||||
templateUrl : 'partials/realm-social.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmSocialCtrl'
|
||||
})
|
||||
.when('/realms/:realm/default-roles', {
|
||||
templateUrl : 'partials/realm-default-roles.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmDefaultRolesCtrl'
|
||||
})
|
||||
.when('/realms/:realm/required-credentials', {
|
||||
templateUrl : 'partials/realm-credentials.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmRequiredCredentialsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/smtp-settings', {
|
||||
templateUrl : 'partials/realm-smtp.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmSMTPSettingsCtrl'
|
||||
})
|
||||
.when('/create/user/:realm', {
|
||||
templateUrl : 'partials/user-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'UserDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users/:user', {
|
||||
templateUrl : 'partials/user-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users/:user/user-credentials', {
|
||||
templateUrl : 'partials/user-credentials.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserCredentialsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users/:user/role-mappings', {
|
||||
templateUrl : 'partials/role-mappings.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserRoleMappingCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users', {
|
||||
templateUrl : 'partials/user-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserListCtrl'
|
||||
})
|
||||
|
||||
.when('/create/role/:realm', {
|
||||
templateUrl : 'partials/role-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
role : function() {
|
||||
return {};
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RoleDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/roles/:role', {
|
||||
templateUrl : 'partials/role-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
role : function(RoleLoader) {
|
||||
return RoleLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RoleDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/roles', {
|
||||
templateUrl : 'partials/role-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RoleListCtrl'
|
||||
})
|
||||
|
||||
.when('/create/role/:realm/applications/:application', {
|
||||
templateUrl : 'partials/application-role-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
role : function() {
|
||||
return {};
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationRoleDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/roles/:role', {
|
||||
templateUrl : 'partials/application-role-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
role : function(ApplicationRoleLoader) {
|
||||
return ApplicationRoleLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationRoleDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/credentials', {
|
||||
templateUrl : 'partials/application-credentials.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationCredentialsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/roles', {
|
||||
templateUrl : 'partials/application-role-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
roles : function(ApplicationRoleListLoader) {
|
||||
return ApplicationRoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationRoleListCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/scope-mappings', {
|
||||
templateUrl : 'partials/application-scope-mappings.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationScopeMappingCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/installation', {
|
||||
templateUrl : 'partials/application-installation.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationInstallationCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application/sessions', {
|
||||
templateUrl : 'partials/application-sessions.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationSessionsCtrl'
|
||||
})
|
||||
.when('/create/application/:realm', {
|
||||
templateUrl : 'partials/application-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
application : function() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications/:application', {
|
||||
templateUrl : 'partials/application-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
application : function(ApplicationLoader) {
|
||||
return ApplicationLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/applications', {
|
||||
templateUrl : 'partials/application-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'ApplicationListCtrl'
|
||||
})
|
||||
|
||||
// OAUTH Client
|
||||
|
||||
.when('/realms/:realm/oauth-clients/:oauth/credentials', {
|
||||
templateUrl : 'partials/oauth-client-credentials.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauth : function(OAuthClientLoader) {
|
||||
return OAuthClientLoader();
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientCredentialsCtrl'
|
||||
})
|
||||
.when('/realms/:realm/oauth-clients/:oauth/scope-mappings', {
|
||||
templateUrl : 'partials/oauth-client-scope-mappings.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauth : function(OAuthClientLoader) {
|
||||
return OAuthClientLoader();
|
||||
},
|
||||
applications : function(ApplicationListLoader) {
|
||||
return ApplicationListLoader();
|
||||
},
|
||||
roles : function(RoleListLoader) {
|
||||
return RoleListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientScopeMappingCtrl'
|
||||
})
|
||||
.when('/realms/:realm/oauth-clients/:oauth/installation', {
|
||||
templateUrl : 'partials/oauth-client-installation.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauth : function(OAuthClientLoader) {
|
||||
return OAuthClientLoader();
|
||||
},
|
||||
installation : function(OAuthClientInstallationLoader) {
|
||||
return OAuthClientInstallationLoader();
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientInstallationCtrl'
|
||||
})
|
||||
.when('/create/oauth-client/:realm', {
|
||||
templateUrl : 'partials/oauth-client-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauth : function() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/oauth-clients/:oauth', {
|
||||
templateUrl : 'partials/oauth-client-detail.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauth : function(OAuthClientLoader) {
|
||||
return OAuthClientLoader();
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/oauth-clients', {
|
||||
templateUrl : 'partials/oauth-client-list.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
oauthClients : function(OAuthClientListLoader) {
|
||||
return OAuthClientListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'OAuthClientListCtrl'
|
||||
})
|
||||
|
||||
.when('/', {
|
||||
templateUrl : 'partials/home.html',
|
||||
controller : 'HomeCtrl'
|
||||
})
|
||||
.when('/mocks/:realm', {
|
||||
templateUrl : 'partials/realm-detail_mock.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'RealmDetailCtrl'
|
||||
})
|
||||
.otherwise({
|
||||
templateUrl : 'partials/notfound.html'
|
||||
});
|
||||
} ]);
|
||||
|
||||
module.config(function($httpProvider) {
|
||||
$httpProvider.responseInterceptors.push('errorInterceptor');
|
||||
|
||||
var spinnerFunction = function(data, headersGetter) {
|
||||
if (resourceRequests == 0) {
|
||||
loadingTimer = window.setTimeout(function() {
|
||||
$('#loading').show();
|
||||
loadingTimer = -1;
|
||||
}, 500);
|
||||
}
|
||||
resourceRequests++;
|
||||
return data;
|
||||
};
|
||||
$httpProvider.defaults.transformRequest.push(spinnerFunction);
|
||||
|
||||
$httpProvider.responseInterceptors.push('spinnerInterceptor');
|
||||
|
||||
});
|
||||
|
||||
module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Auth, Notifications) {
|
||||
return function(promise) {
|
||||
return promise.then(function(response) {
|
||||
return response;
|
||||
}, function(response) {
|
||||
if (response.status == 401) {
|
||||
console.log('session timeout?');
|
||||
Auth.loggedIn = false;
|
||||
window.location = '/auth/rest/admin/login?path=' + $location.path();
|
||||
} else if (response.status == 404) {
|
||||
Notifications.error("Not found");
|
||||
} else if (response.status) {
|
||||
if (response.data && response.data.errorMessage) {
|
||||
Notifications.error(response.data.errorMessage);
|
||||
} else {
|
||||
Notifications.error("An unexpected server error has occurred");
|
||||
}
|
||||
}
|
||||
return $q.reject(response);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location) {
|
||||
return function(promise) {
|
||||
return promise.then(function(response) {
|
||||
resourceRequests--;
|
||||
if (resourceRequests == 0) {
|
||||
if(loadingTimer != -1) {
|
||||
window.clearTimeout(loadingTimer);
|
||||
loadingTimer = -1;
|
||||
}
|
||||
$('#loading').hide();
|
||||
}
|
||||
return response;
|
||||
}, function(response) {
|
||||
resourceRequests--;
|
||||
if (resourceRequests == 0) {
|
||||
if(loadingTimer != -1) {
|
||||
window.clearTimeout(loadingTimer);
|
||||
loadingTimer = -1;
|
||||
}
|
||||
$('#loading').hide();
|
||||
}
|
||||
|
||||
return $q.reject(response);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// collapsable form fieldsets
|
||||
module.directive('collapsable', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.click(function() {
|
||||
$(this).toggleClass('collapsed');
|
||||
$(this).find('.toggle-icons').toggleClass('kc-icon-collapse').toggleClass('kc-icon-expand');
|
||||
$(this).find('.toggle-icons').text($(this).text() == "Icon: expand" ? "Icon: collapse" : "Icon: expand");
|
||||
$(this).parent().find('.form-group').toggleClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// collapsable form fieldsets
|
||||
module.directive('uncollapsed', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.prepend('<span class="kc-icon-collapse toggle-icons">Icon: collapse</span>');
|
||||
element.click(function() {
|
||||
$(this).toggleClass('collapsed');
|
||||
$(this).find('.toggle-icons').toggleClass('kc-icon-collapse').toggleClass('kc-icon-expand');
|
||||
$(this).find('.toggle-icons').text($(this).text() == "Icon: expand" ? "Icon: collapse" : "Icon: expand");
|
||||
$(this).parent().find('.form-group').toggleClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// collapsable form fieldsets
|
||||
module.directive('collapsed', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.prepend('<span class="kc-icon-expand toggle-icons">Icon: expand</span>');
|
||||
element.parent().find('.form-group').toggleClass('hidden');
|
||||
element.click(function() {
|
||||
$(this).toggleClass('collapsed');
|
||||
$(this).find('.toggle-icons').toggleClass('kc-icon-collapse').toggleClass('kc-icon-expand');
|
||||
$(this).find('.toggle-icons').text($(this).text() == "Icon: expand" ? "Icon: collapse" : "Icon: expand");
|
||||
$(this).parent().find('.form-group').toggleClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Directive for presenting an ON-OFF switch for checkbox.
|
||||
* Usage: <input ng-model="mmm" name="nnn" id="iii" onoffswitch [on-text="ooo" off-text="fff"] />
|
||||
*/
|
||||
module.directive('onoffswitch', function() {
|
||||
return {
|
||||
restrict: "EA",
|
||||
require: 'ngModel',
|
||||
replace: true,
|
||||
scope: {
|
||||
ngModel: '=',
|
||||
ngDisabled: '=',
|
||||
ngBind: '=',
|
||||
name: '=',
|
||||
id: '=',
|
||||
onText: '@onText',
|
||||
offText: '@offText'
|
||||
},
|
||||
compile: function(element, attrs) {
|
||||
if (!attrs.onText) { attrs.onText = "ON"; }
|
||||
if (!attrs.offText) { attrs.offText = "OFF"; }
|
||||
if (!attrs.ngDisabled) { attrs.ngDisabled = false; }
|
||||
|
||||
var html = "<span><div class=\"onoffswitch\" data-ng-class=\"{disabled: ngDisabled}\" tabindex=\"0\" onkeydown=\"var code = event.keyCode || event.which; if (code === 32 || code === 13) { event.stopImmediatePropagation(); event.preventDefault(); $(event.target).find('input').click();}\">" +
|
||||
"<input type=\"checkbox\" data-ng-model=\"ngModel\" ng-disabled=\"ngDisabled\"" +
|
||||
" class=\"onoffswitch-checkbox\" name=\"" + attrs.name + "\" id=\"" + attrs.id + "\">" +
|
||||
"<label for=\"" + attrs.id + "\" class=\"onoffswitch-label\">" +
|
||||
"<span class=\"onoffswitch-inner\">" +
|
||||
"<span class=\"onoffswitch-active\">{{onText}}</span>" +
|
||||
"<span class=\"onoffswitch-inactive\">{{offText}}</span>" +
|
||||
"</span>" +
|
||||
"<span class=\"onoffswitch-switch\"></span>" +
|
||||
"</label>" +
|
||||
"</div></span>";
|
||||
|
||||
element.replaceWith($(html));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcInput', function() {
|
||||
var d = {
|
||||
scope : true,
|
||||
replace : false,
|
||||
link : function(scope, element, attrs) {
|
||||
var form = element.children('form');
|
||||
var label = element.children('label');
|
||||
var input = element.children('input');
|
||||
|
||||
var id = form.attr('name') + '.' + input.attr('name');
|
||||
|
||||
element.attr('class', 'control-group');
|
||||
|
||||
label.attr('class', 'control-label');
|
||||
label.attr('for', id);
|
||||
|
||||
input.wrap('<div class="controls"/>');
|
||||
input.attr('id', id);
|
||||
|
||||
if (!input.attr('placeHolder')) {
|
||||
input.attr('placeHolder', label.text());
|
||||
}
|
||||
|
||||
if (input.attr('required')) {
|
||||
label.append(' <span class="required">*</span>');
|
||||
}
|
||||
}
|
||||
};
|
||||
return d;
|
||||
});
|
||||
|
||||
module.directive('kcEnter', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if (event.which === 13) {
|
||||
scope.$apply(function() {
|
||||
scope.$eval(attrs.kcEnter);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('kcSave', function ($compile, Notifications) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, elem, attr, ctrl) {
|
||||
elem.addClass("btn btn-primary btn-lg");
|
||||
elem.attr("type","submit");
|
||||
elem.bind('click', function() {
|
||||
$scope.$apply(function() {
|
||||
var form = elem.closest('form');
|
||||
if (form && form.attr('name')) {
|
||||
var ngValid = form.find('.ng-valid');
|
||||
if ($scope[form.attr('name')].$valid) {
|
||||
//ngValid.removeClass('error');
|
||||
ngValid.parent().removeClass('has-error');
|
||||
$scope['save']();
|
||||
} else {
|
||||
Notifications.error("Missing or invalid field(s). Please verify the fields in red.")
|
||||
//ngValid.removeClass('error');
|
||||
ngValid.parent().removeClass('has-error');
|
||||
|
||||
var ngInvalid = form.find('.ng-invalid');
|
||||
//ngInvalid.addClass('error');
|
||||
ngInvalid.parent().addClass('has-error');
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcReset', function ($compile, Notifications) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, elem, attr, ctrl) {
|
||||
elem.addClass("btn btn-default btn-lg");
|
||||
elem.attr("type","submit");
|
||||
elem.bind('click', function() {
|
||||
$scope.$apply(function() {
|
||||
var form = elem.closest('form');
|
||||
if (form && form.attr('name')) {
|
||||
form.find('.ng-valid').removeClass('error');
|
||||
form.find('.ng-invalid').removeClass('error');
|
||||
$scope['reset']();
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcCancel', function ($compile, Notifications) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, elem, attr, ctrl) {
|
||||
elem.addClass("btn btn-default btn-lg");
|
||||
elem.attr("type","submit");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcDelete', function ($compile, Notifications) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function ($scope, elem, attr, ctrl) {
|
||||
elem.addClass("btn btn-danger btn-lg");
|
||||
elem.attr("type","submit");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.directive('kcSelect', function ($compile, Notifications) {
|
||||
return {
|
||||
scope: {
|
||||
kcOptions: '=',
|
||||
kcModel: '=',
|
||||
id: "=",
|
||||
kcPlaceholder: '@'
|
||||
},
|
||||
restrict: 'EA',
|
||||
replace: true,
|
||||
templateUrl: 'templates/kc-select.html',
|
||||
link: function(scope, element, attr) {
|
||||
scope.updateModel = function(item) {
|
||||
scope.kcModel = item;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.directive('kcNavigation', function ($compile, Notifications) {
|
||||
return {
|
||||
scope: {
|
||||
kcCurrent: '@',
|
||||
kcRealm: '=',
|
||||
kcSocial: '='
|
||||
},
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'templates/kc-navigation.html',
|
||||
|
||||
compile: function(element, attrs){
|
||||
if (!attrs.kcSocial) {
|
||||
attrs.kcSocial = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Used to select the element (invoke $(elem).select()) on specified action list.
|
||||
* Usages kc-select-action="click mouseover"
|
||||
* When used in the textarea element, this will select/highlight the textarea content on specified action (i.e. click).
|
||||
*/
|
||||
module.directive('kcSelectAction', function ($compile, Notifications) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function (elem, attrs) {
|
||||
|
||||
var events = attrs.kcSelectAction.split(" ");
|
||||
|
||||
for(var i=0; i < events.length; i++){
|
||||
|
||||
elem.bind(events[i], function(){
|
||||
elem.select();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.filter('remove', function() {
|
||||
return function(input, remove, attribute) {
|
||||
if (!input || !remove) {
|
||||
return input;
|
||||
}
|
||||
|
||||
var out = [];
|
||||
for ( var i = 0; i < input.length; i++) {
|
||||
var e = input[i];
|
||||
|
||||
if (Array.isArray(remove)) {
|
||||
for (var j = 0; j < remove.length; j++) {
|
||||
if (attribute) {
|
||||
if (remove[j][attribute] == e[attribute]) {
|
||||
e = null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (remove[j] == e) {
|
||||
e = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (attribute) {
|
||||
if (remove[attribute] == e[attribute]) {
|
||||
e = null;
|
||||
}
|
||||
} else {
|
||||
if (remove == e) {
|
||||
e = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e != null) {
|
||||
out.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
});
|
||||
|
||||
module.filter('capitalize', function() {
|
||||
return function(input) {
|
||||
return input.substring(0, 1).toUpperCase() + input.substring(1);
|
||||
}
|
||||
});
|
||||
@@ -1,352 +0,0 @@
|
||||
module.controller('ApplicationRoleListCtrl', function($scope, $location, realm, application, roles) {
|
||||
$scope.realm = realm;
|
||||
$scope.roles = roles;
|
||||
$scope.application = application;
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('ApplicationCredentialsCtrl', function($scope, $location, realm, application, ApplicationCredentials, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.application = application;
|
||||
var secret = ApplicationCredentials.get({ realm : realm.realm, application : application.name },
|
||||
function() {
|
||||
$scope.secret = secret.value;
|
||||
}
|
||||
);
|
||||
|
||||
$scope.changePassword = function() {
|
||||
var secret = ApplicationCredentials.update({ realm : realm.realm, application : application.name },
|
||||
function() {
|
||||
Notifications.success('The secret has been changed.');
|
||||
$scope.secret = secret.value;
|
||||
},
|
||||
function() {
|
||||
Notifications.error("The secret was not changed due to a problem.");
|
||||
$scope.secret = "error";
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('ApplicationSessionsCtrl', function($scope, $location, realm, application) {
|
||||
$scope.realm = realm;
|
||||
$scope.application = application;
|
||||
});
|
||||
|
||||
module.controller('ApplicationRoleDetailCtrl', function($scope, realm, application, role, roles, applications,
|
||||
Role, ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
|
||||
$http, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.application = application;
|
||||
$scope.role = angular.copy(role);
|
||||
$scope.create = !role.name;
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.create) {
|
||||
ApplicationRole.save({
|
||||
realm: realm.realm,
|
||||
application : application.name
|
||||
}, $scope.role, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
role = angular.copy($scope.role);
|
||||
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/applications/" + application.name + "/roles/" + id);
|
||||
Notifications.success("The role has been created.");
|
||||
});
|
||||
} else {
|
||||
$scope.update();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
Dialog.confirmDelete($scope.role.name, 'role', function() {
|
||||
$scope.role.$remove({
|
||||
realm : realm.realm,
|
||||
application : application.name,
|
||||
role : $scope.role.name
|
||||
}, function() {
|
||||
$location.url("/realms/" + realm.realm + "/applications/" + application.name + "/roles");
|
||||
Notifications.success("The role has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$location.url("/realms/" + realm.realm + "/applications/" + application.name + "/roles");
|
||||
};
|
||||
|
||||
|
||||
roleControl($scope, realm, role, roles, applications,
|
||||
ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
|
||||
$http, $location, Notifications, Dialog);
|
||||
|
||||
});
|
||||
|
||||
module.controller('ApplicationListCtrl', function($scope, realm, applications, Application, $location) {
|
||||
console.log('ApplicationListCtrl');
|
||||
$scope.realm = realm;
|
||||
$scope.applications = applications;
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('ApplicationInstallationCtrl', function($scope, realm, application, ApplicationInstallation,ApplicationInstallationJBoss, $http, $routeParams) {
|
||||
console.log('ApplicationInstallationCtrl');
|
||||
$scope.realm = realm;
|
||||
$scope.application = application;
|
||||
$scope.installation = null;
|
||||
$scope.download = null;
|
||||
$scope.configFormat = null;
|
||||
|
||||
$scope.configFormats = [
|
||||
"keycloak.json",
|
||||
"Wildfly/JBoss Subsystem XML"
|
||||
];
|
||||
|
||||
$scope.changeFormat = function() {
|
||||
if ($scope.configFormat == "keycloak.json") {
|
||||
var url = ApplicationInstallation.url({ realm: $routeParams.realm, application: $routeParams.application });
|
||||
var installation = $http.get(url).success(function(data) {
|
||||
var tmp = angular.fromJson(data);
|
||||
$scope.installation = angular.toJson(tmp, true);
|
||||
})
|
||||
$scope.download = url;
|
||||
} else if ($scope.configFormat == "Wildfly/JBoss Subsystem XML") {
|
||||
var url = ApplicationInstallationJBoss.url({ realm: $routeParams.realm, application: $routeParams.application });
|
||||
var installation = $http.get(url).success(function(data) {
|
||||
$scope.installation = data;
|
||||
})
|
||||
$scope.download = url;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.controller('ApplicationDetailCtrl', function($scope, realm, application, Application, $location, Dialog, Notifications) {
|
||||
console.log('ApplicationDetailCtrl');
|
||||
|
||||
$scope.realm = realm;
|
||||
$scope.create = !application.name;
|
||||
if (!$scope.create) {
|
||||
$scope.application= angular.copy(application);
|
||||
} else {
|
||||
$scope.application = {};
|
||||
$scope.application.webOrigins = [];
|
||||
$scope.application.redirectUris = [];
|
||||
}
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
|
||||
$scope.$watch('application', function() {
|
||||
if (!angular.equals($scope.application, application)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.deleteWebOrigin = function(index) {
|
||||
$scope.application.webOrigins.splice(index, 1);
|
||||
}
|
||||
$scope.addWebOrigin = function() {
|
||||
$scope.application.webOrigins.push($scope.newWebOrigin);
|
||||
$scope.newWebOrigin = "";
|
||||
}
|
||||
$scope.deleteRedirectUri = function(index) {
|
||||
$scope.application.redirectUris.splice(index, 1);
|
||||
}
|
||||
$scope.addRedirectUri = function() {
|
||||
$scope.application.redirectUris.push($scope.newRedirectUri);
|
||||
$scope.newRedirectUri = "";
|
||||
}
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.create) {
|
||||
Application.save({
|
||||
realm: realm.realm,
|
||||
application: ''
|
||||
}, $scope.application, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/applications/" + id);
|
||||
Notifications.success("The application has been created.");
|
||||
});
|
||||
} else {
|
||||
Application.update({
|
||||
realm : realm.realm,
|
||||
application : application.name
|
||||
}, $scope.application, function() {
|
||||
$scope.changed = false;
|
||||
application = angular.copy($scope.application);
|
||||
Notifications.success("Your changes have been saved to the application.");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.application = angular.copy(application);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/applications");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
Dialog.confirmDelete($scope.application.name, 'application', function() {
|
||||
$scope.application.$remove({
|
||||
realm : realm.realm,
|
||||
application : $scope.application.name
|
||||
}, function() {
|
||||
$location.url("/realms/" + realm.realm + "/applications");
|
||||
Notifications.success("The application has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
module.controller('ApplicationScopeMappingCtrl', function($scope, $http, realm, application, roles, applications, ApplicationRealmScopeMapping, ApplicationApplicationScopeMapping, ApplicationRole) {
|
||||
$scope.realm = realm;
|
||||
$scope.application = application;
|
||||
$scope.realmRoles = angular.copy(roles);
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.applications = applications;
|
||||
$scope.applicationRoles = [];
|
||||
$scope.selectedApplicationRoles = [];
|
||||
$scope.selectedApplicationMappings = [];
|
||||
$scope.applicationMappings = [];
|
||||
|
||||
|
||||
|
||||
$scope.realmMappings = ApplicationRealmScopeMapping.query({realm : realm.realm, application : application.name}, function(){
|
||||
for (var i = 0; i < $scope.realmMappings.length; i++) {
|
||||
var role = $scope.realmMappings[i];
|
||||
for (var j = 0; j < $scope.realmRoles.length; j++) {
|
||||
var realmRole = $scope.realmRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.realmRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addRealmRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/applications/' + application.name + '/scope-mappings/realm',
|
||||
$scope.selectedRealmRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
|
||||
var role = $scope.selectedRealmRoles[i];
|
||||
var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
$scope.realmMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectRealmRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/applications/' + application.name + '/scope-mappings/realm',
|
||||
{data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
|
||||
var role = $scope.selectedRealmMappings[i];
|
||||
var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmMappings.splice(idx, 1);
|
||||
$scope.realmRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedRealmMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addApplicationRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/applications/' + application.name + '/scope-mappings/applications/' + $scope.targetApp.name,
|
||||
$scope.selectedApplicationRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
|
||||
var role = $scope.selectedApplicationRoles[i];
|
||||
var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
$scope.applicationMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteApplicationRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/applications/' + application.name + '/scope-mappings/applications/' + $scope.targetApp.name,
|
||||
{data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
|
||||
var role = $scope.selectedApplicationMappings[i];
|
||||
var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationMappings.splice(idx, 1);
|
||||
$scope.applicationRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.changeApplication = function() {
|
||||
if ($scope.targetApp) {
|
||||
$scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.targetApp.name}, function() {
|
||||
$scope.applicationMappings = ApplicationApplicationScopeMapping.query({realm : realm.realm, application : application.name, targetApp : $scope.targetApp.name}, function(){
|
||||
for (var i = 0; i < $scope.applicationMappings.length; i++) {
|
||||
var role = $scope.applicationMappings[i];
|
||||
for (var j = 0; j < $scope.applicationRoles.length; j++) {
|
||||
var realmRole = $scope.applicationRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.applicationRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$scope.applicationRoles = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -1,255 +0,0 @@
|
||||
module.controller('OAuthClientCredentialsCtrl', function($scope, $location, realm, oauth, OAuthClientCredentials, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.oauth = oauth;
|
||||
|
||||
var secret = OAuthClientCredentials.get({ realm : realm.realm, oauth : oauth.id },
|
||||
function() {
|
||||
$scope.secret = secret.value;
|
||||
}
|
||||
);
|
||||
|
||||
$scope.changePassword = function() {
|
||||
var secret = OAuthClientCredentials.update({ realm : realm.realm, oauth : oauth.id },
|
||||
function() {
|
||||
Notifications.success('The secret has been changed.');
|
||||
$scope.secret = secret.value;
|
||||
},
|
||||
function() {
|
||||
Notifications.error("The secret was not changed due to a problem.");
|
||||
$scope.secret = "error";
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.controller('OAuthClientListCtrl', function($scope, realm, oauthClients, OAuthClient, $location) {
|
||||
$scope.realm = realm;
|
||||
$scope.oauthClients = oauthClients;
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('OAuthClientDetailCtrl', function($scope, realm, oauth, OAuthClient, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.create = !oauth.id;
|
||||
if (!$scope.create) {
|
||||
$scope.oauth= angular.copy(oauth);
|
||||
} else {
|
||||
$scope.oauth = {};
|
||||
$scope.oauth.webOrigins = [];
|
||||
$scope.oauth.redirectUris = [];
|
||||
}
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
|
||||
$scope.$watch('oauth', function() {
|
||||
if (!angular.equals($scope.oauth, oauth)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.deleteWebOrigin = function(index) {
|
||||
$scope.oauth.webOrigins.splice(index, 1);
|
||||
}
|
||||
$scope.addWebOrigin = function() {
|
||||
$scope.oauth.webOrigins.push($scope.newWebOrigin);
|
||||
$scope.newWebOrigin = "";
|
||||
}
|
||||
$scope.deleteRedirectUri = function(index) {
|
||||
$scope.oauth.redirectUris.splice(index, 1);
|
||||
}
|
||||
$scope.addRedirectUri = function() {
|
||||
$scope.oauth.redirectUris.push($scope.newRedirectUri);
|
||||
$scope.newRedirectUri = "";
|
||||
}
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.create) {
|
||||
OAuthClient.save({
|
||||
realm: realm.realm
|
||||
}, $scope.oauth, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/oauth-clients/" + id);
|
||||
Notifications.success("The oauth client has been created.");
|
||||
});
|
||||
} else {
|
||||
OAuthClient.update({
|
||||
realm : realm.realm,
|
||||
id : oauth.id
|
||||
}, $scope.oauth, function() {
|
||||
$scope.changed = false;
|
||||
oauth = angular.copy($scope.oauth);
|
||||
Notifications.success("Your changes have been saved to the oauth client.");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.oauth = angular.copy(oauth);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/oauth-clients");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
Dialog.confirmDelete($scope.oauth.name, 'oauth', function() {
|
||||
$scope.oauth.$remove({
|
||||
realm : realm.realm,
|
||||
id : $scope.oauth.id
|
||||
}, function() {
|
||||
$location.url("/realms/" + realm.realm + "/oauth-clients");
|
||||
Notifications.success("The oauth client has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
module.controller('OAuthClientScopeMappingCtrl', function($scope, $http, realm, oauth, roles, applications, OAuthClientRealmScopeMapping, OAuthClientApplicationScopeMapping, ApplicationRole) {
|
||||
$scope.realm = realm;
|
||||
$scope.oauth = oauth;
|
||||
$scope.realmRoles = angular.copy(roles);
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.applications = applications;
|
||||
$scope.applicationRoles = [];
|
||||
$scope.selectedApplicationRoles = [];
|
||||
$scope.selectedApplicationMappings = [];
|
||||
$scope.applicationMappings = [];
|
||||
|
||||
|
||||
|
||||
$scope.realmMappings = OAuthClientRealmScopeMapping.query({realm : realm.realm, oauth : oauth.id}, function(){
|
||||
for (var i = 0; i < $scope.realmMappings.length; i++) {
|
||||
var role = $scope.realmMappings[i];
|
||||
for (var j = 0; j < $scope.realmRoles.length; j++) {
|
||||
var realmRole = $scope.realmRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.realmRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addRealmRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/oauth-clients/' + oauth.id + '/scope-mappings/realm',
|
||||
$scope.selectedRealmRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
|
||||
var role = $scope.selectedRealmRoles[i];
|
||||
var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
$scope.realmMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectRealmRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/oauth-clients/' + oauth.id + '/scope-mappings/realm',
|
||||
{data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
|
||||
var role = $scope.selectedRealmMappings[i];
|
||||
var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmMappings.splice(idx, 1);
|
||||
$scope.realmRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedRealmMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addApplicationRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/oauth-clients/' + oauth.id + '/scope-mappings/applications/' + $scope.targetApp.name,
|
||||
$scope.selectedApplicationRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
|
||||
var role = $scope.selectedApplicationRoles[i];
|
||||
var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
$scope.applicationMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteApplicationRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/oauth-clients/' + oauth.id + '/scope-mappings/applications/' + $scope.targetApp.name,
|
||||
{data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
|
||||
var role = $scope.selectedApplicationMappings[i];
|
||||
var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationMappings.splice(idx, 1);
|
||||
$scope.applicationRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.changeApplication = function() {
|
||||
if ($scope.targetApp) {
|
||||
$scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.targetApp.name}, function() {
|
||||
$scope.applicationMappings = OAuthClientApplicationScopeMapping.query({realm : realm.realm, oauth : oauth.id, targetApp : $scope.targetApp.name}, function(){
|
||||
for (var i = 0; i < $scope.applicationMappings.length; i++) {
|
||||
var role = $scope.applicationMappings[i];
|
||||
for (var j = 0; j < $scope.applicationRoles.length; j++) {
|
||||
var realmRole = $scope.applicationRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.applicationRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$scope.targetApp = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
module.controller('OAuthClientInstallationCtrl', function($scope, realm, installation, oauth, OAuthClientInstallation, $routeParams) {
|
||||
$scope.realm = realm;
|
||||
$scope.oauth = oauth;
|
||||
$scope.installation = installation;
|
||||
$scope.download = OAuthClientInstallation.url({ realm: $routeParams.realm, oauth: $routeParams.oauth });
|
||||
});
|
||||
@@ -1,744 +0,0 @@
|
||||
module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications) {
|
||||
$scope.addMessage = function() {
|
||||
Notifications.success("test");
|
||||
};
|
||||
|
||||
$scope.auth = Auth;
|
||||
$http.get('/auth/rest/admin/whoami').success(function(data, status) {
|
||||
Auth.user = data;
|
||||
Auth.loggedIn = true;
|
||||
})
|
||||
.error(function(data, status) {
|
||||
Auth.loggedIn = false;
|
||||
});
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.fragment = $location.path();
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('HomeCtrl', function(Realm, $location) {
|
||||
Realm.query(null, function(realms) {
|
||||
var realm;
|
||||
if (realms.length == 1) {
|
||||
realm = realms[0].realm;
|
||||
} else if (realms.length == 2) {
|
||||
if (realms[0].realm == 'keycloak-admin') {
|
||||
realm = realms[1].realm;
|
||||
} else if (realms[1].realm == 'administration') {
|
||||
realm = realms[0].realm;
|
||||
}
|
||||
}
|
||||
if (realm) {
|
||||
$location.url('/realms/' + realm);
|
||||
} else {
|
||||
$location.url('/realms');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('RealmListCtrl', function($scope, Realm, Current) {
|
||||
$scope.realms = Realm.query();
|
||||
Current.realms = $scope.realms;
|
||||
});
|
||||
|
||||
module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $location) {
|
||||
// Current.realms = Realm.get();
|
||||
$scope.current = Current;
|
||||
|
||||
$scope.changeRealm = function(selectedRealm) {
|
||||
$location.url("/realms/" + selectedRealm);
|
||||
};
|
||||
|
||||
$scope.showNav = function() {
|
||||
var show = Current.realms.length > 0;
|
||||
return Auth.loggedIn && show;
|
||||
}
|
||||
$scope.refresh = function() {
|
||||
Current.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, $location, Dialog, Notifications) {
|
||||
console.log('RealmCreateCtrl');
|
||||
|
||||
Current.realm = null;
|
||||
|
||||
$scope.realm = {
|
||||
enabled: true
|
||||
};
|
||||
|
||||
$scope.changed = false;
|
||||
$scope.files = [];
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
|
||||
$scope.onFileSelect = function($files) {
|
||||
$scope.files = $files;
|
||||
};
|
||||
|
||||
$scope.clearFileSelect = function() {
|
||||
$scope.files = null;
|
||||
}
|
||||
|
||||
$scope.uploadFile = function() {
|
||||
//$files: an array of files selected, each file has name, size, and type.
|
||||
for (var i = 0; i < $scope.files.length; i++) {
|
||||
var $file = $scope.files[i];
|
||||
$scope.upload = $upload.upload({
|
||||
url: '/auth/rest/admin/realms', //upload.php script, node.js route, or servlet url
|
||||
// method: POST or PUT,
|
||||
// headers: {'headerKey': 'headerValue'}, withCredential: true,
|
||||
data: {myObj: ""},
|
||||
file: $file
|
||||
/* set file formData name for 'Content-Desposition' header. Default: 'file' */
|
||||
//fileFormDataName: myFile,
|
||||
/* customize how data is added to formData. See #40#issuecomment-28612000 for example */
|
||||
//formDataAppender: function(formData, key, val){}
|
||||
}).progress(function(evt) {
|
||||
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
|
||||
}).success(function(data, status, headers, config) {
|
||||
Notifications.success("The realm has been uploaded.");
|
||||
$location.url("/realms");
|
||||
})
|
||||
.error(function() {
|
||||
Notifications.error("The realm can not be uploaded. Please verify the file.");
|
||||
|
||||
});
|
||||
//.then(success, error, progress);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
var ssl = window.location.protocol == 'https:';
|
||||
realmCopy.sslNotRequired = !ssl;
|
||||
console.log('creating new realm **');
|
||||
Realm.create(realmCopy, function(data, headers) {
|
||||
var data = Realm.query(function() {
|
||||
Current.realms = data;
|
||||
for (var i = 0; i < Current.realms.length; i++) {
|
||||
if (Current.realms[i].realm == realmCopy.realm) {
|
||||
Current.realm = Current.realms[i];
|
||||
}
|
||||
}
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
Notifications.success("The realm has been created.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
//$location.url("/realms");
|
||||
window.history.back();
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
|
||||
$scope.createRealm = !realm.realm;
|
||||
$scope.serverInfo = serverInfo;
|
||||
|
||||
console.log('RealmDetailCtrl');
|
||||
|
||||
if ($scope.createRealm) {
|
||||
$scope.realm = {
|
||||
enabled: true,
|
||||
requireSsl: true
|
||||
};
|
||||
} else {
|
||||
if (Current.realm == null || Current.realm.realm != realm.realm) {
|
||||
for (var i = 0; i < Current.realms.length; i++) {
|
||||
if (realm.realm == Current.realms[i].realm) {
|
||||
Current.realm = Current.realms[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('realm name: ' + realm.realm);
|
||||
for (var i = 0; i < Current.realms.length; i++) {
|
||||
console.log('checking Current.realm:' + Current.realms[i].realm);
|
||||
if (Current.realms[i].realm == realm.realm) {
|
||||
Current.realm = Current.realms[i];
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (Current.realm == null || Current.realm.realm != realm.realm) {
|
||||
console.log('should be unreachable');
|
||||
console.log('Why? ' + Current.realms.length + ' ' + Current.realm);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
$scope.realm = angular.copy(realm);
|
||||
$scope.realm.requireSsl = !realm.sslNotRequired;
|
||||
}
|
||||
|
||||
$scope.social = $scope.realm.social;
|
||||
$scope.registrationAllowed = $scope.realm.registrationAllowed;
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
|
||||
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
realmCopy.sslNotRequired = !realmCopy.requireSsl;
|
||||
delete realmCopy["requireSsl"];
|
||||
if ($scope.createRealm) {
|
||||
Realm.save(realmCopy, function(data, headers) {
|
||||
console.log('creating new realm');
|
||||
var data = Realm.query(function() {
|
||||
Current.realms = data;
|
||||
for (var i = 0; i < Current.realms.length; i++) {
|
||||
if (Current.realms[i].realm == realmCopy.realm) {
|
||||
Current.realm = Current.realms[i];
|
||||
}
|
||||
}
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
Notifications.success("The realm has been created.");
|
||||
$scope.social = $scope.realm.social;
|
||||
$scope.registrationAllowed = $scope.realm.registrationAllowed;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log('updating realm...');
|
||||
$scope.changed = false;
|
||||
console.log('oldCopy.realm - ' + oldCopy.realm);
|
||||
Realm.update({ id : oldCopy.realm}, realmCopy, function () {
|
||||
var data = Realm.query(function () {
|
||||
Current.realms = data;
|
||||
for (var i = 0; i < Current.realms.length; i++) {
|
||||
if (Current.realms[i].realm == realmCopy.realm) {
|
||||
Current.realm = Current.realms[i];
|
||||
oldCopy = angular.copy($scope.realm);
|
||||
}
|
||||
}
|
||||
});
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
$scope.social = $scope.realm.social;
|
||||
$scope.registrationAllowed = $scope.realm.registrationAllowed;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(oldCopy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
//$location.url("/realms");
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
Dialog.confirmDelete($scope.realm.realm, 'realm', function() {
|
||||
Realm.remove({ id : $scope.realm.realm }, function() {
|
||||
Current.realms = Realm.query();
|
||||
Notifications.success("The realm has been deleted.");
|
||||
$location.url("/");
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RealmRequiredCredentialsCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
|
||||
console.log('RealmRequiredCredentialsCtrl');
|
||||
|
||||
$scope.realm = realm;
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
|
||||
$scope.allPolicies = PasswordPolicy.allPolicies;
|
||||
$scope.policyMessages = PasswordPolicy.policyMessages;
|
||||
|
||||
$scope.policy = PasswordPolicy.parse(realm.passwordPolicy);
|
||||
var oldPolicy = angular.copy($scope.policy);
|
||||
|
||||
$scope.addPolicy = function(policy){
|
||||
if (!$scope.policy) {
|
||||
$scope.policy = [];
|
||||
}
|
||||
$scope.policy.push(policy);
|
||||
}
|
||||
|
||||
$scope.removePolicy = function(index){
|
||||
$scope.policy.splice(index, 1);
|
||||
}
|
||||
|
||||
$scope.userCredentialOptions = {
|
||||
'multiple' : true,
|
||||
'simple_tags' : true,
|
||||
'tags' : ['password', 'totp', 'cert']
|
||||
};
|
||||
|
||||
$scope.changed = false;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.$watch('policy', function(oldVal, newVal) {
|
||||
if (!angular.equals($scope.policy, oldPolicy)) {
|
||||
$scope.realm.passwordPolicy = PasswordPolicy.toString($scope.policy);
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.changed = false;
|
||||
|
||||
Realm.update($scope.realm, function () {
|
||||
$location.url("/realms/" + realm.realm + "/required-credentials");
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
oldCopy = angular.copy($scope.realm);
|
||||
oldPolicy = angular.copy($scope.policy);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(oldCopy);
|
||||
$scope.policy = angular.copy(oldPolicy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RealmDefaultRolesCtrl', function ($scope, Realm, realm, applications, roles, Notifications, ApplicationRole, Application) {
|
||||
|
||||
console.log('RealmDefaultRolesCtrl');
|
||||
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.availableRealmRoles = [];
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmDefRoles = [];
|
||||
|
||||
$scope.applications = angular.copy(applications);
|
||||
for (var i = 0; i < applications.length; i++) {
|
||||
if (applications[i].name == 'account') {
|
||||
$scope.application = $scope.applications[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.availableAppRoles = [];
|
||||
$scope.selectedAppRoles = [];
|
||||
$scope.selectedAppDefRoles = [];
|
||||
|
||||
if (!$scope.realm.hasOwnProperty('defaultRoles') || $scope.realm.defaultRoles === null) {
|
||||
$scope.realm.defaultRoles = [];
|
||||
}
|
||||
|
||||
// Populate available roles. Available roles are neither already assigned
|
||||
for (var i = 0; i < roles.length; i++) {
|
||||
var item = roles[i].name;
|
||||
|
||||
if ($scope.realm.defaultRoles.indexOf(item) < 0) {
|
||||
$scope.availableRealmRoles.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.addRealmDefaultRole = function () {
|
||||
|
||||
// Remove selected roles from the Available roles and add them to realm default roles (move from left to right).
|
||||
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
|
||||
var selectedRole = $scope.selectedRealmRoles[i];
|
||||
|
||||
$scope.realm.defaultRoles.push(selectedRole);
|
||||
|
||||
var index = $scope.availableRealmRoles.indexOf(selectedRole);
|
||||
if (index > -1) {
|
||||
$scope.availableRealmRoles.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update/save the realm with new default roles.
|
||||
Realm.update($scope.realm, function () {
|
||||
Notifications.success("Realm default roles updated.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmDefaultRole = function () {
|
||||
|
||||
// Remove selected roles from the realm default roles and add them to available roles (move from right to left).
|
||||
for (var i = 0; i < $scope.selectedRealmDefRoles.length; i++) {
|
||||
$scope.availableRealmRoles.push($scope.selectedRealmDefRoles[i]);
|
||||
|
||||
var index = $scope.realm.defaultRoles.indexOf($scope.selectedRealmDefRoles[i]);
|
||||
if (index > -1) {
|
||||
$scope.realm.defaultRoles.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update/save the realm with new default roles.
|
||||
//var realmCopy = angular.copy($scope.realm);
|
||||
Realm.update($scope.realm, function () {
|
||||
Notifications.success("Realm default roles updated.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeApplication = function () {
|
||||
|
||||
$scope.selectedAppRoles = [];
|
||||
$scope.selectedAppDefRoles = [];
|
||||
|
||||
// Populate available roles for selected application
|
||||
if ($scope.application) {
|
||||
var appDefaultRoles = ApplicationRole.query({realm: $scope.realm.realm, application: $scope.application.name}, function () {
|
||||
|
||||
if (!$scope.application.hasOwnProperty('defaultRoles') || $scope.application.defaultRoles === null) {
|
||||
$scope.application.defaultRoles = [];
|
||||
}
|
||||
|
||||
$scope.availableAppRoles = [];
|
||||
|
||||
for (var i = 0; i < appDefaultRoles.length; i++) {
|
||||
var roleName = appDefaultRoles[i].name;
|
||||
if ($scope.application.defaultRoles.indexOf(roleName) < 0) {
|
||||
$scope.availableAppRoles.push(roleName);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$scope.availableAppRoles = null;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.addAppDefaultRole = function () {
|
||||
|
||||
// Remove selected roles from the app available roles and add them to app default roles (move from left to right).
|
||||
for (var i = 0; i < $scope.selectedAppRoles.length; i++) {
|
||||
var role = $scope.selectedAppRoles[i];
|
||||
|
||||
var idx = $scope.application.defaultRoles.indexOf(role);
|
||||
if (idx < 0) {
|
||||
$scope.application.defaultRoles.push(role);
|
||||
}
|
||||
|
||||
idx = $scope.availableAppRoles.indexOf(role);
|
||||
|
||||
if (idx != -1) {
|
||||
$scope.availableAppRoles.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update/save the selected application with new default roles.
|
||||
Application.update({
|
||||
realm: $scope.realm.realm,
|
||||
id: $scope.application.name
|
||||
}, $scope.application, function () {
|
||||
Notifications.success("Your changes have been saved to the application.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.rmAppDefaultRole = function () {
|
||||
|
||||
// Remove selected roles from the app default roles and add them to app available roles (move from right to left).
|
||||
for (var i = 0; i < $scope.selectedAppDefRoles.length; i++) {
|
||||
var role = $scope.selectedAppDefRoles[i];
|
||||
var idx = $scope.application.defaultRoles.indexOf(role);
|
||||
if (idx != -1) {
|
||||
$scope.application.defaultRoles.splice(idx, 1);
|
||||
}
|
||||
idx = $scope.availableAppRoles.indexOf(role);
|
||||
if (idx < 0) {
|
||||
$scope.availableAppRoles.push(role);
|
||||
}
|
||||
}
|
||||
|
||||
// Update/save the selected application with new default roles.
|
||||
Application.update({
|
||||
realm: $scope.realm.realm,
|
||||
id: $scope.application.name
|
||||
}, $scope.application, function () {
|
||||
Notifications.success("Your changes have been saved to the application.");
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.controller('RealmSocialCtrl', function($scope, realm, Realm, serverInfo, $location, Notifications) {
|
||||
console.log('RealmSocialCtrl');
|
||||
|
||||
$scope.realm = angular.copy(realm);
|
||||
$scope.serverInfo = serverInfo;
|
||||
|
||||
$scope.allProviders = serverInfo.socialProviders;
|
||||
$scope.configuredProviders = [];
|
||||
|
||||
$scope.$watch('realm.socialProviders', function(socialProviders) {
|
||||
$scope.configuredProviders = [];
|
||||
for (var providerConfig in socialProviders) {
|
||||
var i = providerConfig.split('.');
|
||||
if (i.length == 2 && i[1] == 'key') {
|
||||
$scope.configuredProviders.push(i[0]);
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
$scope.changed = false;
|
||||
$scope.callbackUrl = $location.absUrl().replace(/\/admin.*/, "/rest/social/callback");
|
||||
|
||||
$scope.addProvider = function(pId) {
|
||||
if (!$scope.realm.socialProviders) {
|
||||
$scope.realm.socialProviders = {};
|
||||
}
|
||||
|
||||
$scope.realm.socialProviders[pId + ".key"] = "";
|
||||
$scope.realm.socialProviders[pId + ".secret"] = "";
|
||||
};
|
||||
|
||||
$scope.removeProvider = function(pId) {
|
||||
delete $scope.realm.socialProviders[pId+".key"];
|
||||
delete $scope.realm.socialProviders[pId+".secret"];
|
||||
};
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
realmCopy.social = true;
|
||||
$scope.changed = false;
|
||||
Realm.update(realmCopy, function () {
|
||||
$location.url("/realms/" + realm.realm + "/social-settings");
|
||||
Notifications.success("Saved changes to realm");
|
||||
oldCopy = realmCopy;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(oldCopy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, TimeUnit) {
|
||||
console.log('RealmTokenDetailCtrl');
|
||||
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.realm.tokenLifespanUnit = TimeUnit.autoUnit(realm.tokenLifespan);
|
||||
$scope.realm.tokenLifespan = TimeUnit.toUnit(realm.tokenLifespan, $scope.realm.tokenLifespanUnit);
|
||||
$scope.$watch('realm.tokenLifespanUnit', function(to, from) {
|
||||
$scope.realm.tokenLifespan = TimeUnit.convert($scope.realm.tokenLifespan, from, to);
|
||||
});
|
||||
|
||||
$scope.realm.accessCodeLifespanUnit = TimeUnit.autoUnit(realm.accessCodeLifespan);
|
||||
$scope.realm.accessCodeLifespan = TimeUnit.toUnit(realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit);
|
||||
$scope.$watch('realm.accessCodeLifespanUnit', function(to, from) {
|
||||
$scope.realm.accessCodeLifespan = TimeUnit.convert($scope.realm.accessCodeLifespan, from, to);
|
||||
});
|
||||
|
||||
$scope.realm.accessCodeLifespanUserActionUnit = TimeUnit.autoUnit(realm.accessCodeLifespanUserAction);
|
||||
$scope.realm.accessCodeLifespanUserAction = TimeUnit.toUnit(realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit);
|
||||
$scope.$watch('realm.accessCodeLifespanUserActionUnit', function(to, from) {
|
||||
$scope.realm.accessCodeLifespanUserAction = TimeUnit.convert($scope.realm.accessCodeLifespanUserAction, from, to);
|
||||
});
|
||||
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
$scope.changed = false;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
delete realmCopy["tokenLifespanUnit"];
|
||||
delete realmCopy["accessCodeLifespanUnit"];
|
||||
delete realmCopy["accessCodeLifespanUserActionUnit"];
|
||||
|
||||
realmCopy.tokenLifespan = TimeUnit.toSeconds($scope.realm.tokenLifespan, $scope.realm.tokenLifespanUnit)
|
||||
realmCopy.accessCodeLifespan = TimeUnit.toSeconds($scope.realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit)
|
||||
realmCopy.accessCodeLifespanUserAction = TimeUnit.toSeconds($scope.realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit)
|
||||
|
||||
$scope.changed = false;
|
||||
Realm.update(realmCopy, function () {
|
||||
$location.url("/realms/" + realm.realm + "/token-settings");
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(oldCopy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RealmKeysDetailCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.generate = function() {
|
||||
Dialog.confirmGenerateKeys($scope.realm.realm, 'realm', function() {
|
||||
Realm.update({ realm: realm.realm, publicKey : 'GENERATE' }, function () {
|
||||
Notifications.success('New keys generated for realm.');
|
||||
Realm.get({ id : realm.realm }, function(updated) {
|
||||
$scope.realm = updated;
|
||||
})
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RoleListCtrl', function($scope, $location, realm, roles) {
|
||||
|
||||
$scope.realm = realm;
|
||||
$scope.roles = roles;
|
||||
|
||||
$scope.$watch(function() {
|
||||
return $location.path();
|
||||
}, function() {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.controller('RoleDetailCtrl', function($scope, realm, role, roles, applications,
|
||||
Role, ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
|
||||
$http, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.role = angular.copy(role);
|
||||
$scope.create = !role.name;
|
||||
|
||||
$scope.changed = $scope.create;
|
||||
|
||||
$scope.save = function() {
|
||||
console.log('save');
|
||||
if ($scope.create) {
|
||||
Role.save({
|
||||
realm: realm.realm
|
||||
}, $scope.role, function (data, headers) {
|
||||
$scope.changed = false;
|
||||
role = angular.copy($scope.role);
|
||||
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
$location.url("/realms/" + realm.realm + "/roles/" + id);
|
||||
Notifications.success("The role has been created.");
|
||||
});
|
||||
} else {
|
||||
$scope.update();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.remove = function () {
|
||||
Dialog.confirmDelete($scope.role.name, 'role', function () {
|
||||
$scope.role.$remove({
|
||||
realm: realm.realm,
|
||||
role: $scope.role.name
|
||||
}, function () {
|
||||
$location.url("/realms/" + realm.realm + "/roles");
|
||||
Notifications.success("The role has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.cancel = function () {
|
||||
$location.url("/realms/" + realm.realm + "/roles");
|
||||
};
|
||||
|
||||
|
||||
|
||||
roleControl($scope, realm, role, roles, applications,
|
||||
ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
|
||||
$http, $location, Notifications, Dialog);
|
||||
});
|
||||
|
||||
module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
|
||||
console.log('RealmSMTPSettingsCtrl');
|
||||
|
||||
var booleanSmtpAtts = ["auth","ssl","starttls"];
|
||||
|
||||
$scope.realm = realm;
|
||||
|
||||
if ($scope.realm.smtpServer) {
|
||||
$scope.realm.smtpServer = typeObject($scope.realm.smtpServer);
|
||||
};
|
||||
|
||||
var oldCopy = angular.copy($scope.realm);
|
||||
$scope.changed = false;
|
||||
|
||||
$scope.$watch('realm', function() {
|
||||
if (!angular.equals($scope.realm, oldCopy)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
realmCopy['smtpServer'] = detypeObject(realmCopy.smtpServer);
|
||||
$scope.changed = false;
|
||||
Realm.update(realmCopy, function () {
|
||||
$location.url("/realms/" + realm.realm + "/smtp-settings");
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.realm = angular.copy(oldCopy);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
/* Convert string attributes containing a boolean to actual boolean type + convert an integer string (port) to integer. */
|
||||
function typeObject(obj){
|
||||
for (var att in obj){
|
||||
if (booleanSmtpAtts.indexOf(att) < 0)
|
||||
continue;
|
||||
if (obj[att] === "true"){
|
||||
obj[att] = true;
|
||||
} else if (obj[att] === "false"){
|
||||
obj[att] = false;
|
||||
}
|
||||
}
|
||||
|
||||
obj['port'] = parseInt(obj['port']);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* Convert all non-string values to strings to invert changes caused by the typeObject function. */
|
||||
function detypeObject(obj){
|
||||
for (var att in obj){
|
||||
if (booleanSmtpAtts.indexOf(att) < 0)
|
||||
continue;
|
||||
if (obj[att] === true){
|
||||
obj[att] = "true";
|
||||
} else if (obj[att] === false){
|
||||
obj[att] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
obj['port'] = obj['port'].toString();
|
||||
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
@@ -1,367 +0,0 @@
|
||||
module.controller('UserRoleMappingCtrl', function($scope, $http, realm, user, roles, applications, RealmRoleMapping, ApplicationRoleMapping, ApplicationRole) {
|
||||
$scope.realm = realm;
|
||||
$scope.user = user;
|
||||
$scope.realmRoles = angular.copy(roles);
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.applications = applications;
|
||||
$scope.applicationRoles = [];
|
||||
$scope.selectedApplicationRoles = [];
|
||||
$scope.selectedApplicationMappings = [];
|
||||
$scope.applicationMappings = [];
|
||||
|
||||
$scope.realmMappings = RealmRoleMapping.query({realm : realm.realm, userId : user.username}, function(){
|
||||
for (var i = 0; i < $scope.realmMappings.length; i++) {
|
||||
var role = $scope.realmMappings[i];
|
||||
for (var j = 0; j < $scope.realmRoles.length; j++) {
|
||||
var realmRole = $scope.realmRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.realmRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addRealmRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/users/' + user.username + '/role-mappings/realm',
|
||||
$scope.selectedRealmRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
|
||||
var role = $scope.selectedRealmRoles[i];
|
||||
var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
$scope.realmMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectRealmRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/users/' + user.username + '/role-mappings/realm',
|
||||
{data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
|
||||
var role = $scope.selectedRealmMappings[i];
|
||||
var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmMappings.splice(idx, 1);
|
||||
$scope.realmRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedRealmMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addApplicationRole = function() {
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/users/' + user.username + '/role-mappings/applications/' + $scope.application.name,
|
||||
$scope.selectedApplicationRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
|
||||
var role = $scope.selectedApplicationRoles[i];
|
||||
var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
$scope.applicationMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteApplicationRole = function() {
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/users/' + user.username + '/role-mappings/applications/' + $scope.application.name,
|
||||
{data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
|
||||
var role = $scope.selectedApplicationMappings[i];
|
||||
var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationMappings.splice(idx, 1);
|
||||
$scope.applicationRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.changeApplication = function() {
|
||||
if ($scope.application) {
|
||||
$scope.applicationRoles = ApplicationRole.query({realm : realm.realm, userId : user.username, application : $scope.application.name}, function() {
|
||||
$scope.applicationMappings = ApplicationRoleMapping.query({realm : realm.realm, userId : user.username, application : $scope.application.name}, function(){
|
||||
for (var i = 0; i < $scope.applicationMappings.length; i++) {
|
||||
var role = $scope.applicationMappings[i];
|
||||
for (var j = 0; j < $scope.applicationRoles.length; j++) {
|
||||
var realmRole = $scope.applicationRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.applicationRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$scope.applicationRoles = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
module.controller('UserListCtrl', function($scope, realm, User) {
|
||||
$scope.realm = realm;
|
||||
$scope.searchQuery = function() {
|
||||
$scope.searchLoaded = false;
|
||||
$scope.currentSearch = $scope.search;
|
||||
|
||||
var params = { realm: realm.realm };
|
||||
if ($scope.search) {
|
||||
params.search = $scope.search;
|
||||
}
|
||||
|
||||
$scope.users = User.query(params, function() {
|
||||
$scope.searchLoaded = true;
|
||||
$scope.lastSearch = params.search;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.controller('UserDetailCtrl', function($scope, realm, user, User, $location, Dialog, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.user = angular.copy(user);
|
||||
$scope.create = !user.username;
|
||||
|
||||
$scope.changed = false; // $scope.create;
|
||||
|
||||
// ID - Name map for required actions. IDs are enum names.
|
||||
var userReqActionList = [
|
||||
{id: "VERIFY_EMAIL", text: "Verify Email"},
|
||||
{id: "UPDATE_PROFILE", text: "Update Profile"},
|
||||
{id: "CONFIGURE_TOTP", text: "Configure Totp"},
|
||||
{id: "UPDATE_PASSWORD", text: "Update Password"}
|
||||
];
|
||||
|
||||
// Options for the req actions tag selector
|
||||
$scope.userReqActionsOptions = {
|
||||
'multiple' : true,
|
||||
'tags' : userReqActionList
|
||||
};
|
||||
|
||||
// Model for the req actions tag selector
|
||||
$scope.userActions = [];
|
||||
for (var i = 0; i < userReqActionList.length; i++){
|
||||
var action = userReqActionList[i];
|
||||
|
||||
if ($scope.user.requiredActions && $scope.user.requiredActions.indexOf(action.id) > -1){
|
||||
$scope.userActions.push({id: action.id, text: action.text});
|
||||
}
|
||||
}
|
||||
|
||||
// Watching ui-select2 model to properly format the req actions for user
|
||||
$scope.$watch("userActions", function(newValue, oldValue) {
|
||||
$scope.user.requiredActions = [];
|
||||
for (var i=0; i < newValue.length; i++){
|
||||
var action = newValue[i];
|
||||
$scope.user.requiredActions.push(action.id);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('user', function() {
|
||||
if (!angular.equals($scope.user, user)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
if ($scope.create) {
|
||||
User.save({
|
||||
realm: realm.realm
|
||||
}, $scope.user, function () {
|
||||
$scope.changed = false;
|
||||
user = angular.copy($scope.user);
|
||||
|
||||
$location.url("/realms/" + realm.realm + "/users/" + $scope.user.username);
|
||||
Notifications.success("The user has been created.");
|
||||
});
|
||||
} else {
|
||||
User.update({
|
||||
realm: realm.realm,
|
||||
userId: $scope.user.username
|
||||
}, $scope.user, function () {
|
||||
$scope.changed = false;
|
||||
user = angular.copy($scope.user);
|
||||
Notifications.success("Your changes have been saved to the user.");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.user = angular.copy(user);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
$location.url("/realms/" + realm.realm + "/users");
|
||||
};
|
||||
|
||||
$scope.remove = function() {
|
||||
Dialog.confirmDelete($scope.user.username, 'user', function() {
|
||||
$scope.user.$remove({
|
||||
realm : realm.realm,
|
||||
userId : $scope.user.username
|
||||
}, function() {
|
||||
$location.url("/realms/" + realm.realm + "/users");
|
||||
Notifications.success("The user has been deleted.");
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('UserCredentialsCtrl', function($scope, realm, user, User, UserCredentials, Notifications, Dialog) {
|
||||
console.log('UserCredentialsCtrl');
|
||||
|
||||
$scope.realm = realm;
|
||||
$scope.user = angular.copy(user);
|
||||
|
||||
$scope.isTotp = false;
|
||||
if(!!user.totp){
|
||||
$scope.isTotp = user.totp;
|
||||
}
|
||||
|
||||
$scope.resetPassword = function() {
|
||||
if ($scope.pwdChange) {
|
||||
if ($scope.password != $scope.confirmPassword) {
|
||||
Notifications.error("Password and confirmation does not match.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dialog.confirm('Reset password', 'Are you sure you want to reset the users password?', function() {
|
||||
UserCredentials.resetPassword({ realm: realm.realm, userId: user.username }, { type : "password", value : $scope.password }, function() {
|
||||
Notifications.success("The password has been reset");
|
||||
$scope.password = null;
|
||||
$scope.confirmPassword = null;
|
||||
}, function() {
|
||||
Notifications.error("Failed to reset user password");
|
||||
});
|
||||
}, function() {
|
||||
$scope.password = null;
|
||||
$scope.confirmPassword = null;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeTotp = function() {
|
||||
Dialog.confirm('Remove totp', 'Are you sure you want to remove the users totp configuration?', function() {
|
||||
UserCredentials.removeTotp({ realm: realm.realm, userId: user.username }, { }, function() {
|
||||
Notifications.success("The users totp configuration has been removed");
|
||||
$scope.user.totp = false;
|
||||
}, function() {
|
||||
Notifications.error("Failed to remove the users totp configuration");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.resetPasswordEmail = function() {
|
||||
Dialog.confirm('Reset password email', 'Are you sure you want to send password reset email to user?', function() {
|
||||
UserCredentials.resetPasswordEmail({ realm: realm.realm, userId: user.username }, { }, function() {
|
||||
Notifications.success("Password reset email sent to user");
|
||||
}, function() {
|
||||
Notifications.error("Failed to send password reset mail to user");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('user', function() {
|
||||
if (!angular.equals($scope.user, user)) {
|
||||
$scope.userChange = true;
|
||||
} else {
|
||||
$scope.userChange = false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.$watch('password', function() {
|
||||
if (!!$scope.password){
|
||||
$scope.pwdChange = true;
|
||||
} else {
|
||||
$scope.pwdChange = false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.reset = function() {
|
||||
$scope.password = "";
|
||||
$scope.confirmPassword = "";
|
||||
|
||||
$scope.user = angular.copy(user);
|
||||
|
||||
$scope.isTotp = false;
|
||||
if(!!user.totp){
|
||||
$scope.isTotp = user.totp;
|
||||
}
|
||||
|
||||
$scope.pwdChange = false;
|
||||
$scope.userChange = false;
|
||||
};
|
||||
});
|
||||
|
||||
module.controller('RoleMappingCtrl', function($scope, realm, User, users, role, RoleMapping, Notifications) {
|
||||
$scope.realm = realm;
|
||||
$scope.realmId = realm.realm || realm.realm;
|
||||
$scope.allUsers = User.query({ realm : $scope.realmId });
|
||||
$scope.users = users;
|
||||
$scope.role = role;
|
||||
|
||||
$scope.addUser = function() {
|
||||
var user = $scope.newUser;
|
||||
$scope.newUser = null;
|
||||
|
||||
for ( var i = 0; i < $scope.allUsers.length; i++) {
|
||||
if ($scope.allUsers[i].userId == user) {
|
||||
user = $scope.allUsers[i];
|
||||
RoleMapping.save({
|
||||
realm : $scope.realmId,
|
||||
role : role
|
||||
}, user, function() {
|
||||
$scope.users = RoleMapping.query({
|
||||
realm : $scope.realmId,
|
||||
role : role
|
||||
});
|
||||
Notifications.success("The role mapping has been added for the user.");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removeUser = function(userId) {
|
||||
for (var i = 0; i < $scope.users.length; i++) {
|
||||
var user = $scope.users[i];
|
||||
if ($scope.users[i].userId == userId) {
|
||||
RoleMapping.delete({
|
||||
realm : $scope.realmId,
|
||||
role : role
|
||||
}, user, function() {
|
||||
$scope.users = RoleMapping.query({
|
||||
realm : $scope.realmId,
|
||||
role : role
|
||||
});
|
||||
|
||||
Notifications.success("The role mapping has been removed for the user.");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('keycloak.loaders', [ 'keycloak.services', 'ngResource' ]);
|
||||
|
||||
module.factory('Loader', function($q) {
|
||||
var loader = {};
|
||||
loader.get = function(service, id) {
|
||||
return function() {
|
||||
var i = id && id();
|
||||
var delay = $q.defer();
|
||||
service.get(i, function(entry) {
|
||||
delay.resolve(entry);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch ' + i);
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
}
|
||||
loader.query = function(service, id) {
|
||||
return function() {
|
||||
var i = id && id();
|
||||
var delay = $q.defer();
|
||||
service.query(i, function(entry) {
|
||||
delay.resolve(entry);
|
||||
}, function() {
|
||||
delay.reject('Unable to fetch ' + i);
|
||||
});
|
||||
return delay.promise;
|
||||
};
|
||||
}
|
||||
return loader;
|
||||
});
|
||||
|
||||
module.factory('RealmListLoader', function(Loader, Realm, $q) {
|
||||
return Loader.get(Realm);
|
||||
});
|
||||
|
||||
module.factory('ServerInfoLoader', function(Loader, ServerInfo, $q) {
|
||||
return Loader.get(ServerInfo);
|
||||
});
|
||||
|
||||
module.factory('RealmLoader', function(Loader, Realm, $route, $q) {
|
||||
return Loader.get(Realm, function() {
|
||||
return {
|
||||
id : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserListLoader', function(Loader, User, $route, $q) {
|
||||
return Loader.query(User, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserLoader', function(Loader, User, $route, $q) {
|
||||
return Loader.get(User, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
userId : $route.current.params.user
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RoleLoader', function(Loader, Role, $route, $q) {
|
||||
return Loader.get(Role, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
role : $route.current.params.role
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RoleListLoader', function(Loader, Role, $route, $q) {
|
||||
return Loader.query(Role, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationRoleLoader', function(Loader, ApplicationRole, $route, $q) {
|
||||
return Loader.get(ApplicationRole, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
application : $route.current.params.application,
|
||||
role : $route.current.params.role
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationInstallationLoader', function(Loader, ApplicationInstallation, $route, $q) {
|
||||
return Loader.get(ApplicationInstallation, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
application : $route.current.params.application
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationRoleListLoader', function(Loader, ApplicationRole, $route, $q) {
|
||||
return Loader.query(ApplicationRole, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
application : $route.current.params.application
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.factory('ApplicationLoader', function(Loader, Application, $route, $q) {
|
||||
return Loader.get(Application, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
application : $route.current.params.application
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationListLoader', function(Loader, Application, $route, $q) {
|
||||
return Loader.query(Application, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('RoleMappingLoader', function(Loader, RoleMapping, $route, $q) {
|
||||
var realm = $route.current.params.realm || $route.current.params.application;
|
||||
|
||||
return Loader.query(RoleMapping, function() {
|
||||
return {
|
||||
realm : realm,
|
||||
role : $route.current.params.role
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientLoader', function(Loader, OAuthClient, $route, $q) {
|
||||
return Loader.get(OAuthClient, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
id : $route.current.params.oauth
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientListLoader', function(Loader, OAuthClient, $route, $q) {
|
||||
return Loader.query(OAuthClient, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientInstallationLoader', function(Loader, OAuthClientInstallation, $route, $q) {
|
||||
return Loader.get(OAuthClientInstallation, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
oauth : $route.current.params.oauth
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,700 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('keycloak.services', [ 'ngResource' ]);
|
||||
|
||||
module.service('Dialog', function($dialog) {
|
||||
var dialog = {};
|
||||
|
||||
var escapeHtml = function(str) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
};
|
||||
|
||||
dialog.confirmDelete = function(name, type, success) {
|
||||
var title = 'Delete ' + escapeHtml(type.charAt(0).toUpperCase() + type.slice(1));
|
||||
var msg = '<span class="primary">Are you sure you want to permanently delete the ' + escapeHtml(type) + ' <strong>' + escapeHtml(name) + '</strong>?</span>';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel',
|
||||
cssClass : 'btn btn-default'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : 'Delete',
|
||||
cssClass : 'btn btn-danger'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
success();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dialog.confirmGenerateKeys = function(name, type, success) {
|
||||
var title = 'Generate new keys for realm';
|
||||
var msg = '<span class="primary">Are you sure you want to permanently generate new keys for <strong>' + name + '</strong>?</span>';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel',
|
||||
cssClass : 'btn btn-default'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : 'Generate new keys',
|
||||
cssClass : 'btn btn-danger'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
success();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dialog.confirm = function(title, message, success, cancel) {
|
||||
var title = title;
|
||||
var msg = '<span class="primary">' + message + '"</span>';
|
||||
var btns = [ {
|
||||
result : 'cancel',
|
||||
label : 'Cancel',
|
||||
cssClass : 'btn btn-default'
|
||||
}, {
|
||||
result : 'ok',
|
||||
label : title,
|
||||
cssClass : 'btn btn-danger'
|
||||
} ];
|
||||
|
||||
$dialog.messageBox(title, msg, btns).open().then(function(result) {
|
||||
if (result == "ok") {
|
||||
success();
|
||||
} else {
|
||||
cancel && cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return dialog
|
||||
});
|
||||
|
||||
module.factory('Notifications', function($rootScope, $timeout) {
|
||||
// time (in ms) the notifications are shown
|
||||
var delay = 5000;
|
||||
|
||||
var notifications = {};
|
||||
|
||||
var scheduled = null;
|
||||
var schedulePop = function() {
|
||||
if (scheduled) {
|
||||
$timeout.cancel(scheduled);
|
||||
}
|
||||
|
||||
scheduled = $timeout(function() {
|
||||
$rootScope.notification = null;
|
||||
scheduled = null;
|
||||
}, delay);
|
||||
};
|
||||
|
||||
if (!$rootScope.notifications) {
|
||||
$rootScope.notifications = [];
|
||||
}
|
||||
|
||||
notifications.message = function(type, header, message) {
|
||||
$rootScope.notification = {
|
||||
type : type,
|
||||
header: header,
|
||||
message : message
|
||||
};
|
||||
|
||||
schedulePop();
|
||||
}
|
||||
|
||||
notifications.info = function(message) {
|
||||
notifications.message("info", "Info!", message);
|
||||
};
|
||||
|
||||
notifications.success = function(message) {
|
||||
notifications.message("success", "Success!", message);
|
||||
};
|
||||
|
||||
notifications.error = function(message) {
|
||||
notifications.message("danger", "Error!", message);
|
||||
};
|
||||
|
||||
notifications.warn = function(message) {
|
||||
notifications.message("warning", "Warning!", message);
|
||||
};
|
||||
|
||||
return notifications;
|
||||
});
|
||||
|
||||
module.factory('Realm', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:id', {
|
||||
id : '@realm'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
},
|
||||
create : {
|
||||
method : 'POST',
|
||||
params : { id : ''}
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ServerInfo', function($resource) {
|
||||
return $resource('/auth/rest/admin/serverinfo');
|
||||
});
|
||||
|
||||
module.factory('User', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/users/:userId', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserCredentials', function($resource) {
|
||||
var credentials = {};
|
||||
|
||||
credentials.resetPassword = $resource('/auth/rest/admin/realms/:realm/users/:userId/reset-password', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
}).update;
|
||||
|
||||
credentials.removeTotp = $resource('/auth/rest/admin/realms/:realm/users/:userId/remove-totp', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
}).update;
|
||||
|
||||
credentials.resetPasswordEmail = $resource('/auth/rest/admin/realms/:realm/users/:userId/reset-password-email', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
}).update;
|
||||
|
||||
return credentials;
|
||||
});
|
||||
|
||||
module.factory('RealmRoleMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/realm', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationRoleMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/users/:userId/role-mappings/applications/:application', {
|
||||
realm : '@realm',
|
||||
userId : '@userId',
|
||||
application : "@application"
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationRealmScopeMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/realm', {
|
||||
realm : '@realm',
|
||||
application : '@application'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationApplicationScopeMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application/scope-mappings/applications/:targetApp', {
|
||||
realm : '@realm',
|
||||
application : '@application',
|
||||
targetApp : '@targetApp'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.factory('RealmRoles', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/roles', {
|
||||
realm : '@realm'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RoleRealmComposites', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/realm', {
|
||||
realm : '@realm',
|
||||
role : '@role'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RoleApplicationComposites', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role/composites/applications/:application', {
|
||||
realm : '@realm',
|
||||
role : '@role',
|
||||
application : "@application"
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function roleControl($scope, realm, role, roles, applications,
|
||||
ApplicationRole, RoleById, RoleRealmComposites, RoleApplicationComposites,
|
||||
$http, $location, Notifications, Dialog) {
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $location.path();
|
||||
}, function () {
|
||||
$scope.path = $location.path().substring(1).split("/");
|
||||
});
|
||||
|
||||
$scope.$watch('role', function () {
|
||||
if (!angular.equals($scope.role, role)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.update = function () {
|
||||
RoleById.update({
|
||||
realm: realm.realm,
|
||||
role: role.id
|
||||
}, $scope.role, function () {
|
||||
$scope.changed = false;
|
||||
role = angular.copy($scope.role);
|
||||
Notifications.success("Your changes have been saved to the role.");
|
||||
});
|
||||
};
|
||||
|
||||
$scope.reset = function () {
|
||||
$scope.role = angular.copy(role);
|
||||
$scope.changed = false;
|
||||
};
|
||||
|
||||
if (!role.id) return;
|
||||
|
||||
$scope.compositeSwitch = role.composite;
|
||||
$scope.compositeSwitchDisabled = role.composite;
|
||||
$scope.realmRoles = angular.copy(roles);
|
||||
$scope.selectedRealmRoles = [];
|
||||
$scope.selectedRealmMappings = [];
|
||||
$scope.realmMappings = [];
|
||||
$scope.applications = applications;
|
||||
$scope.applicationRoles = [];
|
||||
$scope.selectedApplicationRoles = [];
|
||||
$scope.selectedApplicationMappings = [];
|
||||
$scope.applicationMappings = [];
|
||||
|
||||
console.log('remove self');
|
||||
for (var j = 0; j < $scope.realmRoles.length; j++) {
|
||||
if ($scope.realmRoles[j].id == role.id) {
|
||||
var realmRole = $scope.realmRoles[j];
|
||||
var idx = $scope.realmRoles.indexOf(realmRole);
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$scope.realmMappings = RoleRealmComposites.query({realm : realm.realm, role : role.id}, function(){
|
||||
for (var i = 0; i < $scope.realmMappings.length; i++) {
|
||||
var role = $scope.realmMappings[i];
|
||||
for (var j = 0; j < $scope.realmRoles.length; j++) {
|
||||
var realmRole = $scope.realmRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.realmRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.addRealmRole = function() {
|
||||
$scope.compositeSwitchDisabled=true;
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
|
||||
$scope.selectedRealmRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmRoles.length; i++) {
|
||||
var role = $scope.selectedRealmRoles[i];
|
||||
var idx = $scope.realmRoles.indexOf($scope.selectedRealmRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmRoles.splice(idx, 1);
|
||||
$scope.realmMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectRealmRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteRealmRole = function() {
|
||||
$scope.compositeSwitchDisabled=true;
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
|
||||
{data : $scope.selectedRealmMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedRealmMappings.length; i++) {
|
||||
var role = $scope.selectedRealmMappings[i];
|
||||
var idx = $scope.realmMappings.indexOf($scope.selectedRealmMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.realmMappings.splice(idx, 1);
|
||||
$scope.realmRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedRealmMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addApplicationRole = function() {
|
||||
$scope.compositeSwitchDisabled=true;
|
||||
$http.post('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
|
||||
$scope.selectedApplicationRoles).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationRoles.length; i++) {
|
||||
var role = $scope.selectedApplicationRoles[i];
|
||||
var idx = $scope.applicationRoles.indexOf($scope.selectedApplicationRoles[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
$scope.applicationMappings.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationRoles = [];
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteApplicationRole = function() {
|
||||
$scope.compositeSwitchDisabled=true;
|
||||
$http.delete('/auth/rest/admin/realms/' + realm.realm + '/roles-by-id/' + role.id + '/composites',
|
||||
{data : $scope.selectedApplicationMappings, headers : {"content-type" : "application/json"}}).success(function() {
|
||||
for (var i = 0; i < $scope.selectedApplicationMappings.length; i++) {
|
||||
var role = $scope.selectedApplicationMappings[i];
|
||||
var idx = $scope.applicationMappings.indexOf($scope.selectedApplicationMappings[i]);
|
||||
if (idx != -1) {
|
||||
$scope.applicationMappings.splice(idx, 1);
|
||||
$scope.applicationRoles.push(role);
|
||||
}
|
||||
}
|
||||
$scope.selectedApplicationMappings = [];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.changeApplication = function() {
|
||||
$scope.applicationRoles = ApplicationRole.query({realm : realm.realm, application : $scope.compositeApp.name}, function() {
|
||||
$scope.applicationMappings = RoleApplicationComposites.query({realm : realm.realm, role : role.id, application : $scope.compositeApp.name}, function(){
|
||||
for (var i = 0; i < $scope.applicationMappings.length; i++) {
|
||||
var role = $scope.applicationMappings[i];
|
||||
for (var j = 0; j < $scope.applicationRoles.length; j++) {
|
||||
var realmRole = $scope.applicationRoles[j];
|
||||
if (realmRole.id == role.id) {
|
||||
var idx = $scope.applicationRoles.indexOf(realmRole);
|
||||
if (idx != -1) {
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
for (var j = 0; j < $scope.applicationRoles.length; j++) {
|
||||
if ($scope.applicationRoles[j] == role.id) {
|
||||
var appRole = $scope.applicationRoles[j];
|
||||
var idx = $scope.applicationRoles.indexof(appRole);
|
||||
$scope.applicationRoles.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.factory('Role', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/roles/:role', {
|
||||
realm : '@realm',
|
||||
role : '@role'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('RoleById', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/roles-by-id/:role', {
|
||||
realm : '@realm',
|
||||
role : '@role'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationRole', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application/roles/:role', {
|
||||
realm : '@realm',
|
||||
application : "@application",
|
||||
role : '@role'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('Application', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application', {
|
||||
realm : '@realm',
|
||||
application : '@application'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationInstallation', function($resource) {
|
||||
var url = '/auth/rest/admin/realms/:realm/applications/:application/installation/json';
|
||||
return {
|
||||
url : function(parameters)
|
||||
{
|
||||
return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
|
||||
}
|
||||
}
|
||||
});
|
||||
module.factory('ApplicationInstallationJBoss', function($resource) {
|
||||
var url = '/auth/rest/admin/realms/:realm/applications/:application/installation/jboss';
|
||||
return {
|
||||
url : function(parameters)
|
||||
{
|
||||
return url.replace(':realm', parameters.realm).replace(':application', parameters.application);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.factory('ApplicationCredentials', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application/client-secret', {
|
||||
realm : '@realm',
|
||||
application : '@application'
|
||||
}, {
|
||||
update : {
|
||||
method : 'POST'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('ApplicationOrigins', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/applications/:application/allowed-origins', {
|
||||
realm : '@realm',
|
||||
application : '@application'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT',
|
||||
isArray : true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClient', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:id', {
|
||||
realm : '@realm',
|
||||
id : '@id'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientCredentials', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/client-secret', {
|
||||
realm : '@realm',
|
||||
oauth : '@oauth'
|
||||
}, {
|
||||
update : {
|
||||
method : 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.factory('OAuthClientRealmScopeMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/realm', {
|
||||
realm : '@realm',
|
||||
oauth : '@oauth'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientApplicationScopeMapping', function($resource) {
|
||||
return $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/scope-mappings/applications/:targetApp', {
|
||||
realm : '@realm',
|
||||
oauth : '@oauth',
|
||||
targetApp : '@targetApp'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('OAuthClientInstallation', function($resource) {
|
||||
var url = '/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation';
|
||||
var resource = $resource('/auth/rest/admin/realms/:realm/oauth-clients/:oauth/installation', {
|
||||
realm : '@realm',
|
||||
oauth : '@oauth'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
resource.url = function(parameters) {
|
||||
return url.replace(':realm', parameters.realm).replace(':oauth', parameters.oauth);
|
||||
}
|
||||
return resource;
|
||||
});
|
||||
|
||||
|
||||
module.factory('Current', function(Realm, $route) {
|
||||
var current = {};
|
||||
|
||||
current.realms = {};
|
||||
current.realm = null;
|
||||
current.applications = {};
|
||||
current.application = null;
|
||||
|
||||
current.refresh = function() {
|
||||
current.realm = null;
|
||||
current.realms = Realm.query(null, function(realms) {
|
||||
if ($route.current.params.realm) {
|
||||
for (var i = 0; i < realms.length; i++) {
|
||||
if (realms[i].realm == $route.current.params.realm) {
|
||||
current.realm = realms[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
current.refresh();
|
||||
|
||||
return current;
|
||||
});
|
||||
|
||||
module.factory('TimeUnit', function() {
|
||||
var t = {};
|
||||
|
||||
t.autoUnit = function(time) {
|
||||
var unit = 'Seconds';
|
||||
if (time % 60 == 0) {
|
||||
unit = 'Minutes';
|
||||
time = time / 60;
|
||||
}
|
||||
if (time % 60 == 0) {
|
||||
unit = 'Hours';
|
||||
time = time / 60;
|
||||
}
|
||||
if (time % 24 == 0) {
|
||||
unit = 'Days'
|
||||
time = time / 24;
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
t.toSeconds = function(time, unit) {
|
||||
switch (unit) {
|
||||
case 'Seconds': return time;
|
||||
case 'Minutes': return time * 60;
|
||||
case 'Hours': return time * 3600;
|
||||
case 'Days': return time * 86400;
|
||||
default: throw 'invalid unit ' + unit;
|
||||
}
|
||||
}
|
||||
|
||||
t.toUnit = function(time, unit) {
|
||||
switch (unit) {
|
||||
case 'Seconds': return time;
|
||||
case 'Minutes': return Math.ceil(time / 60);
|
||||
case 'Hours': return Math.ceil(time / 3600);
|
||||
case 'Days': return Math.ceil(time / 86400);
|
||||
default: throw 'invalid unit ' + unit;
|
||||
}
|
||||
}
|
||||
|
||||
t.convert = function(time, from, to) {
|
||||
var seconds = t.toSeconds(time, from);
|
||||
return t.toUnit(seconds, to);
|
||||
}
|
||||
|
||||
return t;
|
||||
});
|
||||
|
||||
|
||||
module.factory('PasswordPolicy', function() {
|
||||
var p = {};
|
||||
|
||||
p.policyMessages = {
|
||||
length: "Minimal password length (integer type). Default value is 8.",
|
||||
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
||||
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
|
||||
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
|
||||
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1."
|
||||
}
|
||||
|
||||
p.allPolicies = [
|
||||
{ name: 'length', value: 8 },
|
||||
{ name: 'digits', value: 1 },
|
||||
{ name: 'lowerCase', value: 1 },
|
||||
{ name: 'upperCase', value: 1 },
|
||||
{ name: 'specialChars', value: 1 }
|
||||
];
|
||||
|
||||
p.parse = function(policyString) {
|
||||
var policies = [];
|
||||
|
||||
if (!policyString || policyString.length == 0){
|
||||
return policies;
|
||||
}
|
||||
|
||||
var policyArray = policyString.split(" and ");
|
||||
|
||||
for (var i = 0; i < policyArray.length; i ++){
|
||||
var policyToken = policyArray[i];
|
||||
var re = /(\w+)\(*(\d*)\)*/;
|
||||
|
||||
var policyEntry = re.exec(policyToken);
|
||||
|
||||
policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
|
||||
|
||||
}
|
||||
|
||||
return policies;
|
||||
};
|
||||
|
||||
p.toString = function(policies) {
|
||||
if (!policies || policies.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var policyString = "";
|
||||
|
||||
for (var i in policies){
|
||||
policyString += policies[i].name;
|
||||
if ( policies[i].value ){
|
||||
policyString += '(' + policies[i].value + ')';
|
||||
}
|
||||
policyString += " and ";
|
||||
}
|
||||
|
||||
policyString = policyString.substring(0, policyString.length - 5);
|
||||
|
||||
return policyString;
|
||||
};
|
||||
|
||||
return p;
|
||||
});
|
||||
@@ -1,457 +0,0 @@
|
||||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngResource
|
||||
* @description
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ngdoc object
|
||||
* @name ngResource.$resource
|
||||
* @requires $http
|
||||
*
|
||||
* @description
|
||||
* A factory which creates a resource object that lets you interact with
|
||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
||||
*
|
||||
* The returned resource object has action methods which provide high-level behaviors without
|
||||
* the need to interact with the low level {@link ng.$http $http} service.
|
||||
*
|
||||
* # Installation
|
||||
* To use $resource make sure you have included the `angular-resource.js` that comes in Angular
|
||||
* package. You can also find this file on Google CDN, bower as well as at
|
||||
* {@link http://code.angularjs.org/ code.angularjs.org}.
|
||||
*
|
||||
* Finally load the module in your application:
|
||||
*
|
||||
* angular.module('app', ['ngResource']);
|
||||
*
|
||||
* and you are ready to get started!
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`. If you are using a URL with a port number (e.g.
|
||||
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
|
||||
* number, like this: `$resource('http://example.com\\:8080/api')`.
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods.
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
*
|
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
||||
* URL `/path/greet?salutation=Hello`.
|
||||
*
|
||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
||||
* the data object (useful for non-GET operations).
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
|
||||
* default set of resource actions. The declaration should be created in the following format:
|
||||
*
|
||||
* {action1: {method:?, params:?, isArray:?},
|
||||
* action2: {method:?, params:?, isArray:?},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `action` – {string} – The name of action. This name becomes the name of the method on your
|
||||
* resource object.
|
||||
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and `JSONP`
|
||||
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
|
||||
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
|
||||
* `returns` section.
|
||||
*
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
*
|
||||
* { 'get': {method:'GET'},
|
||||
* 'save': {method:'POST'},
|
||||
* 'query': {method:'GET', isArray:true},
|
||||
* 'remove': {method:'DELETE'},
|
||||
* 'delete': {method:'DELETE'} };
|
||||
*
|
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
|
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
||||
* read, update, delete) on server-side data like this:
|
||||
* <pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It is important to realize that invoking a $resource object method immediately returns an
|
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
* object results in no rendering, once the data arrives from the server then the object is
|
||||
* populated with the data and the view automatically re-renders itself showing the new data. This
|
||||
* means that in most case one never has to write a callback function for the action methods.
|
||||
*
|
||||
* The action methods on the class object or instance object can be invoked with the following
|
||||
* parameters:
|
||||
*
|
||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
|
||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
|
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
|
||||
*
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
*
|
||||
* <pre>
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query(function() {
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0];
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
});
|
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* </pre>
|
||||
*
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function() {
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the success callback for `get`, `query` and other method gets passed
|
||||
* in the response that came from the server as well as $http header getter function, so one
|
||||
* could rewrite the above example and get access to http headers as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(u, getResponseHeaders){
|
||||
u.abc = true;
|
||||
u.$save(function(u, putResponseHeaders) {
|
||||
//u => saved user object
|
||||
//putResponseHeaders => $http header getter
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
* # Buzz client
|
||||
|
||||
Let's look at what a buzz client created with the `$resource` service looks like:
|
||||
<doc:example>
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
function BuzzController($resource) {
|
||||
this.userId = 'googlebuzz';
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
|
||||
);
|
||||
}
|
||||
|
||||
BuzzController.prototype = {
|
||||
fetch: function() {
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
BuzzController.$inject = ['$resource'];
|
||||
</script>
|
||||
|
||||
<div ng-controller="BuzzController">
|
||||
<input ng-model="userId"/>
|
||||
<button ng-click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div ng-repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angular.module('ngResource', ['ng']).
|
||||
factory('$resource', ['$http', '$parse', function($http, $parse) {
|
||||
var DEFAULT_ACTIONS = {
|
||||
'get': {method:'GET'},
|
||||
'save': {method:'POST'},
|
||||
'query': {method:'GET', isArray:true},
|
||||
'remove': {method:'DELETE'},
|
||||
'delete': {method:'DELETE'}
|
||||
};
|
||||
var noop = angular.noop,
|
||||
forEach = angular.forEach,
|
||||
extend = angular.extend,
|
||||
copy = angular.copy,
|
||||
isFunction = angular.isFunction,
|
||||
getter = function(obj, path) {
|
||||
return $parse(path)(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
|
||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
||||
* segments:
|
||||
* segment = *pchar
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriSegment(val) {
|
||||
return encodeUriQuery(val, true).
|
||||
replace(/%26/gi, '&').
|
||||
replace(/%3D/gi, '=').
|
||||
replace(/%2B/gi, '+');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
|
||||
* method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
|
||||
* encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
return encodeURIComponent(val).
|
||||
replace(/%40/gi, '@').
|
||||
replace(/%3A/gi, ':').
|
||||
replace(/%24/g, '$').
|
||||
replace(/%2C/gi, ',').
|
||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
|
||||
function Route(template, defaults) {
|
||||
this.template = template = template + '#';
|
||||
this.defaults = defaults || {};
|
||||
var urlParams = this.urlParams = {};
|
||||
forEach(template.split(/\W/), function(param){
|
||||
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) {
|
||||
urlParams[param] = true;
|
||||
}
|
||||
});
|
||||
this.template = template.replace(/\\:/g, ':');
|
||||
}
|
||||
|
||||
Route.prototype = {
|
||||
url: function(params) {
|
||||
var self = this,
|
||||
url = this.template,
|
||||
val,
|
||||
encodedVal;
|
||||
|
||||
params = params || {};
|
||||
forEach(this.urlParams, function(_, urlParam){
|
||||
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
|
||||
if (angular.isDefined(val) && val !== null) {
|
||||
encodedVal = encodeUriSegment(val);
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1");
|
||||
} else {
|
||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match,
|
||||
leadingSlashes, tail) {
|
||||
if (tail.charAt(0) == '/') {
|
||||
return tail;
|
||||
} else {
|
||||
return leadingSlashes + tail;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
forEach(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
|
||||
}
|
||||
});
|
||||
query.sort();
|
||||
url = url.replace(/\/*$/, '');
|
||||
return url + (query.length ? '?' + query.join('&') : '');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function ResourceFactory(url, paramDefaults, actions) {
|
||||
var route = new Route(url);
|
||||
|
||||
actions = extend({}, DEFAULT_ACTIONS, actions);
|
||||
|
||||
function extractParams(data, actionParams){
|
||||
var ids = {};
|
||||
actionParams = extend({}, paramDefaults, actionParams);
|
||||
forEach(actionParams, function(value, key){
|
||||
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function Resource(value){
|
||||
copy(value || {}, this);
|
||||
}
|
||||
|
||||
forEach(actions, function(action, name) {
|
||||
action.method = angular.uppercase(action.method);
|
||||
var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
|
||||
Resource[name] = function(a1, a2, a3, a4) {
|
||||
var params = {};
|
||||
var data;
|
||||
var success = noop;
|
||||
var error = null;
|
||||
switch(arguments.length) {
|
||||
case 4:
|
||||
error = a4;
|
||||
success = a3;
|
||||
//fallthrough
|
||||
case 3:
|
||||
case 2:
|
||||
if (isFunction(a2)) {
|
||||
if (isFunction(a1)) {
|
||||
success = a1;
|
||||
error = a2;
|
||||
break;
|
||||
}
|
||||
|
||||
success = a2;
|
||||
error = a3;
|
||||
//fallthrough
|
||||
} else {
|
||||
params = a1;
|
||||
data = a2;
|
||||
success = a3;
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
if (isFunction(a1)) success = a1;
|
||||
else if (hasBody) data = a1;
|
||||
else params = a1;
|
||||
break;
|
||||
case 0: break;
|
||||
default:
|
||||
throw "Expected between 0-4 arguments [params, data, success, error], got " +
|
||||
arguments.length + " arguments.";
|
||||
}
|
||||
|
||||
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
|
||||
$http({
|
||||
method: action.method,
|
||||
url: route.url(extend({}, extractParams(data, action.params || {}), params)),
|
||||
data: data
|
||||
}).then(function(response) {
|
||||
var data = response.data;
|
||||
|
||||
if (data) {
|
||||
if (action.isArray) {
|
||||
value.length = 0;
|
||||
forEach(data, function(item) {
|
||||
value.push(new Resource(item));
|
||||
});
|
||||
} else {
|
||||
copy(data, value);
|
||||
}
|
||||
}
|
||||
(success||noop)(value, response.headers);
|
||||
}, error);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
|
||||
Resource.prototype['$' + name] = function(a1, a2, a3) {
|
||||
var params = extractParams(this),
|
||||
success = noop,
|
||||
error;
|
||||
|
||||
switch(arguments.length) {
|
||||
case 3: params = a1; success = a2; error = a3; break;
|
||||
case 2:
|
||||
case 1:
|
||||
if (isFunction(a1)) {
|
||||
success = a1;
|
||||
error = a2;
|
||||
} else {
|
||||
params = a1;
|
||||
success = a2 || noop;
|
||||
}
|
||||
case 0: break;
|
||||
default:
|
||||
throw "Expected between 1-3 arguments [params, success, error], got " +
|
||||
arguments.length + " arguments.";
|
||||
}
|
||||
var data = hasBody ? this : undefined;
|
||||
Resource[name].call(this, params, data, success, error);
|
||||
};
|
||||
});
|
||||
|
||||
Resource.bind = function(additionalParamDefaults){
|
||||
return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
|
||||
};
|
||||
|
||||
return Resource;
|
||||
}
|
||||
|
||||
return ResourceFactory;
|
||||
}]);
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
@@ -1,537 +0,0 @@
|
||||
/**
|
||||
* @license AngularJS v1.0.7
|
||||
* (c) 2010-2012 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*/
|
||||
(function(window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name ngSanitize
|
||||
* @description
|
||||
*/
|
||||
|
||||
/*
|
||||
* HTML Parser By Misko Hevery (misko@hevery.com)
|
||||
* based on: HTML Parser By John Resig (ejohn.org)
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* // Use like so:
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name ngSanitize.$sanitize
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
|
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
||||
* it into the returned string, however, since our parser is more strict than a typical browser
|
||||
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
||||
* browser, won't make it through the sanitizer.
|
||||
*
|
||||
* @param {string} html Html input.
|
||||
* @returns {string} Sanitized html.
|
||||
*
|
||||
* @example
|
||||
<doc:example module="ngSanitize">
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.snippet =
|
||||
'<p style="color:blue">an html\n' +
|
||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
||||
'snippet</p>';
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="html-filter">
|
||||
<td>html filter</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippet"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng-bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng-bind-html-unsafe="snippet"><br/></div></pre></td>
|
||||
<td><div ng-bind-html-unsafe="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should sanitize the html snippet ', function() {
|
||||
expect(using('#html-filter').element('div').html()).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
|
||||
it('should escape snippet without any filter', function() {
|
||||
expect(using('#escaped-html').element('div').html()).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should inline raw snippet if filtered as unsafe', function() {
|
||||
expect(using('#html-unsafe-filter').element("div").html()).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('snippet').enter('new <b>text</b>');
|
||||
expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
|
||||
expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
var $sanitize = function(html) {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
|
||||
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
||||
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
|
||||
|
||||
|
||||
// Good source of info about elements and attributes
|
||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
||||
// http://simon.html5.org/html-elements
|
||||
|
||||
// Safe Void Elements - HTML5
|
||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
||||
var voidElements = makeMap("area,br,col,hr,img,wbr");
|
||||
|
||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
||||
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
||||
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
|
||||
optionalEndTagInlineElements = makeMap("rp,rt"),
|
||||
optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
|
||||
|
||||
// Safe Block Elements - HTML5
|
||||
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
|
||||
"blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
|
||||
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
||||
|
||||
// Inline Elements - HTML5
|
||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
|
||||
"big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
|
||||
"span,strike,strong,sub,sup,time,tt,u,var"));
|
||||
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style");
|
||||
|
||||
var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
|
||||
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
|
||||
var validAttrs = angular.extend({}, uriAttrs, makeMap(
|
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
||||
'valign,value,vspace,width'));
|
||||
|
||||
function makeMap(str) {
|
||||
var obj = {}, items = str.split(','), i;
|
||||
for (i = 0; i < items.length; i++) obj[items[i]] = true;
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @example
|
||||
* htmlParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* @param {string} html string
|
||||
* @param {object} handler
|
||||
*/
|
||||
function htmlParser( html, handler ) {
|
||||
var index, chars, match, stack = [], last = html;
|
||||
stack.last = function() { return stack[ stack.length - 1 ]; };
|
||||
|
||||
while ( html ) {
|
||||
chars = true;
|
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !specialElements[ stack.last() ] ) {
|
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") === 0 ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if (handler.comment) handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// end tag
|
||||
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
||||
match = html.match( END_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( END_TAG_REGEXP, parseEndTag );
|
||||
chars = false;
|
||||
}
|
||||
|
||||
// start tag
|
||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
|
||||
match = html.match( START_TAG_REGEXP );
|
||||
|
||||
if ( match ) {
|
||||
html = html.substring( match[0].length );
|
||||
match[0].replace( START_TAG_REGEXP, parseStartTag );
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( chars ) {
|
||||
index = html.indexOf("<");
|
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
}
|
||||
|
||||
} else {
|
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
|
||||
text = text.
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
|
||||
if ( html == last ) {
|
||||
throw "Parse Error: " + html;
|
||||
}
|
||||
last = html;
|
||||
}
|
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) {
|
||||
tagName = angular.lowercase(tagName);
|
||||
if ( blockElements[ tagName ] ) {
|
||||
while ( stack.last() && inlineElements[ stack.last() ] ) {
|
||||
parseEndTag( "", stack.last() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
|
||||
parseEndTag( "", tagName );
|
||||
}
|
||||
|
||||
unary = voidElements[ tagName ] || !!unary;
|
||||
|
||||
if ( !unary )
|
||||
stack.push( tagName );
|
||||
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
|
||||
var value = doubleQuotedValue
|
||||
|| singleQoutedValue
|
||||
|| unqoutedValue
|
||||
|| '';
|
||||
|
||||
attrs[name] = decodeEntities(value);
|
||||
});
|
||||
if (handler.start) handler.start( tagName, attrs, unary );
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
var pos = 0, i;
|
||||
tagName = angular.lowercase(tagName);
|
||||
if ( tagName )
|
||||
// Find the closest opened tag of the same type
|
||||
for ( pos = stack.length - 1; pos >= 0; pos-- )
|
||||
if ( stack[ pos ] == tagName )
|
||||
break;
|
||||
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
if (handler.end) handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* decodes all entities into regular string
|
||||
* @param value
|
||||
* @returns {string} A string with decoded entities.
|
||||
*/
|
||||
var hiddenPre=document.createElement("pre");
|
||||
function decodeEntities(value) {
|
||||
hiddenPre.innerHTML=value.replace(/</g,"<");
|
||||
return hiddenPre.innerText || hiddenPre.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all potentially dangerous characters, so that the
|
||||
* resulting string can be safely inserted into attribute or
|
||||
* element text.
|
||||
* @param value
|
||||
* @returns escaped text
|
||||
*/
|
||||
function encodeEntities(value) {
|
||||
return value.
|
||||
replace(/&/g, '&').
|
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
||||
return '&#' + value.charCodeAt(0) + ';';
|
||||
}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* create an HTML/XML writer which writes to buffer
|
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string
|
||||
* @returns {object} in the form of {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* }
|
||||
*/
|
||||
function htmlSanitizeWriter(buf){
|
||||
var ignore = false;
|
||||
var out = angular.bind(buf, buf.push);
|
||||
return {
|
||||
start: function(tag, attrs, unary){
|
||||
tag = angular.lowercase(tag);
|
||||
if (!ignore && specialElements[tag]) {
|
||||
ignore = tag;
|
||||
}
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('<');
|
||||
out(tag);
|
||||
angular.forEach(attrs, function(value, key){
|
||||
var lkey=angular.lowercase(key);
|
||||
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
|
||||
out(' ');
|
||||
out(key);
|
||||
out('="');
|
||||
out(encodeEntities(value));
|
||||
out('"');
|
||||
}
|
||||
});
|
||||
out(unary ? '/>' : '>');
|
||||
}
|
||||
},
|
||||
end: function(tag){
|
||||
tag = angular.lowercase(tag);
|
||||
if (!ignore && validElements[tag] == true) {
|
||||
out('</');
|
||||
out(tag);
|
||||
out('>');
|
||||
}
|
||||
if (tag == ignore) {
|
||||
ignore = false;
|
||||
}
|
||||
},
|
||||
chars: function(chars){
|
||||
if (!ignore) {
|
||||
out(encodeEntities(chars));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// define ngSanitize module and register $sanitize service
|
||||
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name ngSanitize.directive:ngBindHtml
|
||||
*
|
||||
* @description
|
||||
* Creates a binding that will sanitize the result of evaluating the `expression` with the
|
||||
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
|
||||
*
|
||||
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
|
||||
*/
|
||||
angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
|
||||
return function(scope, element, attr) {
|
||||
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
|
||||
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
|
||||
value = $sanitize(value);
|
||||
element.html(value || '');
|
||||
});
|
||||
};
|
||||
}]);
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name ngSanitize.filter:linky
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
||||
* plain email address links.
|
||||
*
|
||||
* @param {string} text Input text.
|
||||
* @returns {string} Html-linkified text.
|
||||
*
|
||||
* @usage
|
||||
<span ng-bind-html="linky_expression | linky"></span>
|
||||
*
|
||||
* @example
|
||||
<doc:example module="ngSanitize">
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl($scope) {
|
||||
$scope.snippet =
|
||||
'Pretty text with some links:\n'+
|
||||
'http://angularjs.org/,\n'+
|
||||
'mailto:us@somewhere.org,\n'+
|
||||
'another@somewhere.org,\n'+
|
||||
'and one more: ftp://127.0.0.1/.';
|
||||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="linky-filter">
|
||||
<td>linky filter</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br></div></pre></td>
|
||||
<td><div ng-bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should linkify the snippet with urls', function() {
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links: ' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>, ' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>, ' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' +
|
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
||||
});
|
||||
|
||||
it ('should not linkify snippet without the linky filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("Pretty text with some links:\n" +
|
||||
"http://angularjs.org/,\n" +
|
||||
"mailto:us@somewhere.org,\n" +
|
||||
"another@somewhere.org,\n" +
|
||||
"and one more: ftp://127.0.0.1/.");
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('snippet').enter('new http://link.');
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angular.module('ngSanitize').filter('linky', function() {
|
||||
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
|
||||
MAILTO_REGEXP = /^mailto:/;
|
||||
|
||||
return function(text) {
|
||||
if (!text) return text;
|
||||
var match;
|
||||
var raw = text;
|
||||
var html = [];
|
||||
// TODO(vojta): use $sanitize instead
|
||||
var writer = htmlSanitizeWriter(html);
|
||||
var url;
|
||||
var i;
|
||||
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0];
|
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2] == match[3]) url = 'mailto:' + url;
|
||||
i = match.index;
|
||||
writer.chars(raw.substr(0, i));
|
||||
writer.start('a', {href:url});
|
||||
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
|
||||
writer.end('a');
|
||||
raw = raw.substring(i + match[0].length);
|
||||
}
|
||||
writer.chars(raw);
|
||||
return html.join('');
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
})(window, window.angular);
|
||||
@@ -1,195 +0,0 @@
|
||||
/**
|
||||
* Enhanced Select2 Dropmenus
|
||||
*
|
||||
* @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
|
||||
* This change is so that you do not have to do an additional query yourself on top of Select2's own query
|
||||
* @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
|
||||
*/
|
||||
angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) {
|
||||
var options = {};
|
||||
if (uiSelect2Config) {
|
||||
angular.extend(options, uiSelect2Config);
|
||||
}
|
||||
return {
|
||||
require: 'ngModel',
|
||||
compile: function (tElm, tAttrs) {
|
||||
var watch,
|
||||
repeatOption,
|
||||
repeatAttr,
|
||||
isSelect = tElm.is('select'),
|
||||
isMultiple = (tAttrs.multiple !== undefined);
|
||||
|
||||
// Enable watching of the options dataset if in use
|
||||
if (tElm.is('select')) {
|
||||
repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');
|
||||
|
||||
if (repeatOption.length) {
|
||||
repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
|
||||
watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
|
||||
}
|
||||
}
|
||||
|
||||
return function (scope, elm, attrs, controller) {
|
||||
// instance-specific options
|
||||
var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
|
||||
|
||||
/*
|
||||
Convert from Select2 view-model to Angular view-model.
|
||||
*/
|
||||
var convertToAngularModel = function(select2_data) {
|
||||
var model;
|
||||
if (opts.simple_tags) {
|
||||
model = []
|
||||
angular.forEach(select2_data, function(value, index) {
|
||||
model.push(value.id)
|
||||
})
|
||||
} else {
|
||||
model = select2_data
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
/*
|
||||
Convert from Angular view-model to Select2 view-model.
|
||||
*/
|
||||
var convertToSelect2Model = function(angular_data) {
|
||||
var model = []
|
||||
if (!angular_data) {
|
||||
return model;
|
||||
}
|
||||
|
||||
if (opts.simple_tags) {
|
||||
model = [];
|
||||
angular.forEach(
|
||||
angular_data,
|
||||
function(value, index) {
|
||||
model.push({'id': value, 'text': value});
|
||||
})
|
||||
} else {
|
||||
model = angular_data;
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
if (isSelect) {
|
||||
// Use <select multiple> instead
|
||||
delete opts.multiple;
|
||||
delete opts.initSelection;
|
||||
} else if (isMultiple) {
|
||||
opts.multiple = true;
|
||||
}
|
||||
|
||||
if (controller) {
|
||||
// Watch the model for programmatic changes
|
||||
scope.$watch(tAttrs.ngModel, function(current, old) {
|
||||
if (!current) {
|
||||
return
|
||||
}
|
||||
if (current == old) {
|
||||
return
|
||||
}
|
||||
controller.$render()
|
||||
}, true)
|
||||
controller.$render = function () {
|
||||
if (isSelect) {
|
||||
elm.select2('val', controller.$viewValue);
|
||||
} else {
|
||||
if (opts.multiple) {
|
||||
elm.select2(
|
||||
'data', convertToSelect2Model(controller.$viewValue));
|
||||
} else {
|
||||
if (angular.isObject(controller.$viewValue)) {
|
||||
elm.select2('data', controller.$viewValue);
|
||||
} else if (!controller.$viewValue) {
|
||||
elm.select2('data', null);
|
||||
} else {
|
||||
elm.select2('val', controller.$viewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Watch the options dataset for changes
|
||||
if (watch) {
|
||||
scope.$watch(watch, function (newVal, oldVal, scope) {
|
||||
if (!newVal) return;
|
||||
// Delayed so that the options have time to be rendered
|
||||
$timeout(function () {
|
||||
elm.select2('val', controller.$viewValue);
|
||||
// Refresh angular to remove the superfluous option
|
||||
elm.trigger('change');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update valid and dirty statuses
|
||||
controller.$parsers.push(function (value) {
|
||||
var div = elm.prev()
|
||||
div
|
||||
.toggleClass('ng-invalid', !controller.$valid)
|
||||
.toggleClass('ng-valid', controller.$valid)
|
||||
.toggleClass('ng-invalid-required', !controller.$valid)
|
||||
.toggleClass('ng-valid-required', controller.$valid)
|
||||
.toggleClass('ng-dirty', controller.$dirty)
|
||||
.toggleClass('ng-pristine', controller.$pristine);
|
||||
return value;
|
||||
});
|
||||
|
||||
if (!isSelect) {
|
||||
// Set the view and model value and update the angular template manually for the ajax/multiple select2.
|
||||
elm.bind("change", function () {
|
||||
if (scope.$$phase) return;
|
||||
scope.$apply(function () {
|
||||
controller.$setViewValue(
|
||||
convertToAngularModel(elm.select2('data')));
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.initSelection) {
|
||||
var initSelection = opts.initSelection;
|
||||
opts.initSelection = function (element, callback) {
|
||||
initSelection(element, function (value) {
|
||||
controller.$setViewValue(convertToAngularModel(value));
|
||||
callback(value);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elm.bind("$destroy", function() {
|
||||
elm.select2("destroy");
|
||||
});
|
||||
|
||||
attrs.$observe('disabled', function (value) {
|
||||
elm.select2('enable', !value);
|
||||
});
|
||||
|
||||
attrs.$observe('readonly', function (value) {
|
||||
elm.select2('readonly', !!value);
|
||||
});
|
||||
|
||||
if (attrs.ngMultiple) {
|
||||
scope.$watch(attrs.ngMultiple, function(newVal) {
|
||||
elm.select2(opts);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the plugin late so that the injected DOM does not disrupt the template compiler
|
||||
$timeout(function () {
|
||||
elm.select2(opts);
|
||||
|
||||
// Set initial value - I'm not sure about this but it seems to need to be there
|
||||
elm.val(controller.$viewValue);
|
||||
// important!
|
||||
controller.$render();
|
||||
|
||||
// Not sure if I should just check for !isSelect OR if I should check for 'tags' key
|
||||
if (!opts.initSelection && !isSelect)
|
||||
controller.$setViewValue(
|
||||
convertToAngularModel(elm.select2('data')));
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}]);
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* jQuery Idle Timeout 1.1
|
||||
* Copyright (c) 2011 Eric Hynds
|
||||
*
|
||||
* http://www.erichynds.com/jquery/a-new-and-improved-jquery-idle-timeout-plugin/
|
||||
*
|
||||
* Depends:
|
||||
* - jQuery 1.4.2+
|
||||
* - jQuery Idle Timer (by Paul Irish, http://paulirish.com/2009/jquery-idletimer-plugin/)
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
|
||||
(function($, win){
|
||||
|
||||
var idleTimeout = {
|
||||
init: function( element, resume, options ){
|
||||
var self = this, elem;
|
||||
|
||||
this.warning = elem = $(element);
|
||||
this.resume = $(resume);
|
||||
this.options = options;
|
||||
this.countdownOpen = false;
|
||||
this.failedRequests = options.failedRequests;
|
||||
this._startTimer();
|
||||
|
||||
// expose obj to data cache so peeps can call internal methods
|
||||
$.data( elem[0], 'idletimout', this );
|
||||
|
||||
// start the idle timer
|
||||
$.idleTimer(options.idleAfter * 1000);
|
||||
|
||||
// once the user becomes idle
|
||||
$(document).bind("idle.idleTimer", function(){
|
||||
|
||||
// if the user is idle and a countdown isn't already running
|
||||
if( $.data(document, 'idleTimer') === 'idle' && !self.countdownOpen ){
|
||||
self._stopTimer();
|
||||
self.countdownOpen = true;
|
||||
self._idle();
|
||||
}
|
||||
});
|
||||
|
||||
// bind continue link
|
||||
this.resume.bind("click", function(e){
|
||||
e.preventDefault();
|
||||
|
||||
win.clearInterval(self.countdown); // stop the countdown
|
||||
self.countdownOpen = false; // stop countdown
|
||||
self._startTimer(); // start up the timer again
|
||||
self._keepAlive( false ); // ping server
|
||||
options.onResume.call( self.warning ); // call the resume callback
|
||||
});
|
||||
},
|
||||
|
||||
_idle: function(){
|
||||
var self = this,
|
||||
options = this.options,
|
||||
warning = this.warning[0],
|
||||
counter = options.warningLength;
|
||||
|
||||
// fire the onIdle function
|
||||
options.onIdle.call(warning);
|
||||
|
||||
// set inital value in the countdown placeholder
|
||||
options.onCountdown.call(warning, counter);
|
||||
|
||||
// create a timer that runs every second
|
||||
this.countdown = win.setInterval(function(){
|
||||
if(--counter === 0){
|
||||
window.clearInterval(self.countdown);
|
||||
options.onTimeout.call(warning);
|
||||
} else {
|
||||
options.onCountdown.call(warning, counter);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
_startTimer: function(){
|
||||
var self = this;
|
||||
|
||||
if (this.options.pollingInterval > 0) {
|
||||
this.timer = win.setTimeout(function () {
|
||||
self._keepAlive();
|
||||
}, this.options.pollingInterval * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
_stopTimer: function(){
|
||||
// reset the failed requests counter
|
||||
this.failedRequests = this.options.failedRequests;
|
||||
win.clearTimeout(this.timer);
|
||||
},
|
||||
|
||||
_keepAlive: function( recurse ){
|
||||
var self = this,
|
||||
options = this.options;
|
||||
|
||||
if( typeof recurse === "undefined" ){
|
||||
recurse = true;
|
||||
}
|
||||
|
||||
// if too many requests failed, abort
|
||||
if( !this.failedRequests ){
|
||||
console.log('aborting...');
|
||||
this._stopTimer();
|
||||
options.onAbort.call( this.warning[0] );
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
timeout: options.AJAXTimeout,
|
||||
url: options.keepAliveURL,
|
||||
error: function(){
|
||||
console.log('failure for keepalive');
|
||||
self.failedRequests--;
|
||||
},
|
||||
success: function(response){
|
||||
console.log('success for keepalive');
|
||||
/* if($.trim(response) !== options.serverResponseEquals){
|
||||
self.failedRequests--;
|
||||
}*/
|
||||
},
|
||||
complete: function(){
|
||||
if( recurse ){
|
||||
self._startTimer();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// expose
|
||||
$.idleTimeout = function(element, resume, options){
|
||||
idleTimeout.init( element, resume, $.extend($.idleTimeout.options, options) );
|
||||
return this;
|
||||
};
|
||||
|
||||
// options
|
||||
$.idleTimeout.options = {
|
||||
// number of seconds after user is idle to show the warning
|
||||
warningLength: 30,
|
||||
|
||||
// url to call to keep the session alive while the user is active
|
||||
keepAliveURL: "",
|
||||
|
||||
// the response from keepAliveURL must equal this text:
|
||||
serverResponseEquals: "OK",
|
||||
|
||||
// user is considered idle after this many seconds. 10 minutes default
|
||||
idleAfter: 600,
|
||||
|
||||
// a polling request will be sent to the server every X seconds
|
||||
pollingInterval: 60,
|
||||
|
||||
// number of failed polling requests until we abort this script
|
||||
failedRequests: 5,
|
||||
|
||||
// the $.ajax timeout in MILLISECONDS!
|
||||
AJAXTimeout: 250,
|
||||
|
||||
/*
|
||||
Callbacks
|
||||
"this" refers to the element found by the first selector passed to $.idleTimeout.
|
||||
*/
|
||||
// callback to fire when the session times out
|
||||
onTimeout: $.noop,
|
||||
|
||||
// fires when the user becomes idle
|
||||
onIdle: $.noop,
|
||||
|
||||
// fires during each second of warningLength
|
||||
onCountdown: $.noop,
|
||||
|
||||
// fires when the user resumes the session
|
||||
onResume: $.noop,
|
||||
|
||||
// callback to fire when the script is aborted due to too many failed requests
|
||||
onAbort: $.noop
|
||||
};
|
||||
|
||||
})(jQuery, window);
|
||||
@@ -1,116 +0,0 @@
|
||||
(function($){
|
||||
|
||||
$.idleTimer = function f(newTimeout){
|
||||
|
||||
//$.idleTimer.tId = -1 //timeout ID
|
||||
|
||||
var idle = false, //indicates if the user is idle
|
||||
enabled = true, //indicates if the idle timer is enabled
|
||||
timeout = 30000, //the amount of time (ms) before the user is considered idle
|
||||
events = 'mousemove keydown DOMMouseScroll mousewheel mousedown', // activity is one of these events
|
||||
//f.olddate = undefined, // olddate used for getElapsedTime. stored on the function
|
||||
|
||||
/* (intentionally not documented)
|
||||
* Toggles the idle state and fires an appropriate event.
|
||||
* @return {void}
|
||||
*/
|
||||
toggleIdleState = function(){
|
||||
|
||||
//toggle the state
|
||||
idle = !idle;
|
||||
|
||||
// reset timeout counter
|
||||
f.olddate = +new Date;
|
||||
|
||||
//fire appropriate event
|
||||
$(document).trigger( $.data(document,'idleTimer', idle ? "idle" : "active" ) + '.idleTimer');
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the idle timer. This removes appropriate event handlers
|
||||
* and cancels any pending timeouts.
|
||||
* @return {void}
|
||||
* @method stop
|
||||
* @static
|
||||
*/
|
||||
stop = function(){
|
||||
|
||||
//set to disabled
|
||||
enabled = false;
|
||||
|
||||
//clear any pending timeouts
|
||||
clearTimeout($.idleTimer.tId);
|
||||
|
||||
//detach the event handlers
|
||||
$(document).unbind('.idleTimer');
|
||||
},
|
||||
|
||||
|
||||
/* (intentionally not documented)
|
||||
* Handles a user event indicating that the user isn't idle.
|
||||
* @param {Event} event A DOM2-normalized event object.
|
||||
* @return {void}
|
||||
*/
|
||||
handleUserEvent = function(){
|
||||
|
||||
//clear any existing timeout
|
||||
clearTimeout($.idleTimer.tId);
|
||||
|
||||
|
||||
|
||||
//if the idle timer is enabled
|
||||
if (enabled){
|
||||
|
||||
|
||||
//if it's idle, that means the user is no longer idle
|
||||
if (idle){
|
||||
toggleIdleState();
|
||||
}
|
||||
|
||||
//set a new timeout
|
||||
$.idleTimer.tId = setTimeout(toggleIdleState, timeout);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Starts the idle timer. This adds appropriate event handlers
|
||||
* and starts the first timeout.
|
||||
* @param {int} newTimeout (Optional) A new value for the timeout period in ms.
|
||||
* @return {void}
|
||||
* @method $.idleTimer
|
||||
* @static
|
||||
*/
|
||||
|
||||
|
||||
f.olddate = f.olddate || +new Date;
|
||||
|
||||
//assign a new timeout if necessary
|
||||
if (typeof newTimeout == "number"){
|
||||
timeout = newTimeout;
|
||||
} else if (newTimeout === 'destroy') {
|
||||
stop();
|
||||
return this;
|
||||
} else if (newTimeout === 'getElapsedTime'){
|
||||
return (+new Date) - f.olddate;
|
||||
}
|
||||
|
||||
//assign appropriate event handlers
|
||||
$(document).bind($.trim((events+' ').split(' ').join('.idleTimer ')),handleUserEvent);
|
||||
|
||||
|
||||
//set a timeout to toggle state
|
||||
$.idleTimer.tId = setTimeout(toggleIdleState, timeout);
|
||||
|
||||
// assume the user is active for the first x seconds.
|
||||
$.data(document,'idleTimer',"active");
|
||||
|
||||
|
||||
|
||||
|
||||
}; // end of $.idleTimer()
|
||||
|
||||
|
||||
|
||||
})(jQuery);
|
||||
@@ -1,36 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">{{application.name}}</a></li>
|
||||
<li class="active">Credentials</li>
|
||||
</ol>
|
||||
<h2 data-ng-hide="create"><span>{{application.name}}</span> Credentials</h2>
|
||||
<form class="form-horizontal" name="credentialForm" novalidate>
|
||||
<fieldset >
|
||||
<legend><span class="text">Client Secret</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="secret">Secret</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-disabled="true" class="form-control" type="text" id="secret" name="secret" data-ng-model="secret" autofocus
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions">
|
||||
<button type="submit" data-ng-click="changePassword()" class="btn btn-primary btn-lg">Regenerate Secret
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,122 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-show="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li class="active">Add Application</li>
|
||||
</ol>
|
||||
<h2 data-ng-show="create"><span>{{realm.realm}}</span> Add Application</h2>
|
||||
<p class="subtitle" data-ng-show="create"><span class="required">*</span> Required fields</p>
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">{{application.name}}</a></li>
|
||||
<li class="active">Settings</li>
|
||||
</ol>
|
||||
<h2 data-ng-hide="create"><span>{{application.name}}</span> Settings</h2>
|
||||
<form class="form-horizontal" name="applicationForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="create">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="application.name" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" name="baseUrl" id="baseUrl"
|
||||
data-ng-model="application.baseUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" name="adminUrl" id="adminUrl"
|
||||
data-ng-model="application.adminUrl">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
|
||||
<div class="col-sm-4">
|
||||
<div ng-repeat="webOrigin in application.webOrigins" class="row kc-item-deletable">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control kc-button-control"
|
||||
type="text" data-ng-class="{'input-below':!$first}"
|
||||
name="webOrigin" id="webOrigin"
|
||||
data-ng-model="webOrigin" readonly/>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-danger"
|
||||
type="button" data-ng-click="deleteWebOrigin($index)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control kc-button-control"
|
||||
type="text" name="newWebOrigin" id="newWebOrigin"
|
||||
placeholder="New Web Origin..." data-ng-model="newWebOrigin"
|
||||
data-ng-class="{'input-below':application.webOrigins.length}"/>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary"
|
||||
data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI</label>
|
||||
<div class="col-sm-4">
|
||||
<div ng-repeat="redirectUri in application.redirectUris" class="row kc-item-deletable">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" data-ng-class="{'input-below':!$first}"
|
||||
name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
|
||||
Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
|
||||
placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
|
||||
data-ng-class="{'input-below':application.redirectUris.length}" />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="create">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
<div class="pull-right form-actions" data-ng-show="!create">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,100 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
|
||||
<li><a href="#">Installation</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">{{application.name}}</a></li>
|
||||
<li class="active">Scope</li>
|
||||
</ol>
|
||||
<h2><span>{{application.name}}</span> Scope Mappings</h2>
|
||||
<p class="subtitle"></p>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend><span class="text">Realm Roles</span></legend>
|
||||
<div class="form-group col-sm-10">
|
||||
<div class="controls changing-selectors">
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="available">Available Roles</label>
|
||||
<select id="available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmRoles"
|
||||
ng-options="r.name for r in realmRoles">
|
||||
</select>
|
||||
</div>
|
||||
<div class="middle-buttons kc-vertical">
|
||||
<button class="kc-button" type="submit" ng-click="addRealmRole()" tooltip="Move right" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-right">Move right</span>
|
||||
</button>
|
||||
<button class="kc-button" type="submit" ng-click="deleteRealmRole()" tooltip="Move left" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-left">Move left</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="assigned">Assigned Roles</label>
|
||||
<select id="assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmMappings"
|
||||
ng-options="r.name for r in realmMappings">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="applications.length > 0">
|
||||
<legend><span class="text">Application Roles</span> </legend>
|
||||
<div class="form-group input-select">
|
||||
<label class="col-sm-2 control-label" for="applications">Application</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<div class="select-kc">
|
||||
<select id="applications" name="applications" ng-change="changeApplication()" ng-model="targetApp" ng-options="a.name for a in (applications|remove:application:'id')">
|
||||
<option value="" selected> Select an Application </option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" ng-show="targetApp">
|
||||
<div class="controls changing-selectors col-sm-10">
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="app-available">Available Roles</label>
|
||||
<select id="app-available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedApplicationRoles"
|
||||
ng-options="r.name for r in applicationRoles">
|
||||
</select>
|
||||
</div>
|
||||
<div class="middle-buttons kc-vertical">
|
||||
<button class="kc-button" type="submit" ng-click="addApplicationRole()" tooltip="Move right" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-right">Move right</span>
|
||||
</button>
|
||||
<button class="kc-button" type="submit" ng-click="deleteApplicationRole()" tooltip="Move left" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-left">Move left</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="app-assigned">Assigned Roles</label>
|
||||
<select id="app-assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedApplicationMappings"
|
||||
ng-options="r.name for r in applicationMappings">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/installation">Installation</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/roles">Roles</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/scope-mappings">Scope</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/applications/{{application.name}}/sessions">Sessions</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/applications/{{application.name}}">{{application.name}}</a></li>
|
||||
<li class="active">Sessions</li>
|
||||
</ol>
|
||||
<h2 data-ng-hide="create"><span>{{application.name}}</span> Sessions (Placeholder Page)</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,48 +0,0 @@
|
||||
<nav id="kc-navigation" class="navbar" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<h1 class="navbar-title"></h1>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse navbar-collapse-1">
|
||||
<ul class="nav navbar-nav navbar-utility" data-ng-show="auth.loggedIn">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
|
||||
<span class="pficon pficon-user"></span>
|
||||
{{auth.user.displayName}}<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/auth/rest/realms/keycloak-admin/account?referrer=admin-console">Manage Account</a></li>
|
||||
<li class="separator"><a href="/auth/rest/admin/logout">Sign Out</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-primary persistent-secondary" data-ng-controller="RealmDropdownCtrl">
|
||||
<li class="dropdown context" data-ng-show="showNav()">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
{{current.realm.realm}}
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li data-ng-repeat="realm in current.realms">
|
||||
<a href="" ng-click="changeRealm(realm.realm)">{{realm.realm}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="active pull-right" data-ng-show="auth.loggedIn">
|
||||
<a class="button primary" href="#/create/realm" data-ng-class="path[0] == 'create' && path[1] == 'realm' && 'active'"
|
||||
data-ng-show="auth.loggedIn">Add Realm</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- TODO remove once this page is properly styled -->
|
||||
<style type="text/css">
|
||||
.icon-spinner6 {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,104 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-show="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||
<li class="active">Add OAuth Client</li>
|
||||
</ol>
|
||||
<h2 data-ng-show="create"><span>{{realm.realm}}</span> Add OAuth Client</h2>
|
||||
<p class="subtitle" data-ng-show="create"><span class="required">*</span> Required fields</p>
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">{{oauth.name}}</a></li>
|
||||
<li class="active">Settings</li>
|
||||
</ol>
|
||||
<h2 data-ng-hide="create"><span>{{oauth.name}}</span> Settings</h2>
|
||||
<form class="form-horizontal" name="oauthForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="create">*</span></label>
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="oauth.name" autofocus
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group clearfix block">
|
||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="oauth.enabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
|
||||
<div class="col-sm-4">
|
||||
<div ng-repeat="webOrigin in oauth.webOrigins" class="kc-item-deletable row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" data-ng-class="{'input-below':!$first}"
|
||||
name="webOrigin" id="webOrigin" data-ng-model="webOrigin" readonly />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-danger" type="button" data-ng-click="deleteWebOrigin($index)">
|
||||
Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" name="newWebOrigin" id="newWebOrigin"
|
||||
placeholder="New Web Origin..." data-ng-model="newWebOrigin"
|
||||
data-ng-class="{'input-below':oauth.webOrigins.length}" />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary" data-ng-click="addWebOrigin()" ng-show="newWebOrigin.length > 0">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="newRedirectUri">Redirect URI</label>
|
||||
<div class="col-sm-4">
|
||||
<div ng-repeat="redirectUri in oauth.redirectUris" class="kc-item-deletable row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" data-ng-class="{'input-below':!$first}"
|
||||
name="redirectUri" id="redirectUri" data-ng-model="redirectUri" readonly />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-danger" type="button" data-ng-click="deleteRedirectUri($index)">
|
||||
Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" type="text" name="newRedirectUri" id="newRedirectUri"
|
||||
placeholder="New Redirect URI..." data-ng-model="newRedirectUri"
|
||||
data-ng-class="{'input-below':oauth.redirectUris.length}" />
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary" data-ng-click="addRedirectUri()" ng-show="newRedirectUri.length > 0">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="create">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
<div class="pull-right form-actions" data-ng-show="!create">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="bs-sidebar col-sm-3" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
|
||||
</ul>
|
||||
<div id="content">
|
||||
<h2>OAuth Client Installation</h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<textarea class="form-control" rows="20" kc-select-action="click">{{installation | json}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<div class="pull-right form-actions">
|
||||
<a class="btn btn-primary btn-lg" href="{{download}}" download="keycloak.json" type="submit">Download</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,98 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">Settings</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/credentials">Credentials</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/scope-mappings">Scope</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}/installation">Installation</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-hide="create">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/oauth-clients/{{oauth.id}}">{{oauth.name}}</a></li>
|
||||
<li class="active">Scope</li>
|
||||
</ol>
|
||||
<h2><span>{{oauth.name}}</span> Scope Mappings</h2>
|
||||
<p class="subtitle"></p>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend><span class="text">Realm Roles</span></legend>
|
||||
<div class="form-group col-sm-10">
|
||||
<div class="controls changing-selectors">
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="available">Available Roles</label>
|
||||
<select id="available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmRoles"
|
||||
ng-options="r.name for r in realmRoles">
|
||||
</select>
|
||||
</div>
|
||||
<div class="middle-buttons kc-vertical">
|
||||
<button class="kc-button" type="submit" ng-click="addRealmRole()" tooltip="Move right" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-right">Move right</span>
|
||||
</button>
|
||||
<button class="kc-button" type="submit" ng-click="deleteRealmRole()" tooltip="Move left" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-left">Move left</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="assigned">Assigned Roles</label>
|
||||
<select id="assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedRealmMappings"
|
||||
ng-options="r.name for r in realmMappings">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset ng-show="applications.length > 0">
|
||||
<legend><span class="text">Application Roles</span> </legend>
|
||||
<div class="form-group input-select">
|
||||
<label class="col-sm-2 control-label" for="applications">Application</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<div class="select-kc">
|
||||
<select id="applications" name="applications" ng-change="changeApplication()" ng-model="targetApp" ng-options="a.name for a in (applications)">
|
||||
<option value="" selected> Select an Application </option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="targetApp">
|
||||
<div class="controls changing-selectors col-sm-10">
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="app-available">Available Roles</label>
|
||||
<select id="app-available" class="form-control" multiple size="5"
|
||||
ng-multiple="true"
|
||||
ng-model="selectedApplicationRoles"
|
||||
ng-options="r.name for r in applicationRoles">
|
||||
</select>
|
||||
</div>
|
||||
<div class="middle-buttons kc-vertical">
|
||||
<button class="kc-button" type="submit" ng-click="addApplicationRole()" tooltip="Move right" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-right">Move right</span>
|
||||
</button>
|
||||
<button class="kc-button" type="submit" ng-click="deleteApplicationRole()" tooltip="Move left" tooltip-placement="right">
|
||||
<span class="kc-icon-arrow-left">Move left</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="select-title">
|
||||
<label class="control-label" for="app-assigned">Assigned Roles</label>
|
||||
<select id="app-assigned" class="form-control" multiple size=5
|
||||
ng-multiple="true"
|
||||
ng-model="selectedApplicationMappings"
|
||||
ng-options="r.name for r in applicationMappings">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,104 +0,0 @@
|
||||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<kc-navigation data-kc-current="general" data-kc-realm="realm.realm" data-kc-social="social"></kc-navigation>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb" data-ng-hide="createRealm">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||
<li class="active">General</li>
|
||||
</ol>
|
||||
<h2 class="pull-left" data-ng-show="createRealm">Add Realm</h2>
|
||||
<h2 data-ng-hide="createRealm"><span>{{realm.realm}}</span> General Settings</h2>
|
||||
<p class="subtitle" data-ng-show="createRealm"><span class="required">*</span> Required fields</p>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend class="aj-collapse open"><span class="text">Required Settings</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend><span class="text">Login Options</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="social">Social login</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.social" name="social" id="social" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-ng-show="realm.social">
|
||||
<label class="col-sm-2 control-label" for="updateProfileOnInitialSocialLogin">Update profile on first social login</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.updateProfileOnInitialSocialLogin" name="updateProfileOnInitialSocialLogin" id="updateProfileOnInitialSocialLogin" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="registrationAllowed" class="col-sm-2 control-label">User registration</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="resetPasswordAllowed" class="col-sm-2 control-label">Reset password</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="verifyEmail" class="col-sm-2 control-label">Verify email</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="requireSsl" class="col-sm-2 control-label">Require SSL</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend><span class="text">Optional Settings</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="loginTheme">Login Theme</label>
|
||||
<div class="col-sm-4">
|
||||
<select kc-select id="loginTheme"
|
||||
data-kc-placeholder="Select one..."
|
||||
data-kc-model="realm.loginTheme"
|
||||
data-kc-options="serverInfo.themes.login">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="accountTheme">Account Theme</label>
|
||||
<div class="col-sm-4">
|
||||
<select kc-select id="accountTheme"
|
||||
data-kc-placeholder="Select one..."
|
||||
data-kc-model="realm.accountTheme"
|
||||
data-kc-options="serverInfo.themes.account">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="createRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
<div class="pull-right form-actions" data-ng-show="!createRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,116 +0,0 @@
|
||||
<h2 class="pull-left" data-ng-show="createRealm">Add Realm</h2>
|
||||
<h2 data-ng-hide="createRealm"><span>{{realm.realm}}</span> General Settings</h2>
|
||||
<p class="subtitle" data-ng-show="createRealm"><span class="required">*</span> Required fields</p>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<legend uncollapsed class="aj-collapse open"><span class="text">Required Settings</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label control-label" for="enabled">Enabled</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend uncollapsed><span class="text">Login Options</span></legend>
|
||||
<div class="form-group clearfix block">
|
||||
<label for="social" class="col-sm-2 control-label">Social login</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.social" name="social" id="social" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block" data-ng-show="realm.social">
|
||||
<label for="updateProfileOnInitialSocialLogin" class="col-sm-2 control-label">Update profile on first social login</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.updateProfileOnInitialSocialLogin" name="updateProfileOnInitialSocialLogin" id="updateProfileOnInitialSocialLogin" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label for="registrationAllowed" class="col-sm-2 control-label">User registration</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label for="resetPasswordAllowed" class="col-sm-2 control-label">Reset password</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label for="verifyEmail" class="col-sm-2 control-label">Verify email</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix block">
|
||||
<label for="requireSsl" class="col-sm-2 control-label">Require SSL</label>
|
||||
<div class="col-sm-4">
|
||||
<input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend uncollapsed><span class="text">Optional Settings</span></legend>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 control-label" for="loginTheme">Login Theme</label>
|
||||
<!--<select id="loginTheme" name="loginTheme" ng-model="realm.loginTheme" ng-options="t for t in serverInfo.themes.login"></select>-->
|
||||
<div class="col-sm-4">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
|
||||
{{realm.loginTheme}}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
||||
<li role="presentation" data-ng-repeat="t in serverInfo.themes.login">
|
||||
<a role="menuitem" tabindex="-1" href="" data-ng-click="realm.loginTheme = t">{{t}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="accountTheme">Account Theme</label>
|
||||
<!--<select id="accountTheme" name="accountTheme" ng-model="realm.accountTheme" ng-options="t for t in serverInfo.themes.account"></select>-->
|
||||
<div class="col-sm-4">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
|
||||
{{realm.accountTheme}}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
|
||||
<li role="presentation" data-ng-repeat="t in serverInfo.themes.account">
|
||||
<a role="menuitem" tabindex="-1" href="" data-ng-click="realm.accountTheme = t">{{t}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="accountTheme2">Account Theme2</label>
|
||||
<!--<select id="accountTheme" name="accountTheme" ng-model="realm.accountTheme" ng-options="t for t in serverInfo.themes.account"></select>-->
|
||||
<div class="col-sm-4">
|
||||
<kc-select data-kc-model="realm.accountTheme" data-kc-options="serverInfo.themes.account"/>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions" data-ng-show="createRealm">
|
||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
<div class="pull-right form-actions" data-ng-show="!createRealm">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,27 +0,0 @@
|
||||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<data-kc-navigation data-kc-current="keys" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||
<li class="active">Keys</li>
|
||||
</ol>
|
||||
<h2><span>{{realm.realm}}</span> Keys</h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="publicKey">Public key</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<textarea type="text" id="publicKey" name="publicKey" class="form-control" rows="5"
|
||||
kc-select-action="click" readonly>{{realm.publicKey}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions">
|
||||
<button class="btn btn-primary btn-lg" type="submit" data-ng-click="generate()">Generate new keys</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,34 +0,0 @@
|
||||
<div id="wrapper" class="container">
|
||||
<div class="row">
|
||||
<div class="bs-sidebar col-md-3 clearfix">
|
||||
<ul data-ng-hide="createRealm">
|
||||
<li class="active"><a href="#/realms">Realms</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
<div class="top-nav" data-ng-hide="createRealm">
|
||||
<ul class="rcue-tabs">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div id="content">
|
||||
|
||||
<h1>
|
||||
<span class="gray">Realms</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Realm</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr data-ng-repeat="r in realms">
|
||||
<td><a href="#/realms/{{r.realm}}">{{r.realm}}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="container-right-bg"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
<ul data-ng-hide="createRealm">
|
||||
<li data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
|
||||
path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'default-roles' || path[2] == 'registration-settings' ||
|
||||
path[2] == 'keys-settings' || path[2] == 'smtp-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||
<li data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
|
||||
</li>
|
||||
<li data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
|
||||
<li data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||
<li class="active">Social</li>
|
||||
</ol>
|
||||
<h2><span>{{realm.realm}}</span> Social Providers Settings</h2>
|
||||
|
||||
<div class="alert alert-info alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">
|
||||
<span class="pficon pficon-close"></span>
|
||||
</button>
|
||||
<span class="pficon pficon-info"></span>
|
||||
<strong>Social Callback URL:</strong> {{callbackUrl}}</p>
|
||||
</div>
|
||||
|
||||
<form name="realmForm" novalidate>
|
||||
<fieldset>
|
||||
<div>
|
||||
<table class="table">
|
||||
<caption class="hidden">Table of social providers</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="5" class="kc-table-actions">
|
||||
<div class="pull-right">
|
||||
<div class="select-kc">
|
||||
<select ng-model="newProviderId"
|
||||
ng-options="(p|capitalize) for p in (allProviders|remove:configuredProviders)"
|
||||
data-ng-change="addProvider(newProviderId); newProviderId = null">
|
||||
<option value="" disabled selected>Add provider...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr ng-show="configuredProviders.length > 0">
|
||||
<th>Provider</th>
|
||||
<th>Key <span class="required">*</span></th>
|
||||
<th>Secret <span class="required">*</span></th>
|
||||
<th colspan="1">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ng-show="configuredProviders.length > 0">
|
||||
<tr ng-repeat="pId in configuredProviders">
|
||||
<td>
|
||||
<div class="clearfix">
|
||||
<input class="form-control input-small disabled" type="text" value="{{pId|capitalize}}" readonly>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" type="text" placeholder="Key" ng-model="realm.socialProviders[pId+'.key']"
|
||||
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control" type="text" placeholder="Secret" ng-model="realm.socialProviders[pId+'.secret']"
|
||||
ng-class="{'dirty': postSaveProviders.indexOf(pId) > -1}" required>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-div"><i class="pficon pficon-delete" ng-click="removeProvider(pId)"></i></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="pull-right form-actions">
|
||||
<button type="submit" kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button type="submit" kc-save class="primary" data-ng-show="changed">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO remove once this page is properly styled -->
|
||||
<style type="text/css">
|
||||
.social-provider input.ng-invalid.dirty,
|
||||
.social-provider input.ng-invalid.ng-dirty {
|
||||
background-color: #FFEEEE;
|
||||
}
|
||||
|
||||
.social-provider .actions > div {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.social-provider td {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.social-provider .action-div {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.social-provider .icon-remove, .social-provider .icon-question {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +0,0 @@
|
||||
<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-sm-9" role="main">
|
||||
<data-kc-navigation data-kc-current="token" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
|
||||
<li class="active">Token</li>
|
||||
</ol>
|
||||
<h2><span>{{realm.realm}}</span> Token Settings</h2>
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<div class="form-group input-select">
|
||||
<label class="col-sm-2 control-label" for="tokenLifespan">Token lifespan</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control" type="number" required min="1"
|
||||
max="31536000" data-ng-model="realm.tokenLifespan"
|
||||
id="tokenLifespan" name="tokenLifespan"/>
|
||||
</div>
|
||||
<div class="col-sm-2 select-kc">
|
||||
<select name="tokenLifespanUnit" data-ng-model="realm.tokenLifespanUnit" >
|
||||
<option data-ng-selected="!realm.tokenLifespanUnit">Seconds</option>
|
||||
<option>Minutes</option>
|
||||
<option>Hours</option>
|
||||
<option>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="accessCodeLifespan">Access code lifespan</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespan" id="accessCodeLifespan" name="accessCodeLifespan">
|
||||
</div>
|
||||
<div class="col-sm-2 select-kc">
|
||||
<select name="accessCodeLifespanUnit" data-ng-model="realm.accessCodeLifespanUnit">
|
||||
<option data-ng-selected="!realm.accessCodeLifespanUnit">Seconds</option>
|
||||
<option>Minutes</option>
|
||||
<option>Hours</option>
|
||||
<option>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group input-select">
|
||||
<label class="col-sm-2 control-label" for="accessCodeLifespanUserAction" class="two-lines">Access code user action lifespan</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanUserAction" id="accessCodeLifespanUserAction" name="accessCodeLifespanUserAction">
|
||||
</div>
|
||||
<div class="col-sm-2 select-kc">
|
||||
<select name="accessCodeLifespanUserActionUnit" data-ng-model="realm.accessCodeLifespanUserActionUnit">
|
||||
<option data-ng-selected="!realm.accessCodeLifespanUserActionUnit">Seconds</option>
|
||||
<option>Minutes</option>
|
||||
<option>Hours</option>
|
||||
<option>Days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="pull-right form-actions">
|
||||
<button kc-reset data-ng-show="changed">Clear changes</button>
|
||||
<button kc-save data-ng-show="changed">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,46 +0,0 @@
|
||||
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
|
||||
<div id="content-area" class="col-md-9" role="main">
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
|
||||
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
|
||||
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
|
||||
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="content">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
|
||||
<li class="active">Users</li>
|
||||
</ol>
|
||||
<h2><span>{{user.username}}'s</span> Credentials</h2>
|
||||
|
||||
<form class="form-horizontal" name="userForm" novalidate>
|
||||
<fieldset class="border-top">
|
||||
<legend><span class="text">Credential Management</span></legend>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="password">Reset password</label>
|
||||
<div class="col-sm-4 kc-multiline">
|
||||
<input class="form-control" type="password" id="password" name="password" data-ng-model="password" placeholder="Temporary password" required>
|
||||
<input class="form-control" type="password" id="confirmPassword" name="confirmPassword" data-ng-model="confirmPassword" placeholder="Password confirmation" required>
|
||||
<button class="btn btn-danger" type="submit" data-ng-click="resetPassword()" data-ng-show="password">Reset Password</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="user.email">
|
||||
<label class="col-sm-2 control-label" for="password">Reset password email</label>
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-danger" type="submit" data-ng-click="resetPasswordEmail()" >Send Email</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" data-ng-show="user.totp">
|
||||
<label class="col-sm-2 control-label" for="password">Remove totp</label>
|
||||
<div class="col-sm-4" data-ng-show="user.totp">
|
||||
<button class="btn btn-danger" type="submit" data-ng-click="removeTotp()">Remove TOTP</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +0,0 @@
|
||||
<div class="modal-dialog" tabindex="-1" role="dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
|
||||
<span class="pficon pficon-close"></span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="myModalLabel">{{ title }}</h4>
|
||||
</div>
|
||||
<div class="modal-body"><p ng-bind-html-unsafe="message"></p></div>
|
||||
<div class="modal-footer">
|
||||
<button ng-repeat="btn in buttons" ng-click="close(btn.result)" class="" ng-class="btn.cssClass">{{ btn.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
<ul class="nav nav-tabs nav-tabs-pf">
|
||||
<li ng-class="{active: kcCurrent == 'general'}"><a href="#/realms/{{kcRealm}}">General</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'social'}" data-ng-show="kcSocial"><a href="#/realms/{{kcRealm}}/social-settings">Social</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'roles'}"><a href="#/realms/{{kcRealm}}/roles">Roles</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'defRoles'}"><a href="#/realms/{{kcRealm}}/default-roles">Default Roles</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'credentials'}"><a href="#/realms/{{kcRealm}}/required-credentials">Credentials</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'token'}"><a href="#/realms/{{kcRealm}}/token-settings">Token</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'keys'}"><a href="#/realms/{{kcRealm}}/keys-settings">Keys</a></li>
|
||||
<li ng-class="{active: kcCurrent == 'email'}"><a href="#/realms/{{kcRealm}}/smtp-settings">Email</a></li>
|
||||
</ul>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd ">
|
||||
</web-fragment>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-core</artifactId>
|
||||
<name>Keycloak Broker Core</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.resteasy</groupId>
|
||||
<artifactId>resteasy-jaxrs</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractIdentityProvider<C extends IdentityProviderModel> implements IdentityProvider<C> {
|
||||
|
||||
private final C config;
|
||||
|
||||
public AbstractIdentityProvider(C config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public C getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractIdentityProviderFactory<T extends IdentityProvider> implements IdentityProviderFactory<T> {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public T create(KeycloakSession session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class AuthenticationRequest {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final UriInfo uriInfo;
|
||||
private final String state;
|
||||
private final HttpRequest httpRequest;
|
||||
private final RealmModel realm;
|
||||
private final String redirectUri;
|
||||
private final ClientSessionModel clientSession;
|
||||
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.httpRequest = httpRequest;
|
||||
this.uriInfo = uriInfo;
|
||||
this.state = state;
|
||||
this.redirectUri = redirectUri;
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
public KeycloakSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public UriInfo getUriInfo() {
|
||||
return this.uriInfo;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public HttpRequest getHttpRequest() {
|
||||
return this.httpRequest;
|
||||
}
|
||||
|
||||
public RealmModel getRealm() {
|
||||
return this.realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the redirect url that must be included in an authentication request in order to process responses from an
|
||||
* identity provider.</p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getRedirectUri() {
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
public ClientSessionModel getClientSession() {
|
||||
return this.clientSession;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
/**
|
||||
* <p>Represents all identity information obtained from an {@link IdentityProvider} after a
|
||||
* successful authentication.</p>
|
||||
*
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class FederatedIdentity {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private String token;
|
||||
private String identityProviderId;
|
||||
private String brokerSessionId;
|
||||
private String brokerUserId;
|
||||
|
||||
public FederatedIdentity(String id) {
|
||||
if (id == null) {
|
||||
throw new RuntimeException("No identifier provider for identity.");
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (name != null) {
|
||||
int i = name.lastIndexOf(' ');
|
||||
if (i != -1) {
|
||||
firstName = name.substring(0, i);
|
||||
lastName = name.substring(i + 1);
|
||||
} else {
|
||||
firstName = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return this.token;
|
||||
}
|
||||
|
||||
public String getIdentityProviderId() {
|
||||
return this.identityProviderId;
|
||||
}
|
||||
|
||||
public void setIdentityProviderId(String identityProviderId) {
|
||||
this.identityProviderId = identityProviderId;
|
||||
}
|
||||
|
||||
public String getBrokerSessionId() {
|
||||
return brokerSessionId;
|
||||
}
|
||||
|
||||
public void setBrokerSessionId(String brokerSessionId) {
|
||||
this.brokerSessionId = brokerSessionId;
|
||||
}
|
||||
|
||||
public String getBrokerUserId() {
|
||||
return brokerUserId;
|
||||
}
|
||||
|
||||
public void setBrokerUserId(String brokerUserId) {
|
||||
this.brokerUserId = brokerUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"id='" + id + '\'' +
|
||||
", username='" + username + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", token='" + token + '\'' +
|
||||
", identityProviderId='" + identityProviderId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
/**
|
||||
* @author pedroigor
|
||||
*/
|
||||
public class IdentityBrokerException extends RuntimeException {
|
||||
|
||||
public IdentityBrokerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public IdentityBrokerException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
||||
|
||||
public interface AuthenticationCallback {
|
||||
/**
|
||||
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
|
||||
* with the remote IDP
|
||||
*
|
||||
* @param userNotes notes to add to the UserSessionModel
|
||||
* @param identityProviderConfig provider config
|
||||
* @param federatedIdentity federated identity
|
||||
* @param code relayState or state parameter used to identity the client session
|
||||
* @return
|
||||
*/
|
||||
public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code);
|
||||
}
|
||||
|
||||
/**
|
||||
* JAXRS callback endpoint for when the remote IDP wants to callback to keycloak.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event);
|
||||
|
||||
/**
|
||||
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
|
||||
* only once during the authentication.</p>
|
||||
*
|
||||
* <p>Depending on how the authentication is performed, this method may redirect the user to the identity provider for authentication.
|
||||
* In this case, the response would contain a {@link javax.ws.rs.core.Response} that will be used to redirect the user.</p>
|
||||
*
|
||||
* <p>However, if the authentication flow does not require a redirect to the identity provider (eg.: simple challenge/response mechanism), this method may return a response containing
|
||||
* a {@link FederatedIdentity} representing the identity information for an user. In this case, the authentication flow stops.</p>
|
||||
*
|
||||
* @param request The initial authentication request. Contains all the contextual information in order to build an authentication request to the
|
||||
* identity provider.
|
||||
* @return
|
||||
*/
|
||||
Response handleRequest(AuthenticationRequest request);
|
||||
|
||||
/**
|
||||
* <p>Returns a {@link javax.ws.rs.core.Response} containing the token previously stored during the authentication process for a
|
||||
* specific user.</p>
|
||||
*
|
||||
* @param identity
|
||||
* @return
|
||||
*/
|
||||
Response retrieveToken(FederatedIdentityModel identity);
|
||||
|
||||
/**
|
||||
* Called when a Keycloak application initiates a logout through the browser. This is expected to do a logout
|
||||
* with the IDP
|
||||
*
|
||||
* @param userSession
|
||||
* @param uriInfo
|
||||
* @param realm
|
||||
* @return null if this is not supported by this provider
|
||||
*/
|
||||
Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Response export(UriInfo uriInfo, RealmModel realm, String format);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public interface IdentityProviderFactory<T extends IdentityProvider> extends ProviderFactory<T> {
|
||||
|
||||
/**
|
||||
* <p>A friendly name for this factory.</p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link IdentityProvider} based on the configuration contained in
|
||||
* <code>model</code>.</p>
|
||||
*
|
||||
* @param model The configuration to be used to create the identity provider.
|
||||
* @return
|
||||
*/
|
||||
T create(IdentityProviderModel model);
|
||||
|
||||
/**
|
||||
* <p>Creates an {@link IdentityProvider} based on the configuration from
|
||||
* <code>inputStream</code>.</p>
|
||||
*
|
||||
* @param model The model containing the common abd basic configuration for an identity provider.
|
||||
* @param inputStream The input stream from where configuration will be loaded from..
|
||||
* @return
|
||||
*/
|
||||
Map<String, String> parseConfig(InputStream inputStream);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.provider;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class IdentityProviderSpi implements Spi {
|
||||
|
||||
public static final String IDENTITY_PROVIDER_SPI_NAME = "identity_provider";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return IDENTITY_PROVIDER_SPI_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return IdentityProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return IdentityProviderFactory.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.broker.provider.IdentityProviderSpi
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-oidc</artifactId>
|
||||
<name>Keycloak Broker - OpenID Connect Identity Provider</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-broker-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityProviderConfig> extends AbstractIdentityProvider<C> {
|
||||
protected static final Logger logger = Logger.getLogger(AbstractOAuth2IdentityProvider.class);
|
||||
|
||||
public static final String OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code";
|
||||
public static final String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
|
||||
public static final String FEDERATED_REFRESH_TOKEN = "FEDERATED_REFRESH_TOKEN";
|
||||
public static final String FEDERATED_TOKEN_EXPIRATION = "FEDERATED_TOKEN_EXPIRATION";
|
||||
protected static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static final String OAUTH2_PARAMETER_ACCESS_TOKEN = "access_token";
|
||||
public static final String OAUTH2_PARAMETER_SCOPE = "scope";
|
||||
public static final String OAUTH2_PARAMETER_STATE = "state";
|
||||
public static final String OAUTH2_PARAMETER_RESPONSE_TYPE = "response_type";
|
||||
public static final String OAUTH2_PARAMETER_REDIRECT_URI = "redirect_uri";
|
||||
public static final String OAUTH2_PARAMETER_CODE = "code";
|
||||
public static final String OAUTH2_PARAMETER_CLIENT_ID = "client_id";
|
||||
public static final String OAUTH2_PARAMETER_CLIENT_SECRET = "client_secret";
|
||||
public static final String OAUTH2_PARAMETER_GRANT_TYPE = "grant_type";
|
||||
|
||||
|
||||
public AbstractOAuth2IdentityProvider(C config) {
|
||||
super(config);
|
||||
|
||||
if (config.getDefaultScope() == null || config.getDefaultScope().isEmpty()) {
|
||||
config.setDefaultScope(getDefaultScopes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new Endpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response handleRequest(AuthenticationRequest request) {
|
||||
try {
|
||||
URI authorizationUrl = createAuthorizationUrl(request).build();
|
||||
|
||||
return Response.temporaryRedirect(authorizationUrl).build();
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response retrieveToken(FederatedIdentityModel identity) {
|
||||
return Response.ok(identity.getToken()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public C getConfig() {
|
||||
return super.getConfig();
|
||||
}
|
||||
|
||||
protected String extractTokenFromResponse(String response, String tokenName) {
|
||||
if (response.startsWith("{")) {
|
||||
try {
|
||||
return mapper.readTree(response).get(tokenName).getTextValue();
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not extract token [" + tokenName + "] from response [" + response + "].", e);
|
||||
}
|
||||
} else {
|
||||
Matcher matcher = Pattern.compile(tokenName + "=([^&]+)").matcher(response);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
|
||||
String accessToken = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new IdentityBrokerException("No access token from server.");
|
||||
}
|
||||
|
||||
return doGetFederatedIdentity(accessToken);
|
||||
}
|
||||
|
||||
|
||||
protected FederatedIdentity doGetFederatedIdentity(String accessToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
||||
return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
|
||||
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
|
||||
.queryParam(OAUTH2_PARAMETER_STATE, request.getState())
|
||||
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
|
||||
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
|
||||
}
|
||||
|
||||
protected String getJsonProperty(JsonNode jsonNode, String name) {
|
||||
if (jsonNode.has(name)) {
|
||||
return jsonNode.get(name).asText();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected JsonNode asJsonNode(String json) throws IOException {
|
||||
return mapper.readTree(json);
|
||||
}
|
||||
|
||||
protected abstract String getDefaultScopes();
|
||||
|
||||
protected class Endpoint {
|
||||
protected AuthenticationCallback callback;
|
||||
protected RealmModel realm;
|
||||
protected EventBuilder event;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
protected HttpHeaders headers;
|
||||
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
public Endpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
this.callback = callback;
|
||||
this.realm = realm;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@GET
|
||||
public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_STATE) String state,
|
||||
@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_PARAMETER_CODE) String authorizationCode,
|
||||
@QueryParam(OAuth2Constants.ERROR) String error) {
|
||||
if (error != null) {
|
||||
//logger.error("Failed " + getConfig().getAlias() + " broker login: " + error);
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(error);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (authorizationCode != null) {
|
||||
String response = generateTokenRequest(authorizationCode).asString();
|
||||
|
||||
HashMap<String, String> userNotes = new HashMap<String, String>();
|
||||
FederatedIdentity federatedIdentity = getFederatedIdentity(userNotes, response);
|
||||
|
||||
if (getConfig().isStoreToken()) {
|
||||
federatedIdentity.setToken(response);
|
||||
}
|
||||
|
||||
return callback.authenticated(userNotes, getConfig(), federatedIdentity, state);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to make identity provider oauth callback", e);
|
||||
}
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
||||
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
||||
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.keycloak.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.PemUtils;
|
||||
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
||||
|
||||
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new KeycloakEndpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
protected class KeycloakEndpoint extends OIDCEndpoint {
|
||||
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
super(callback, realm, event);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path(AdapterConstants.K_LOGOUT)
|
||||
public Response backchannelLogout(String input) {
|
||||
JWSInput token = new JWSInput(input);
|
||||
PublicKey key = getExternalIdpKey();
|
||||
if (key != null) {
|
||||
if (!verify(token, key)) {
|
||||
logger.warn("Failed to verify logout request");
|
||||
return Response.status(400).build();
|
||||
}
|
||||
}
|
||||
LogoutAction action = null;
|
||||
try {
|
||||
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!validateAction(action)) return Response.status(400).build();
|
||||
if (action.getKeycloakSessionIds() != null) {
|
||||
for (String sessionId : action.getKeycloakSessionIds()) {
|
||||
String brokerSessionId = getConfig().getAlias() + "." + sessionId;
|
||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (userSession != null) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
protected boolean validateAction(AdminAction action) {
|
||||
if (!action.validate()) {
|
||||
logger.warn("admin request failed, not validated" + action.getAction());
|
||||
return false;
|
||||
}
|
||||
if (action.isExpired()) {
|
||||
logger.warn("admin request failed, expired token");
|
||||
return false;
|
||||
}
|
||||
if (!getConfig().getClientId().equals(action.getResource())) {
|
||||
logger.warn("Resource name does not match");
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||
return super.generateTokenRequest(authorizationCode)
|
||||
.param(AdapterConstants.APPLICATION_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<KeycloakOIDCIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "keycloak-oidc";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Keycloak OpenID Connect";
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakOIDCIdentityProvider create(IdentityProviderModel model) {
|
||||
return new KeycloakOIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
|
||||
|
||||
public OAuth2IdentityProviderConfig(IdentityProviderModel model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
public String getAuthorizationUrl() {
|
||||
return getConfig().get("authorizationUrl");
|
||||
}
|
||||
|
||||
public void setAuthorizationUrl(String authorizationUrl) {
|
||||
getConfig().put("authorizationUrl", authorizationUrl);
|
||||
}
|
||||
|
||||
public String getTokenUrl() {
|
||||
return getConfig().get("tokenUrl");
|
||||
}
|
||||
|
||||
public void setTokenUrl(String tokenUrl) {
|
||||
getConfig().put("tokenUrl", tokenUrl);
|
||||
}
|
||||
|
||||
public String getUserInfoUrl() {
|
||||
return getConfig().get("userInfoUrl");
|
||||
}
|
||||
|
||||
public void setUserInfoUrl(String userInfoUrl) {
|
||||
getConfig().put("userInfoUrl", userInfoUrl);
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return getConfig().get("clientId");
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
getConfig().put("clientId", clientId);
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return getConfig().get("clientSecret");
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
getConfig().put("clientSecret", clientSecret);
|
||||
}
|
||||
|
||||
public String getDefaultScope() {
|
||||
return getConfig().get("defaultScope");
|
||||
}
|
||||
|
||||
public void setDefaultScope(String defaultScope) {
|
||||
getConfig().put("defaultScope", defaultScope);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.PemUtils;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIdentityProviderConfig> {
|
||||
protected static final Logger logger = Logger.getLogger(OIDCIdentityProvider.class);
|
||||
|
||||
public static final String OAUTH2_PARAMETER_PROMPT = "prompt";
|
||||
public static final String SCOPE_OPENID = "openid";
|
||||
public static final String FEDERATED_ID_TOKEN = "FEDERATED_ID_TOKEN";
|
||||
|
||||
public OIDCIdentityProvider(OIDCIdentityProviderConfig config) {
|
||||
super(config);
|
||||
|
||||
String defaultScope = config.getDefaultScope();
|
||||
|
||||
if (!defaultScope.contains(SCOPE_OPENID)) {
|
||||
config.setDefaultScope(SCOPE_OPENID + " " + defaultScope);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new OIDCEndpoint(callback, realm, event);
|
||||
}
|
||||
|
||||
protected PublicKey getExternalIdpKey() {
|
||||
String signingCert = getConfig().getCertificateSignatureVerifier();
|
||||
try {
|
||||
if (signingCert != null && !signingCert.trim().equals("")) {
|
||||
return PemUtils.decodeCertificate(signingCert).getPublicKey();
|
||||
} else if (getConfig().getPublicKeySignatureVerifier() != null && !getConfig().getPublicKeySignatureVerifier().trim().equals("")) {
|
||||
return PemUtils.decodePublicKey(getConfig().getPublicKeySignatureVerifier());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
protected boolean verify(JWSInput jws, PublicKey key) {
|
||||
if (key == null) return true;
|
||||
if (!getConfig().isValidateSignature()) return true;
|
||||
return RSAProvider.verify(jws, key);
|
||||
|
||||
}
|
||||
|
||||
protected class OIDCEndpoint extends Endpoint {
|
||||
public OIDCEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
|
||||
super(callback, realm, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GET
|
||||
@Path("logout_response")
|
||||
public Response logoutResponse(@Context UriInfo uriInfo,
|
||||
@QueryParam("state") String state) {
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, state);
|
||||
if (userSession == null) {
|
||||
logger.error("no valid user session");
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||
logger.error("usersession in different state");
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
|
||||
}
|
||||
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getLogoutUrl() == null || getConfig().getLogoutUrl().trim().equals("")) return null;
|
||||
UriBuilder logoutUri = UriBuilder.fromUri(getConfig().getLogoutUrl())
|
||||
.queryParam("state", userSession.getId());
|
||||
String idToken = userSession.getNote(FEDERATED_ID_TOKEN);
|
||||
if (idToken != null) logoutUri.queryParam("id_token_hint", idToken);
|
||||
String redirect = RealmsResource.brokerUrl(uriInfo)
|
||||
.path(IdentityBrokerService.class, "getEndpoint")
|
||||
.path(OIDCEndpoint.class, "logoutResponse")
|
||||
.build(realm.getName(), getConfig().getAlias()).toString();
|
||||
logoutUri.queryParam("post_logout_redirect_uri", redirect);
|
||||
return Response.status(302).location(logoutUri.build()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
||||
UriBuilder authorizationUrl = super.createAuthorizationUrl(request);
|
||||
String prompt = getConfig().getPrompt();
|
||||
|
||||
if (prompt != null && !prompt.isEmpty()) {
|
||||
authorizationUrl.queryParam(OAUTH2_PARAMETER_PROMPT, prompt);
|
||||
}
|
||||
|
||||
return authorizationUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FederatedIdentity getFederatedIdentity(Map<String, String> notes, String response) {
|
||||
AccessTokenResponse tokenResponse = null;
|
||||
try {
|
||||
tokenResponse = JsonSerialization.readValue(response, AccessTokenResponse.class);
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not decode access token response.", e);
|
||||
}
|
||||
PublicKey key = getExternalIdpKey();
|
||||
String accessToken = verifyAccessToken(key, tokenResponse);
|
||||
|
||||
String encodedIdToken = tokenResponse.getIdToken();
|
||||
|
||||
notes.put(FEDERATED_ACCESS_TOKEN, accessToken);
|
||||
notes.put(FEDERATED_ID_TOKEN, encodedIdToken);
|
||||
notes.put(FEDERATED_REFRESH_TOKEN, tokenResponse.getRefreshToken());
|
||||
notes.put(FEDERATED_TOKEN_EXPIRATION, Long.toString(tokenResponse.getExpiresIn()));
|
||||
|
||||
|
||||
IDToken idToken = validateIdToken(key, encodedIdToken);
|
||||
|
||||
try {
|
||||
String id = idToken.getSubject();
|
||||
String name = idToken.getName();
|
||||
String preferredUsername = idToken.getPreferredUsername();
|
||||
String email = idToken.getEmail();
|
||||
|
||||
if (id == null || name == null || preferredUsername == null || email == null && getConfig().getUserInfoUrl() != null) {
|
||||
JsonNode userInfo = SimpleHttp.doGet(getConfig().getUserInfoUrl())
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.asJson();
|
||||
|
||||
id = getJsonProperty(userInfo, "sub");
|
||||
name = getJsonProperty(userInfo, "name");
|
||||
preferredUsername = getJsonProperty(userInfo, "preferred_username");
|
||||
email = getJsonProperty(userInfo, "email");
|
||||
}
|
||||
|
||||
FederatedIdentity identity = new FederatedIdentity(id);
|
||||
|
||||
identity.setId(id);
|
||||
identity.setName(name);
|
||||
identity.setEmail(email);
|
||||
|
||||
identity.setBrokerUserId(getConfig().getAlias() + "." + id);
|
||||
if (tokenResponse.getSessionState() != null) {
|
||||
identity.setBrokerSessionId(getConfig().getAlias() + "." + tokenResponse.getSessionState());
|
||||
}
|
||||
|
||||
if (preferredUsername == null) {
|
||||
preferredUsername = email;
|
||||
}
|
||||
|
||||
if (preferredUsername == null) {
|
||||
preferredUsername = id;
|
||||
}
|
||||
|
||||
identity.setUsername(preferredUsername);
|
||||
|
||||
if (getConfig().isStoreToken()) {
|
||||
identity.setToken(response);
|
||||
}
|
||||
|
||||
return identity;
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not fetch attributes from userinfo endpoint.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String verifyAccessToken(PublicKey key, AccessTokenResponse tokenResponse) {
|
||||
String accessToken = tokenResponse.getToken();
|
||||
|
||||
if (accessToken == null) {
|
||||
throw new IdentityBrokerException("No access_token from server.");
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private IDToken validateIdToken(PublicKey key, String encodedToken) {
|
||||
if (encodedToken == null) {
|
||||
throw new IdentityBrokerException("No id_token from server.");
|
||||
}
|
||||
|
||||
try {
|
||||
JWSInput jws = new JWSInput(encodedToken);
|
||||
if (!verify(jws, key)) {
|
||||
throw new IdentityBrokerException("IDToken signature validation failed");
|
||||
}
|
||||
IDToken idToken = jws.readJsonContent(IDToken.class);
|
||||
|
||||
String aud = idToken.getAudience();
|
||||
String iss = idToken.getIssuer();
|
||||
|
||||
if (aud != null && !aud.equals(getConfig().getClientId())) {
|
||||
throw new IdentityBrokerException("Wrong audience from id_token..");
|
||||
}
|
||||
|
||||
String trustedIssuers = getConfig().getIssuer();
|
||||
|
||||
if (trustedIssuers != null) {
|
||||
String[] issuers = trustedIssuers.split(",");
|
||||
|
||||
for (String trustedIssuer : issuers) {
|
||||
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
||||
return idToken;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IdentityBrokerException("Wrong issuer from id_token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
||||
}
|
||||
return idToken;
|
||||
} catch (IOException e) {
|
||||
throw new IdentityBrokerException("Could not decode id token.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultScopes() {
|
||||
return "openid";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||
|
||||
public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
||||
super(identityProviderModel);
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return getConfig().get("prompt");
|
||||
}
|
||||
public void setPrompt(String prompt) {
|
||||
getConfig().put("prompt", prompt);
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return getConfig().get("issuer");
|
||||
}
|
||||
public void setIssuer(String issuer) {
|
||||
getConfig().put("issuer", issuer);
|
||||
}
|
||||
public String getLogoutUrl() {
|
||||
return getConfig().get("logoutUrl");
|
||||
}
|
||||
public void setLogoutUrl(String url) {
|
||||
getConfig().put("logoutUrl", url);
|
||||
}
|
||||
public String getCertificateSignatureVerifier() {
|
||||
return getConfig().get("certificateSignatureVerifier");
|
||||
}
|
||||
|
||||
public void setCertificateSignatureVerifier(String signingCertificate) {
|
||||
getConfig().put("certificateSignatureVerifier", signingCertificate);
|
||||
}
|
||||
public String getPublicKeySignatureVerifier() {
|
||||
return getConfig().get("publicKeySignatureVerifier");
|
||||
}
|
||||
|
||||
public void setPublicKeySignatureVerifier(String signingCertificate) {
|
||||
getConfig().put("publicKeySignatureVerifier", signingCertificate);
|
||||
}
|
||||
|
||||
public boolean isValidateSignature() {
|
||||
return Boolean.valueOf(getConfig().get("validateSignature"));
|
||||
}
|
||||
|
||||
public void setValidateSignature(boolean validateSignature) {
|
||||
getConfig().put("validateSignature", String.valueOf(validateSignature));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.oidc;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.keycloak.broker.oidc.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKParser;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.representations.JSONWebKeySet;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.PemUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<OIDCIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "oidc";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "OpenID Connect v1.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OIDCIdentityProvider create(IdentityProviderModel model) {
|
||||
return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
return parseOIDCConfig(inputStream);
|
||||
}
|
||||
|
||||
protected static Map<String, String> parseOIDCConfig(InputStream inputStream) {
|
||||
OIDCConfigurationRepresentation rep = null;
|
||||
try {
|
||||
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to load openid connect metadata", e);
|
||||
}
|
||||
OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(new IdentityProviderModel());
|
||||
config.setIssuer(rep.getIssuer());
|
||||
config.setLogoutUrl(rep.getLogoutEndpoint());
|
||||
config.setAuthorizationUrl(rep.getAuthorizationEndpoint());
|
||||
config.setTokenUrl(rep.getTokenEndpoint());
|
||||
config.setUserInfoUrl(rep.getUserinfoEndpoint());
|
||||
if (rep.getJwksUri() != null) {
|
||||
String uri = rep.getJwksUri();
|
||||
String keySetString = null;
|
||||
try {
|
||||
keySetString = SimpleHttp.doGet(uri).asString();
|
||||
JSONWebKeySet keySet = JsonSerialization.readValue(keySetString, JSONWebKeySet.class);
|
||||
for (JWK jwk : keySet.getKeys()) {
|
||||
JWKParser parse = JWKParser.create(jwk);
|
||||
if (parse.getJwk().getPublicKeyUse().equals(JWK.SIG_USE)) {
|
||||
PublicKey key = parse.toPublicKey();
|
||||
config.setPublicKeySignatureVerifier(KeycloakModelUtils.getPemFromKey(key));
|
||||
config.setValidateSignature(true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("F ailed to query JWKSet from: " + uri, e);
|
||||
}
|
||||
|
||||
}
|
||||
return config.getConfig();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.keycloak.social.utils;
|
||||
package org.keycloak.broker.oidc.util;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -12,12 +13,16 @@ import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
* @author Vlastimil Elias (velias at redhat dot com)
|
||||
*/
|
||||
public class SimpleHttp {
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private String url;
|
||||
private String method;
|
||||
private Map<String, String> headers;
|
||||
@@ -52,8 +57,8 @@ public class SimpleHttp {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JSONObject asJson() throws IOException {
|
||||
return new JSONObject(asString());
|
||||
public JsonNode asJson() throws IOException {
|
||||
return mapper.readTree(asString());
|
||||
}
|
||||
|
||||
public String asString() throws IOException {
|
||||
@@ -113,7 +118,11 @@ public class SimpleHttp {
|
||||
connection.setDoOutput(false);
|
||||
}
|
||||
|
||||
String ce = connection.getHeaderField("Content-Encoding");
|
||||
is = connection.getInputStream();
|
||||
if ("gzip".equals(ce)) {
|
||||
is = new GZIPInputStream(is);
|
||||
}
|
||||
return toString(is);
|
||||
} finally {
|
||||
if (os != null) {
|
||||
@@ -0,0 +1,2 @@
|
||||
org.keycloak.broker.oidc.OIDCIdentityProviderFactory
|
||||
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-parent</artifactId>
|
||||
<name>Keycloak Broker Parent</name>
|
||||
<description/>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>oidc</module>
|
||||
<module>saml</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-broker-saml</artifactId>
|
||||
<name>Keycloak Broker - SAML Identity Provider</name>
|
||||
<description/>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-broker-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-saml-protocol</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.picketlink</groupId>
|
||||
<artifactId>picketlink-federation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-events-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,458 @@
|
||||
package org.keycloak.broker.saml;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.ClientConnection;
|
||||
import org.keycloak.VerificationException;
|
||||
import org.keycloak.broker.provider.FederatedIdentity;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
|
||||
import org.keycloak.protocol.saml.SAMLRequestParser;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.picketlink.common.constants.GeneralConstants;
|
||||
import org.picketlink.common.constants.JBossSAMLConstants;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ConfigurationException;
|
||||
import org.picketlink.common.exceptions.ProcessingException;
|
||||
import org.picketlink.common.util.DocumentUtil;
|
||||
import org.picketlink.common.util.StaxParserUtil;
|
||||
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
|
||||
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
|
||||
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
|
||||
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.EncryptedAssertionType;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.RequestAbstractType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
||||
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.FormParam;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.IOException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class SAMLEndpoint {
|
||||
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
||||
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
|
||||
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
|
||||
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
|
||||
protected RealmModel realm;
|
||||
protected EventBuilder event;
|
||||
protected SAMLIdentityProviderConfig config;
|
||||
protected IdentityProvider.AuthenticationCallback callback;
|
||||
|
||||
@Context
|
||||
private UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
|
||||
@Context
|
||||
private ClientConnection clientConnection;
|
||||
|
||||
@Context
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
public SAMLEndpoint(RealmModel realm, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) {
|
||||
this.realm = realm;
|
||||
this.config = config;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@GET
|
||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||
return new PostBinding().execute(samlRequest, samlResponse, relayState);
|
||||
}
|
||||
|
||||
protected abstract class Binding {
|
||||
private boolean checkSsl() {
|
||||
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||
return true;
|
||||
} else {
|
||||
return !realm.getSslRequired().isRequired(clientConnection);
|
||||
}
|
||||
}
|
||||
|
||||
protected Response basicChecks(String samlRequest, String samlResponse) {
|
||||
if (!checkSsl()) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.SSL_REQUIRED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
|
||||
}
|
||||
if (!realm.isEnabled()) {
|
||||
event.event(EventType.LOGIN_ERROR);
|
||||
event.error(Errors.REALM_DISABLED);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
|
||||
}
|
||||
|
||||
if (samlRequest == null && samlResponse == null) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_REQUEST);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract String getBindingType();
|
||||
protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
|
||||
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
||||
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||
protected PublicKey getIDPKey() {
|
||||
X509Certificate certificate = null;
|
||||
try {
|
||||
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
|
||||
} catch (ProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return certificate.getPublicKey();
|
||||
}
|
||||
|
||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||
event = new EventBuilder(realm, session, clientConnection);
|
||||
Response response = basicChecks(samlRequest, samlResponse);
|
||||
if (response != null) return response;
|
||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
||||
else return handleSamlResponse(samlResponse, relayState);
|
||||
}
|
||||
|
||||
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
||||
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
|
||||
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
|
||||
// validate destination
|
||||
if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||
event.detail(Details.REASON, "invalid_destination");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
if (config.isValidateSignature()) {
|
||||
try {
|
||||
verifySignature(holder);
|
||||
} catch (VerificationException e) {
|
||||
logger.error("validation failed", e);
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
}
|
||||
|
||||
if (requestAbstractType instanceof LogoutRequestType) {
|
||||
logger.debug("** logout request");
|
||||
event.event(EventType.LOGOUT);
|
||||
LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
|
||||
return logoutRequest(logout, relayState);
|
||||
|
||||
} else {
|
||||
event.event(EventType.LOGIN);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
protected Response logoutRequest(LogoutRequestType request, String relayState) {
|
||||
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
|
||||
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
|
||||
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
|
||||
for (UserSessionModel userSession : userSessions) {
|
||||
try {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to do backchannel logout for userSession", e);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for (String sessionIndex : request.getSessionIndex()) {
|
||||
String brokerSessionId = brokerUserId + "." + sessionIndex;
|
||||
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
|
||||
if (userSession != null) {
|
||||
try {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
} catch (Exception e) {
|
||||
logger.warn("failed to do backchannel logout for userSession", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String issuerURL = getEntityId(uriInfo, realm);
|
||||
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
||||
builder.logoutRequestID(request.getID());
|
||||
builder.destination(config.getSingleLogoutServiceUrl());
|
||||
builder.issuer(issuerURL);
|
||||
builder.relayState(relayState);
|
||||
if (config.isWantAuthnRequestsSigned()) {
|
||||
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
}
|
||||
try {
|
||||
if (config.isPostBindingResponse()) {
|
||||
return builder.postBinding().response();
|
||||
} else {
|
||||
return builder.redirectBinding().response();
|
||||
}
|
||||
} catch (ConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
||||
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
|
||||
}
|
||||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
||||
|
||||
try {
|
||||
AssertionType assertion = getAssertion(responseType);
|
||||
SubjectType subject = assertion.getSubject();
|
||||
SubjectType.STSubType subType = subject.getSubType();
|
||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||
Map<String, String> notes = new HashMap<>();
|
||||
notes.put(SAML_FEDERATED_SUBJECT, subjectNameID.getValue());
|
||||
if (subjectNameID.getFormat() != null) notes.put(SAML_FEDERATED_SUBJECT_NAMEFORMAT, subjectNameID.getFormat().toString());
|
||||
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
|
||||
|
||||
identity.setUsername(subjectNameID.getValue());
|
||||
|
||||
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||
identity.setEmail(subjectNameID.getValue());
|
||||
}
|
||||
|
||||
if (config.isStoreToken()) {
|
||||
identity.setToken(samlResponse);
|
||||
}
|
||||
|
||||
AuthnStatementType authn = null;
|
||||
for (Object statement : assertion.getStatements()) {
|
||||
if (statement instanceof AuthnStatementType) {
|
||||
authn = (AuthnStatementType)statement;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
|
||||
identity.setBrokerUserId(brokerUserId);
|
||||
if (authn != null && authn.getSessionIndex() != null) {
|
||||
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
||||
notes.put(SAML_FEDERATED_SESSION_INDEX, authn.getSessionIndex());
|
||||
}
|
||||
return callback.authenticated(notes, config, identity, relayState);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
|
||||
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
|
||||
|
||||
if (assertions.isEmpty()) {
|
||||
throw new IdentityBrokerException("No assertion from response.");
|
||||
}
|
||||
|
||||
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
|
||||
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
|
||||
|
||||
if (encryptedAssertion != null) {
|
||||
decryptAssertion(responseType, realm.getPrivateKey());
|
||||
|
||||
}
|
||||
return responseType.getAssertions().get(0).getAssertion();
|
||||
}
|
||||
|
||||
public Response handleSamlResponse(String samlResponse, String relayState) {
|
||||
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
||||
// validate destination
|
||||
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||
event.detail(Details.REASON, "invalid_destination");
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
||||
}
|
||||
if (config.isValidateSignature()) {
|
||||
try {
|
||||
verifySignature(holder);
|
||||
} catch (VerificationException e) {
|
||||
logger.error("validation failed", e);
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
|
||||
}
|
||||
}
|
||||
if (statusResponse instanceof ResponseType) {
|
||||
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
|
||||
|
||||
} else {
|
||||
// todo need to check that it is actually a LogoutResponse
|
||||
return handleLogoutResponse(holder, statusResponse, relayState);
|
||||
}
|
||||
//throw new RuntimeException("Unknown response type");
|
||||
|
||||
}
|
||||
|
||||
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
|
||||
if (relayState == null) {
|
||||
logger.error("no valid user session");
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
|
||||
if (userSession == null) {
|
||||
logger.error("no valid user session");
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||
logger.error("usersession in different state");
|
||||
event.event(EventType.LOGOUT);
|
||||
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
|
||||
}
|
||||
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||
}
|
||||
|
||||
|
||||
protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
|
||||
SAML2Response saml2Response = new SAML2Response();
|
||||
|
||||
try {
|
||||
Document doc = saml2Response.convert(responseType);
|
||||
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||
|
||||
if (enc == null) {
|
||||
throw new IdentityBrokerException("No encrypted assertion found.");
|
||||
}
|
||||
|
||||
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
|
||||
Document newDoc = DocumentUtil.createDocument();
|
||||
Node importedNode = newDoc.importNode(enc, true);
|
||||
newDoc.appendChild(importedNode);
|
||||
|
||||
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
|
||||
SAMLParser parser = new SAMLParser();
|
||||
|
||||
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
|
||||
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
|
||||
.getNodeAsStream(decryptedDocumentElement)));
|
||||
|
||||
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
||||
|
||||
return responseType;
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not decrypt assertion.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected class PostBinding extends Binding {
|
||||
@Override
|
||||
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
|
||||
}
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||
return SAMLRequestParser.parseResponsePostBinding(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBindingType() {
|
||||
return SamlProtocol.SAML_POST_BINDING;
|
||||
}
|
||||
}
|
||||
|
||||
protected class RedirectBinding extends Binding {
|
||||
@Override
|
||||
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||
PublicKey publicKey = getIDPKey();
|
||||
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||
return SAMLRequestParser.parseRequestRedirectBinding(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBindingType() {
|
||||
return SamlProtocol.SAML_REDIRECT_BINDING;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.saml;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
|
||||
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
|
||||
public SAMLIdentityProvider(SAMLIdentityProviderConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
|
||||
return new SAMLEndpoint(realm, getConfig(), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response handleRequest(AuthenticationRequest request) {
|
||||
try {
|
||||
UriInfo uriInfo = request.getUriInfo();
|
||||
RealmModel realm = request.getRealm();
|
||||
String issuerURL = getEntityId(uriInfo, realm);
|
||||
String destinationUrl = getConfig().getSingleSignOnServiceUrl();
|
||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||
|
||||
if (nameIDPolicyFormat == null) {
|
||||
nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
|
||||
}
|
||||
|
||||
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||
|
||||
String assertionConsumerServiceUrl = request.getRedirectUri();
|
||||
|
||||
if (getConfig().isPostBindingResponse()) {
|
||||
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||
}
|
||||
|
||||
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
|
||||
.assertionConsumerUrl(assertionConsumerServiceUrl)
|
||||
.destination(destinationUrl)
|
||||
.issuer(issuerURL)
|
||||
.forceAuthn(getConfig().isForceAuthn())
|
||||
.protocolBinding(protocolBinding)
|
||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
|
||||
.relayState(request.getState());
|
||||
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
PrivateKey privateKey = realm.getPrivateKey();
|
||||
PublicKey publicKey = realm.getPublicKey();
|
||||
|
||||
if (privateKey == null) {
|
||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a private key.");
|
||||
}
|
||||
|
||||
if (publicKey == null) {
|
||||
throw new IdentityBrokerException("Identity Provider [" + getConfig().getAlias() + "] wants a signed authentication request. But the Realm [" + realm.getName() + "] does not have a public key.");
|
||||
}
|
||||
|
||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
||||
|
||||
authnRequestBuilder.signWith(keypair);
|
||||
authnRequestBuilder.signDocument();
|
||||
}
|
||||
|
||||
if (getConfig().isPostBindingAuthnRequest()) {
|
||||
return authnRequestBuilder.postBinding().request();
|
||||
} else {
|
||||
return authnRequestBuilder.redirectBinding().request();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
||||
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response retrieveToken(FederatedIdentityModel identity) {
|
||||
return Response.ok(identity.getToken()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||
if (getConfig().getSingleLogoutServiceUrl() == null || getConfig().getSingleLogoutServiceUrl().trim().equals("")) return null;
|
||||
|
||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||
.issuer(getEntityId(uriInfo, realm))
|
||||
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
||||
.destination(getConfig().getSingleLogoutServiceUrl());
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||
.signDocument();
|
||||
}
|
||||
try {
|
||||
return logoutBuilder.relayState(userSession.getId()).postBinding().request();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
|
||||
|
||||
String authnBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||
|
||||
if (getConfig().isPostBindingAuthnRequest()) {
|
||||
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||
}
|
||||
|
||||
String endpoint = uriInfo.getBaseUriBuilder()
|
||||
.path("realms").path(realm.getName())
|
||||
.path("broker")
|
||||
.path(getConfig().getAlias())
|
||||
.path("endpoint")
|
||||
.build().toString();
|
||||
|
||||
|
||||
|
||||
String descriptor =
|
||||
"<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
|
||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
|
||||
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
||||
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
||||
" </NameIDFormat>\n" +
|
||||
" <SingleLogoutService Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"/>\n" +
|
||||
" <AssertionConsumerService\n" +
|
||||
" Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"\n" +
|
||||
" index=\"1\" isDefault=\"true\" />\n";
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
descriptor +=
|
||||
" <KeyDescriptor use=\"signing\">\n" +
|
||||
" <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
|
||||
" <dsig:X509Data>\n" +
|
||||
" <dsig:X509Certificate>\n" + realm.getCertificatePem() + "\n" +
|
||||
" </dsig:X509Certificate>\n" +
|
||||
" </dsig:X509Data>\n" +
|
||||
" </dsig:KeyInfo>\n" +
|
||||
" </KeyDescriptor>\n";
|
||||
}
|
||||
descriptor +=
|
||||
" </SPSSODescriptor>\n" +
|
||||
"</EntityDescriptor>\n";
|
||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.saml;
|
||||
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||
|
||||
public SAMLIdentityProviderConfig() {
|
||||
}
|
||||
|
||||
public SAMLIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
||||
super(identityProviderModel);
|
||||
}
|
||||
|
||||
public String getSingleSignOnServiceUrl() {
|
||||
return getConfig().get("singleSignOnServiceUrl");
|
||||
}
|
||||
|
||||
public void setSingleSignOnServiceUrl(String singleSignOnServiceUrl) {
|
||||
getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
|
||||
}
|
||||
|
||||
public String getSingleLogoutServiceUrl() {
|
||||
return getConfig().get("singleLogoutServiceUrl");
|
||||
}
|
||||
|
||||
public void setSingleLogoutServiceUrl(String singleLogoutServiceUrl) {
|
||||
getConfig().put("singleLogoutServiceUrl", singleLogoutServiceUrl);
|
||||
}
|
||||
|
||||
public boolean isValidateSignature() {
|
||||
return Boolean.valueOf(getConfig().get("validateSignature"));
|
||||
}
|
||||
|
||||
public void setValidateSignature(boolean validateSignature) {
|
||||
getConfig().put("validateSignature", String.valueOf(validateSignature));
|
||||
}
|
||||
|
||||
public boolean isForceAuthn() {
|
||||
return Boolean.valueOf(getConfig().get("forceAuthn"));
|
||||
}
|
||||
|
||||
public void setForceAuthn(boolean forceAuthn) {
|
||||
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
|
||||
}
|
||||
|
||||
public String getSigningCertificate() {
|
||||
return getConfig().get("signingCertificate");
|
||||
}
|
||||
|
||||
public void setSigningCertificate(String signingCertificate) {
|
||||
getConfig().put("signingCertificate", signingCertificate);
|
||||
}
|
||||
|
||||
public String getNameIDPolicyFormat() {
|
||||
return getConfig().get("nameIDPolicyFormat");
|
||||
}
|
||||
|
||||
public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
|
||||
getConfig().put("nameIDPolicyFormat", nameIDPolicyFormat);
|
||||
}
|
||||
|
||||
public boolean isWantAuthnRequestsSigned() {
|
||||
return Boolean.valueOf(getConfig().get("wantAuthnRequestsSigned"));
|
||||
}
|
||||
|
||||
public void setWantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) {
|
||||
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
||||
}
|
||||
|
||||
public String getEncryptionPublicKey() {
|
||||
return getConfig().get("encryptionPublicKey");
|
||||
}
|
||||
|
||||
public void setEncryptionPublicKey(String encryptionPublicKey) {
|
||||
getConfig().put("encryptionPublicKey", encryptionPublicKey);
|
||||
}
|
||||
|
||||
public boolean isPostBindingAuthnRequest() {
|
||||
return Boolean.valueOf(getConfig().get("postBindingAuthnRequest"));
|
||||
}
|
||||
|
||||
public void setPostBindingAuthnRequest(boolean postBindingAuthnRequest) {
|
||||
getConfig().put("postBindingAuthnRequest", String.valueOf(postBindingAuthnRequest));
|
||||
}
|
||||
|
||||
public boolean isPostBindingResponse() {
|
||||
return Boolean.valueOf(getConfig().get("postBindingResponse"));
|
||||
}
|
||||
|
||||
public void setPostBindingResponse(boolean postBindingResponse) {
|
||||
getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* JBoss, Home of Professional Open Source
|
||||
*
|
||||
* Copyright 2013 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* 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.broker.saml;
|
||||
|
||||
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||
import org.picketlink.common.exceptions.ParsingException;
|
||||
import org.picketlink.common.util.DocumentUtil;
|
||||
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.EndpointType;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.KeyDescriptorType;
|
||||
import org.picketlink.identity.federation.saml.v2.metadata.KeyTypes;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pedro Igor
|
||||
*/
|
||||
public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory<SAMLIdentityProvider> {
|
||||
|
||||
public static final String PROVIDER_ID = "saml";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SAML v2.0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLIdentityProvider create(IdentityProviderModel model) {
|
||||
return new SAMLIdentityProvider(new SAMLIdentityProviderConfig(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||
try {
|
||||
Object parsedObject = new SAMLParser().parse(inputStream);
|
||||
EntityDescriptorType entityType;
|
||||
|
||||
if (EntitiesDescriptorType.class.isInstance(parsedObject)) {
|
||||
entityType = (EntityDescriptorType) ((EntitiesDescriptorType) parsedObject).getEntityDescriptor().get(0);
|
||||
} else {
|
||||
entityType = (EntityDescriptorType) parsedObject;
|
||||
}
|
||||
|
||||
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
|
||||
|
||||
if (!choiceType.isEmpty()) {
|
||||
EntityDescriptorType.EDTChoiceType edtChoiceType = choiceType.get(0);
|
||||
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
|
||||
|
||||
if (!descriptors.isEmpty()) {
|
||||
EntityDescriptorType.EDTDescriptorChoiceType edtDescriptorChoiceType = descriptors.get(0);
|
||||
IDPSSODescriptorType idpDescriptor = edtDescriptorChoiceType.getIdpDescriptor();
|
||||
|
||||
if (idpDescriptor != null) {
|
||||
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
|
||||
String singleSignOnServiceUrl = null;
|
||||
boolean postBinding = false;
|
||||
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
|
||||
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||
postBinding = true;
|
||||
break;
|
||||
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||
}
|
||||
}
|
||||
String singleLogoutServiceUrl = null;
|
||||
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
||||
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||
break;
|
||||
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
||||
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
||||
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
||||
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
||||
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
||||
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
||||
|
||||
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
|
||||
String defaultCertificate = null;
|
||||
|
||||
if (keyDescriptor != null) {
|
||||
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
|
||||
Element keyInfo = keyDescriptorType.getKeyInfo();
|
||||
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
||||
|
||||
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
||||
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
||||
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
||||
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
||||
} else if (keyDescriptorType.getUse() == null) {
|
||||
defaultCertificate = x509KeyInfo.getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultCertificate != null) {
|
||||
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
||||
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
||||
}
|
||||
|
||||
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
||||
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
|
||||
}
|
||||
}
|
||||
|
||||
return samlIdentityProviderConfig.getConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ParsingException pe) {
|
||||
throw new RuntimeException("Could not parse IdP SAML Metadata", pe);
|
||||
}
|
||||
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.broker.saml.SAMLIdentityProviderFactory
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-file</artifactId>
|
||||
<name>Keycloak Connections File</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-export-import-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-export-import-single-file</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* Provides the InMemoryModel and notifies the factory to save it when
|
||||
* the session is done.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class DefaultFileConnectionProvider implements FileConnectionProvider {
|
||||
|
||||
private final DefaultFileConnectionProviderFactory factory;
|
||||
private final KeycloakSession session;
|
||||
private final InMemoryModel inMemoryModel;
|
||||
|
||||
private boolean isRollbackOnly = false;
|
||||
|
||||
public DefaultFileConnectionProvider(DefaultFileConnectionProviderFactory factory,
|
||||
KeycloakSession session,
|
||||
InMemoryModel inMemoryModel) {
|
||||
this.factory = factory;
|
||||
this.session = session;
|
||||
this.inMemoryModel = inMemoryModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InMemoryModel getModel() {
|
||||
return inMemoryModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClosed(KeycloakSession session) {
|
||||
factory.sessionClosed(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void begin() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
factory.commit(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
factory.rollback(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
isRollbackOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getRollbackOnly() {
|
||||
return isRollbackOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return factory.isActive(session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import org.keycloak.exportimport.util.ExportUtils;
|
||||
import org.keycloak.exportimport.util.ImportUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* This class dispenses a FileConnectionProvider to Keycloak sessions. It
|
||||
* makes sure that only one InMemoryModel is provided for each session and it
|
||||
* handles thread contention for the file where the model is read or saved.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class DefaultFileConnectionProviderFactory implements FileConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultFileConnectionProviderFactory.class);
|
||||
|
||||
private File kcdata;
|
||||
private final Map<KeycloakSession, FileConnectionProvider> allProviders = new HashMap<KeycloakSession, FileConnectionProvider>();
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
String fileName = config.get("fileName");
|
||||
if (fileName == null) {
|
||||
fileName = "keycloak-model.json";
|
||||
}
|
||||
|
||||
String directory = config.get("directory");
|
||||
if (directory == null) {
|
||||
directory = System.getProperty("jboss.server.data.dir");
|
||||
}
|
||||
if (directory == null) {
|
||||
directory = ".";
|
||||
}
|
||||
|
||||
kcdata = new File(directory, fileName);
|
||||
}
|
||||
|
||||
public void sessionClosed(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
allProviders.remove(session);
|
||||
//logger.info("Removed session " + session.hashCode());
|
||||
//logger.info("sessionClosed: Session count=" + allModels.size());
|
||||
}
|
||||
}
|
||||
|
||||
void readModelFile(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
if (!kcdata.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(kcdata);
|
||||
ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING);
|
||||
} catch (IOException ioe) {
|
||||
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
|
||||
} finally {
|
||||
//logger.info("Read model file for session=" + session.hashCode());
|
||||
try {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to close output stream.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeModelFile(KeycloakSession session) {
|
||||
synchronized(allProviders) {
|
||||
FileOutputStream outStream = null;
|
||||
|
||||
try {
|
||||
outStream = new FileOutputStream(kcdata);
|
||||
exportModel(session, outStream);
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e);
|
||||
} finally {
|
||||
//logger.info("Wrote model file for session=" + session.hashCode());
|
||||
try {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to close output stream.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException {
|
||||
List<RealmModel> realms = session.realms().getRealms();
|
||||
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
|
||||
for (RealmModel realm : realms) {
|
||||
reps.add(ExportUtils.exportRealm(session, realm, true));
|
||||
}
|
||||
|
||||
JsonSerialization.prettyMapper.writeValue(outStream, reps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileConnectionProvider create(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
FileConnectionProvider fcProvider = allProviders.get(session);
|
||||
if (fcProvider == null) {
|
||||
InMemoryModel model = new InMemoryModel();
|
||||
fcProvider = new DefaultFileConnectionProvider(this, session, model);
|
||||
allProviders.put(session, fcProvider);
|
||||
session.getTransaction().enlist(fcProvider);
|
||||
readModelFile(session);
|
||||
//logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size());
|
||||
}
|
||||
|
||||
return fcProvider;
|
||||
}
|
||||
}
|
||||
|
||||
// commitCount is used for debugging. This allows you to easily run a test
|
||||
// to a particular point and then examine the JSON file.
|
||||
//private static int commitCount = 0;
|
||||
void commit(KeycloakSession session) {
|
||||
//commitCount++;
|
||||
synchronized (allProviders) {
|
||||
// in case commit was somehow called twice on the same session
|
||||
if (!allProviders.containsKey(session)) return;
|
||||
|
||||
try {
|
||||
writeModelFile(session);
|
||||
} finally {
|
||||
allProviders.remove(session);
|
||||
//logger.info("Removed session " + session.hashCode());
|
||||
//logger.info("*** commitCount=" + commitCount);
|
||||
//logger.info("commit(): Session count=" + allModels.size());
|
||||
}
|
||||
|
||||
// if (commitCount == 16) {Thread.dumpStack();System.exit(0);}
|
||||
}
|
||||
}
|
||||
|
||||
void rollback(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
allProviders.remove(session);
|
||||
//logger.info("rollback(): Session count=" + allModels.size());
|
||||
}
|
||||
}
|
||||
|
||||
boolean isActive(KeycloakSession session) {
|
||||
synchronized (allProviders) {
|
||||
return allProviders.containsKey(session);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakTransaction;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public interface FileConnectionProvider extends Provider, KeycloakTransaction {
|
||||
|
||||
InMemoryModel getModel();
|
||||
|
||||
void sessionClosed(KeycloakSession session);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public interface FileConnectionProviderFactory extends ProviderFactory<FileConnectionProvider> {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class FileConnectionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsFile";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return FileConnectionProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return FileConnectionProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @author tags. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.connections.file;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
/**
|
||||
* This class provides an in-memory copy of the entire model for each
|
||||
* Keycloak session. At the start of the session, the model is read
|
||||
* from JSON. When the session's transaction ends, the model is written back
|
||||
* out.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||
*/
|
||||
public class InMemoryModel {
|
||||
private final Map<String, RealmModel> allRealms = new HashMap<String, RealmModel>();
|
||||
|
||||
// realmId, userId, userModel
|
||||
private final Map<String, Map<String,UserModel>> allUsers = new HashMap<String, Map<String,UserModel>>();
|
||||
|
||||
public InMemoryModel() {
|
||||
}
|
||||
|
||||
public void putRealm(String id, RealmModel realm) {
|
||||
allRealms.put(id, realm);
|
||||
allUsers.put(id, new HashMap<String, UserModel>());
|
||||
}
|
||||
|
||||
public RealmModel getRealm(String id) {
|
||||
return allRealms.get(id);
|
||||
}
|
||||
|
||||
public Collection<RealmModel> getRealms() {
|
||||
return allRealms.values();
|
||||
}
|
||||
|
||||
public RealmModel getRealmByName(String name) {
|
||||
for (RealmModel realm : getRealms()) {
|
||||
if (realm.getName().equals(name)) return realm;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean removeRealm(String id) {
|
||||
allUsers.remove(id);
|
||||
return (allRealms.remove(id) != null);
|
||||
}
|
||||
|
||||
protected Map<String, UserModel> realmUsers(String realmId) {
|
||||
Map<String, UserModel> realmUsers = allUsers.get(realmId);
|
||||
if (realmUsers == null) throw new NullPointerException("Realm users not found for id=" + realmId);
|
||||
return realmUsers;
|
||||
}
|
||||
|
||||
public void putUser(String realmId, String userId, UserModel user) {
|
||||
realmUsers(realmId).put(userId, user);
|
||||
}
|
||||
|
||||
public UserModel getUser(String realmId, String userId) {
|
||||
return realmUsers(realmId).get(userId);
|
||||
}
|
||||
|
||||
public boolean hasUserWithUsername(String realmId, String username) {
|
||||
for (UserModel user : getUsers(realmId)) {
|
||||
if (user.getUsername().equals(username)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Collection<UserModel> getUsers(String realmId) {
|
||||
return realmUsers(realmId).values();
|
||||
}
|
||||
|
||||
public boolean removeUser(String realmId, String userId) {
|
||||
return (realmUsers(realmId).remove(userId) != null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.file.FileConnectionSpi
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
<name>Keycloak Connections Infinispan</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Cache<K, V> getCache(String name) {
|
||||
return cacheManager.getCache(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.configuration.cache.CacheMode;
|
||||
import org.infinispan.configuration.cache.Configuration;
|
||||
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
|
||||
import org.infinispan.manager.DefaultCacheManager;
|
||||
import org.infinispan.manager.EmbeddedCacheManager;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
private EmbeddedCacheManager cacheManager;
|
||||
|
||||
private boolean containerManaged;
|
||||
|
||||
@Override
|
||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
|
||||
return new DefaultInfinispanConnectionProvider(cacheManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (cacheManager != null && !containerManaged) {
|
||||
cacheManager.stop();
|
||||
}
|
||||
cacheManager = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
if (cacheManager == null) {
|
||||
synchronized (this) {
|
||||
if (cacheManager == null) {
|
||||
String cacheContainer = config.get("cacheContainer");
|
||||
if (cacheContainer != null) {
|
||||
initContainerManaged(cacheContainer);
|
||||
} else {
|
||||
initEmbedded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initContainerManaged(String cacheContainerLookup) {
|
||||
try {
|
||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||
containerManaged = true;
|
||||
|
||||
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void initEmbedded() {
|
||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||
|
||||
boolean clustered = config.getBoolean("clustered", false);
|
||||
boolean async = config.getBoolean("async", true);
|
||||
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
|
||||
|
||||
if (clustered) {
|
||||
gcb.transport().defaultTransport();
|
||||
}
|
||||
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||
|
||||
cacheManager = new DefaultCacheManager(gcb.build());
|
||||
containerManaged = false;
|
||||
|
||||
logger.debug("Started embedded Infinispan cache container");
|
||||
|
||||
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||
}
|
||||
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, invalidationCacheConfiguration);
|
||||
|
||||
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
|
||||
if (clustered) {
|
||||
String sessionsMode = config.get("sessionsMode", "distributed");
|
||||
if (sessionsMode.equalsIgnoreCase("replicated")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
|
||||
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for sessionsMode");
|
||||
}
|
||||
|
||||
sessionConfigBuilder.clustering().hash()
|
||||
.numOwners(config.getInt("sessionsOwners", 2))
|
||||
.numSegments(config.getInt("sessionsSegments", 60)).build();
|
||||
}
|
||||
|
||||
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProvider extends Provider {
|
||||
|
||||
static final String REALM_CACHE_NAME = "realms";
|
||||
static final String USER_CACHE_NAME = "users";
|
||||
static final String SESSION_CACHE_NAME = "sessions";
|
||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||
|
||||
<K, V> Cache<K, V> getCache(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface InfinispanConnectionProviderFactory extends ProviderFactory<InfinispanConnectionProvider> {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.keycloak.connections.infinispan;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class InfinispanConnectionSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsInfinispan";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return InfinispanConnectionProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return InfinispanConnectionProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
||||
@@ -0,0 +1 @@
|
||||
org.keycloak.connections.infinispan.InfinispanConnectionSpi
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-jpa-liquibase</artifactId>
|
||||
<name>Keycloak Connections JPA Liquibase Updater</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
|
||||
|
||||
<url>${url}</url>
|
||||
<driver>${driver}</driver>
|
||||
<username>${username}</username>
|
||||
<password>${password}</password>
|
||||
|
||||
<referenceUrl>${referenceUrl}</referenceUrl>
|
||||
<referenceDriver>${driver}</referenceDriver>
|
||||
<referenceUsername>${username}</referenceUsername>
|
||||
<referencePassword>${password}</referencePassword>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<username>sa</username>
|
||||
<password></password>
|
||||
<driver>org.h2.Driver</driver>
|
||||
</properties>
|
||||
</project>
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.DatabaseChangeLog;
|
||||
import liquibase.changelog.RanChangeSet;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.DatabaseFactory;
|
||||
import liquibase.database.core.DB2Database;
|
||||
import liquibase.database.core.DerbyDatabase;
|
||||
import liquibase.database.core.FirebirdDatabase;
|
||||
import liquibase.database.core.H2Database;
|
||||
import liquibase.database.core.HsqlDatabase;
|
||||
import liquibase.database.core.InformixDatabase;
|
||||
import liquibase.database.core.MSSQLDatabase;
|
||||
import liquibase.database.core.MySQLDatabase;
|
||||
import liquibase.database.core.OracleDatabase;
|
||||
import liquibase.database.core.PostgresDatabase;
|
||||
import liquibase.database.core.SQLiteDatabase;
|
||||
import liquibase.database.core.SybaseASADatabase;
|
||||
import liquibase.database.core.SybaseDatabase;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.logging.LogFactory;
|
||||
import liquibase.logging.LogLevel;
|
||||
import liquibase.resource.ClassLoaderResourceAccessor;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LiquibaseJpaUpdaterProvider.class);
|
||||
|
||||
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
||||
|
||||
@Override
|
||||
public String getCurrentVersionSql() {
|
||||
return "SELECT ID from DATABASECHANGELOG ORDER BY DATEEXECUTED DESC LIMIT 1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KeycloakSession session, Connection connection) {
|
||||
logger.debug("Starting database update");
|
||||
|
||||
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
|
||||
ThreadLocalSessionContext.setCurrentSession(session);
|
||||
|
||||
try {
|
||||
Liquibase liquibase = getLiquibase(connection);
|
||||
|
||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||
if (!changeSets.isEmpty()) {
|
||||
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
||||
Statement statement = connection.createStatement();
|
||||
try {
|
||||
statement.executeQuery("SELECT id FROM REALM");
|
||||
|
||||
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
|
||||
liquibase.markNextChangeSetRan(null);
|
||||
} catch (SQLException e) {
|
||||
logger.info("Initializing database schema");
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||
logger.debugv("Updating database from {0} to {1}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
||||
} else {
|
||||
logger.infov("Updating database");
|
||||
}
|
||||
}
|
||||
|
||||
liquibase.update((Contexts) null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to update database", e);
|
||||
} finally {
|
||||
ThreadLocalSessionContext.removeCurrentSession();
|
||||
}
|
||||
|
||||
logger.debug("Completed database update");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Connection connection) {
|
||||
try {
|
||||
Liquibase liquibase = getLiquibase(connection);
|
||||
|
||||
liquibase.validate();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to validate database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Liquibase getLiquibase(Connection connection) throws Exception {
|
||||
ServiceLocator sl = ServiceLocator.getInstance();
|
||||
|
||||
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
||||
if (sl.getPackages().remove("liquibase.core")) {
|
||||
sl.addPackageToScan("liquibase.core.xml");
|
||||
}
|
||||
|
||||
if (sl.getPackages().remove("liquibase.parser")) {
|
||||
sl.addPackageToScan("liquibase.parser.core.xml");
|
||||
}
|
||||
|
||||
if (sl.getPackages().remove("liquibase.serializer")) {
|
||||
sl.addPackageToScan("liquibase.serializer.core.xml");
|
||||
}
|
||||
|
||||
sl.getPackages().remove("liquibase.ext");
|
||||
sl.getPackages().remove("liquibase.sdk");
|
||||
}
|
||||
|
||||
LogFactory.setInstance(new LogWrapper());
|
||||
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private static class LogWrapper extends LogFactory {
|
||||
|
||||
private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(LogLevel level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String logLevel, String logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogLevel getLogLevel() {
|
||||
if (LiquibaseJpaUpdaterProvider.logger.isTraceEnabled()) {
|
||||
return LogLevel.DEBUG;
|
||||
} else if (LiquibaseJpaUpdaterProvider.logger.isDebugEnabled()) {
|
||||
return LogLevel.INFO;
|
||||
} else {
|
||||
return LogLevel.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeSet(ChangeSet changeSet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog(String name) {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public JpaUpdaterProvider create(KeycloakSession session) {
|
||||
return new LiquibaseJpaUpdaterProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "liquibase";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ThreadLocalSessionContext {
|
||||
|
||||
private static final ThreadLocal<KeycloakSession> currentSession = new ThreadLocal<KeycloakSession>();
|
||||
|
||||
public static KeycloakSession getCurrentSession() {
|
||||
return currentSession.get();
|
||||
}
|
||||
|
||||
public static void setCurrentSession(KeycloakSession session) {
|
||||
currentSession.set(session);
|
||||
}
|
||||
|
||||
public static void removeCurrentSession() {
|
||||
currentSession.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.keycloak.connections.jpa.updater.liquibase.custom;
|
||||
|
||||
import liquibase.change.custom.CustomSqlChange;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.CustomChangeException;
|
||||
import liquibase.exception.SetupException;
|
||||
import liquibase.exception.ValidationErrors;
|
||||
import liquibase.resource.ResourceAccessor;
|
||||
import liquibase.snapshot.SnapshotGeneratorFactory;
|
||||
import liquibase.statement.SqlStatement;
|
||||
import liquibase.statement.core.UpdateStatement;
|
||||
import liquibase.structure.core.Schema;
|
||||
import liquibase.structure.core.Table;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class AddRealmCodeSecret implements CustomSqlChange {
|
||||
|
||||
private String confirmationMessage;
|
||||
|
||||
@Override
|
||||
public SqlStatement[] generateStatements(Database database) throws CustomChangeException {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Generated codeSecret for realms: ");
|
||||
|
||||
Connection connection = ((JdbcConnection) (database.getConnection())).getWrappedConnection();
|
||||
ArrayList<SqlStatement> statements = new ArrayList<SqlStatement>();
|
||||
|
||||
String correctedTableName = database.correctObjectName("REALM", Table.class);
|
||||
if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM WHERE CODE_SECRET IS NULL");
|
||||
while (resultSet.next()) {
|
||||
String id = resultSet.getString(1);
|
||||
|
||||
UpdateStatement statement = new UpdateStatement(null, null, correctedTableName)
|
||||
.addNewColumnValue("CODE_SECRET", KeycloakModelUtils.generateCodeSecret())
|
||||
.setWhereClause("ID='" + id + "'");
|
||||
statements.add(statement);
|
||||
|
||||
if (!resultSet.isFirst()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(id);
|
||||
}
|
||||
|
||||
if (!statements.isEmpty()) {
|
||||
confirmationMessage = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return statements.toArray(new SqlStatement[statements.size()]);
|
||||
} catch (Exception e) {
|
||||
throw new CustomChangeException("Failed to add realm code secret", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfirmationMessage() {
|
||||
return confirmationMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() throws SetupException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileOpener(ResourceAccessor resourceAccessor) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationErrors validate(Database database) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||