mirror of
https://github.com/casdoor/casdoor.git
synced 2025-09-10 11:42:55 +08:00
Compare commits
1499 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf67be2af6 | ||
|
|
bc94735a8d | ||
|
|
89c6ef5aae | ||
|
|
21da9f5ff2 | ||
|
|
3b11e778e7 | ||
|
|
ad240a373f | ||
|
|
01000f7022 | ||
|
|
f93aeb5350 | ||
|
|
8fa681f883 | ||
|
|
3b16406442 | ||
|
|
fbc16ef124 | ||
|
|
f26f56e88b | ||
|
|
9cb633c9e2 | ||
|
|
d0d059d42f | ||
|
|
c184dc7f3a | ||
|
|
2fa0890c11 | ||
|
|
a0e2be7ba8 | ||
|
|
09b389b1f7 | ||
|
|
a23033758f | ||
|
|
f7bc822087 | ||
|
|
e533ff1ee1 | ||
|
|
9f187f690e | ||
|
|
fe5aa1f214 | ||
|
|
eda742a848 | ||
|
|
83df077a02 | ||
|
|
ad6080e763 | ||
|
|
c179324de4 | ||
|
|
645716e485 | ||
|
|
955e73ddd1 | ||
|
|
2493ae9cfe | ||
|
|
b5c80513fb | ||
|
|
0653353be1 | ||
|
|
d6778fb4e6 | ||
|
|
fee7773839 | ||
|
|
d47ac6b957 | ||
|
|
857824df19 | ||
|
|
1e98d1e11b | ||
|
|
48ba88de2d | ||
|
|
a3a142db39 | ||
|
|
3bb7cc6b81 | ||
|
|
1fb3249bfd | ||
|
|
ff8f61a84c | ||
|
|
a118879dc0 | ||
|
|
386b673446 | ||
|
|
6abd46fe81 | ||
|
|
49d734d249 | ||
|
|
f5b4cd7fab | ||
|
|
76f322861a | ||
|
|
124c28f1e1 | ||
|
|
e0d9cc7ed1 | ||
|
|
75c1ae4366 | ||
|
|
d537377b31 | ||
|
|
462ecce43b | ||
|
|
a84664b55d | ||
|
|
941c56e69e | ||
|
|
a28b871a46 | ||
|
|
387f5d58f7 | ||
|
|
7d846b2060 | ||
|
|
c1c2dcab38 | ||
|
|
f9264f700b | ||
|
|
f3af2a26aa | ||
|
|
0ac69bde53 | ||
|
|
70c99f0e59 | ||
|
|
8d1fdc3a08 | ||
|
|
30c15b8135 | ||
|
|
2d6de216b8 | ||
|
|
ac39722687 | ||
|
|
26a9ec8ee6 | ||
|
|
fea6317430 | ||
|
|
5f702ca418 | ||
|
|
0495d17a07 | ||
|
|
c6a2d59aa4 | ||
|
|
d867afdd70 | ||
|
|
a92430e8fd | ||
|
|
447cb70553 | ||
|
|
e05fbec739 | ||
|
|
65ab36f073 | ||
|
|
d027e07383 | ||
|
|
d3c718b577 | ||
|
|
ea68e6c2dc | ||
|
|
7aa0b2e63f | ||
|
|
a39b121280 | ||
|
|
feef4cc242 | ||
|
|
1b5ef53655 | ||
|
|
18d639cca2 | ||
|
|
3ac5aad648 | ||
|
|
2a53241128 | ||
|
|
835273576b | ||
|
|
7fdc264ff6 | ||
|
|
a120734bb1 | ||
|
|
edd0b30e08 | ||
|
|
2da597b26f | ||
|
|
ef14c84edc | ||
|
|
cb5c7667b5 | ||
|
|
920ed87f75 | ||
|
|
6598f0ccdf | ||
|
|
8e71e23d75 | ||
|
|
146a369f80 | ||
|
|
9bbe5afb7c | ||
|
|
b42391c6ce | ||
|
|
fb035a5353 | ||
|
|
b1f68a60a4 | ||
|
|
201d704a31 | ||
|
|
bf91ad6c97 | ||
|
|
3ccc0339c7 | ||
|
|
1f2b0a3587 | ||
|
|
0b3feb0d5f | ||
|
|
568c0e2c3d | ||
|
|
f4ad2b4034 | ||
|
|
c9f8727890 | ||
|
|
e2e3c1fbb8 | ||
|
|
73915ac0a0 | ||
|
|
bf9d55ff40 | ||
|
|
b36fb50239 | ||
|
|
4307baa759 | ||
|
|
3964bae1df | ||
|
|
d9b97d70be | ||
|
|
ca224fdd4c | ||
|
|
37daea2bbc | ||
|
|
af231bf946 | ||
|
|
6dc7b4d533 | ||
|
|
12cc0f429e | ||
|
|
8cc22dec91 | ||
|
|
0c08ae5365 | ||
|
|
c3485268d3 | ||
|
|
64a4956c42 | ||
|
|
855bdf47e8 | ||
|
|
de7e322fbb | ||
|
|
4cb0cd7c5a | ||
|
|
c6a50349cc | ||
|
|
8a098a4b6e | ||
|
|
09f98fd24a | ||
|
|
515d209063 | ||
|
|
4e17dae2c2 | ||
|
|
0ad4d82d9c | ||
|
|
731daf5204 | ||
|
|
b6b77da7cf | ||
|
|
8b4637aa3a | ||
|
|
87506b84e3 | ||
|
|
fed9332246 | ||
|
|
33afc52a0b | ||
|
|
9035ca365a | ||
|
|
b97ae72179 | ||
|
|
9190db1099 | ||
|
|
1173f75794 | ||
|
|
086859d1ce | ||
|
|
9afaf5d695 | ||
|
|
521f90a603 | ||
|
|
4260efcfd0 | ||
|
|
d772b0b7a8 | ||
|
|
702b390da1 | ||
|
|
b15b3b9335 | ||
|
|
f8f864c5b9 | ||
|
|
90e790f83c | ||
|
|
58413246f3 | ||
|
|
8f307dd907 | ||
|
|
fe42b5e0ba | ||
|
|
383bf44391 | ||
|
|
36f5de3203 | ||
|
|
eae69c41d7 | ||
|
|
91057f54f3 | ||
|
|
daa7b79915 | ||
|
|
d3a5539dae | ||
|
|
7d1c614452 | ||
|
|
e2eafa909b | ||
|
|
56bcef0592 | ||
|
|
0860cbf343 | ||
|
|
2f4180b1b6 | ||
|
|
e3d5619b25 | ||
|
|
019fd87b92 | ||
|
|
5c41c6c4a5 | ||
|
|
b7fafcc62b | ||
|
|
493ceddcd9 | ||
|
|
fc618b9bd5 | ||
|
|
a00900e405 | ||
|
|
77ef5828dd | ||
|
|
c11f013e04 | ||
|
|
b3bafe8402 | ||
|
|
f04a431d85 | ||
|
|
952538916d | ||
|
|
18bb445e71 | ||
|
|
cca88e2cb0 | ||
|
|
86c10fe0ab | ||
|
|
c1b3bf0f45 | ||
|
|
62bda61af5 | ||
|
|
b6f943e326 | ||
|
|
2cc5e82d91 | ||
|
|
e55cd94298 | ||
|
|
08f7a05e61 | ||
|
|
4bee21f4a3 | ||
|
|
5417a90223 | ||
|
|
131820e34e | ||
|
|
2fcbf7cf6c | ||
|
|
14ade8b7e4 | ||
|
|
a11fe59704 | ||
|
|
af55d0547f | ||
|
|
81102f8298 | ||
|
|
141372cb86 | ||
|
|
15a037ca74 | ||
|
|
73c680d56f | ||
|
|
aafc16e4f4 | ||
|
|
7be026dd1f | ||
|
|
3e7938e5f6 | ||
|
|
30789138e2 | ||
|
|
9610ce5b8c | ||
|
|
a39a311d2f | ||
|
|
08e41ab762 | ||
|
|
85ca318e2f | ||
|
|
9032865e60 | ||
|
|
5692522ee0 | ||
|
|
cb1882e589 | ||
|
|
41d9422687 | ||
|
|
3297db688b | ||
|
|
cc82d292f0 | ||
|
|
f2e3037bc5 | ||
|
|
d986a4a9e0 | ||
|
|
2df3878c15 | ||
|
|
24ab8880cc | ||
|
|
f26b4853c5 | ||
|
|
d78e8e9776 | ||
|
|
d61f9a1856 | ||
|
|
aa52af02b3 | ||
|
|
2a5722e45b | ||
|
|
26718bc4a1 | ||
|
|
f8d44e2dca | ||
|
|
26eea501be | ||
|
|
63b8e857bc | ||
|
|
81b336b37a | ||
|
|
9c39179849 | ||
|
|
37d93a5eea | ||
|
|
e926a07c58 | ||
|
|
9c46344e68 | ||
|
|
c0ec73dfd3 | ||
|
|
b1b6ebe692 | ||
|
|
a0931e4597 | ||
|
|
c181006661 | ||
|
|
2e83e49492 | ||
|
|
5661942175 | ||
|
|
7f9f7c6468 | ||
|
|
b7a818e2d3 | ||
|
|
1a8cfe4ee6 | ||
|
|
b3526de675 | ||
|
|
3b9e08b70d | ||
|
|
cfc6015aca | ||
|
|
1600a6799a | ||
|
|
ca60cc3a33 | ||
|
|
df295717f0 | ||
|
|
e3001671a2 | ||
|
|
bbe2162e27 | ||
|
|
92b5ce3722 | ||
|
|
bad21fb6bb | ||
|
|
5a78dcf06d | ||
|
|
558b168477 | ||
|
|
802b6812a9 | ||
|
|
a5a627f92e | ||
|
|
9701818a6e | ||
|
|
06986fbd41 | ||
|
|
3d12ac8dc2 | ||
|
|
f01839123f | ||
|
|
e1b3b0ac6a | ||
|
|
4b0a2fdbfc | ||
|
|
db551eb24a | ||
|
|
18b49bb731 | ||
|
|
17653888a3 | ||
|
|
ee16616df4 | ||
|
|
ea450005e0 | ||
|
|
4c5ad14f6b | ||
|
|
49dda2aea5 | ||
|
|
a74a004540 | ||
|
|
2b89f6b37b | ||
|
|
c699e35e6b | ||
|
|
e28d90d0aa | ||
|
|
4fc7600865 | ||
|
|
19f62a461b | ||
|
|
7ddc2778c0 | ||
|
|
b96fa2a995 | ||
|
|
fcfb73af6e | ||
|
|
43bebc03b9 | ||
|
|
c5f25cbc7d | ||
|
|
3feb6ce84d | ||
|
|
08d6b45fc5 | ||
|
|
56d0de64dc | ||
|
|
1813e8e8c7 | ||
|
|
e27c764a55 | ||
|
|
e5a2057382 | ||
|
|
8457ff7433 | ||
|
|
888a6f2feb | ||
|
|
b57b64fc36 | ||
|
|
0d239ba1cf | ||
|
|
8927e08217 | ||
|
|
0636069584 | ||
|
|
4d0f73c84e | ||
|
|
74a2478e10 | ||
|
|
acc6f3e887 | ||
|
|
185ab9750a | ||
|
|
48adc050d6 | ||
|
|
b0e318c9db | ||
|
|
f9a6efc00f | ||
|
|
bd4a6775dd | ||
|
|
e3a43d0062 | ||
|
|
0cf281cac0 | ||
|
|
7322f67ae0 | ||
|
|
b927c6d7b4 | ||
|
|
01212cd1f3 | ||
|
|
bf55f94d41 | ||
|
|
f14711d315 | ||
|
|
58e1c28f7c | ||
|
|
922b19c64b | ||
|
|
1d21c3fa90 | ||
|
|
6175fd6764 | ||
|
|
2ceb54f058 | ||
|
|
aaeaa7fefa | ||
|
|
d522247552 | ||
|
|
79dbdab6c9 | ||
|
|
fe40910e3b | ||
|
|
2d1736f13a | ||
|
|
12b4d1c7cd | ||
|
|
a45d2b87c1 | ||
|
|
8484465d09 | ||
|
|
dff65eee20 | ||
|
|
596016456c | ||
|
|
673261c258 | ||
|
|
3c5985a3c0 | ||
|
|
4f3d62520a | ||
|
|
96f8b3d937 | ||
|
|
7ab5a5ade1 | ||
|
|
5cbd0a96ca | ||
|
|
7ccd8c4d4f | ||
|
|
b0fa3fc484 | ||
|
|
af01c4226a | ||
|
|
7a3d85a29a | ||
|
|
fd5ccd8d41 | ||
|
|
a439c5195d | ||
|
|
ba2e997d54 | ||
|
|
0818de85d1 | ||
|
|
457c6098a4 | ||
|
|
60f979fbb5 | ||
|
|
ff53e44fa6 | ||
|
|
1832de47db | ||
|
|
535eb0c465 | ||
|
|
c190634cf3 | ||
|
|
f7559aa040 | ||
|
|
1e0b709c73 | ||
|
|
c0800b7fb3 | ||
|
|
6fcdad2100 | ||
|
|
69d26d5c21 | ||
|
|
94e6b5ecb8 | ||
|
|
95e8bdcd36 | ||
|
|
6f1f93725e | ||
|
|
7ae067e369 | ||
|
|
dde936e935 | ||
|
|
fb561a98c8 | ||
|
|
7cd8f030ee | ||
|
|
a3f8ded10c | ||
|
|
e3d135bc6e | ||
|
|
fc864b0de4 | ||
|
|
3211bcc777 | ||
|
|
9f4430ed04 | ||
|
|
05830b9ff6 | ||
|
|
347b25676f | ||
|
|
2417ff84e6 | ||
|
|
468631e654 | ||
|
|
e1dea9f697 | ||
|
|
c0f22bae43 | ||
|
|
c9635d9e2b | ||
|
|
3bd52172ea | ||
|
|
bf730050d5 | ||
|
|
5b733b7f15 | ||
|
|
034f28def9 | ||
|
|
c86ac8e6ad | ||
|
|
d647eed22a | ||
|
|
717c53f6e5 | ||
|
|
097adac871 | ||
|
|
74543b9533 | ||
|
|
110dc04179 | ||
|
|
6464bd10dc | ||
|
|
db878a890e | ||
|
|
12d6d8e6ce | ||
|
|
8ed6e4f934 | ||
|
|
ed9732caf9 | ||
|
|
0de4e7da38 | ||
|
|
a330fbc11f | ||
|
|
ed158d4981 | ||
|
|
8df965b98d | ||
|
|
2c3749820e | ||
|
|
0b17cb9746 | ||
|
|
e2ce9ad625 | ||
|
|
64491abc64 | ||
|
|
934a8947c8 | ||
|
|
943edfb48b | ||
|
|
0d02b5e768 | ||
|
|
ba8d0b5f46 | ||
|
|
973a1df6c2 | ||
|
|
05bfd3a3a3 | ||
|
|
69aa3c8a8b | ||
|
|
a1b010a406 | ||
|
|
89e92cbd47 | ||
|
|
d4c8193357 | ||
|
|
9b33800b4c | ||
|
|
ec98785172 | ||
|
|
45dd4cc344 | ||
|
|
1adb172d6b | ||
|
|
c08f2b1f3f | ||
|
|
62bb257c6d | ||
|
|
230a77e3e3 | ||
|
|
dce0a96dea | ||
|
|
65563fa0cd | ||
|
|
f2a94f671a | ||
|
|
1460a0498f | ||
|
|
adc63ea726 | ||
|
|
0b8be016c5 | ||
|
|
986dcbbda1 | ||
|
|
7d3920fb1f | ||
|
|
b794ef87ee | ||
|
|
a0d6f2125e | ||
|
|
85cbb7d074 | ||
|
|
fdc1be9452 | ||
|
|
2bd7dabd33 | ||
|
|
9b9a58e7ac | ||
|
|
38e389e8c8 | ||
|
|
ab5fcf848e | ||
|
|
b4e51b4631 | ||
|
|
45e25acc80 | ||
|
|
97dcf24a91 | ||
|
|
4c0fff66ff | ||
|
|
e7230700e0 | ||
|
|
f21aa9c0d2 | ||
|
|
4b2b875b2d | ||
|
|
df2a5681cc | ||
|
|
ac102480c7 | ||
|
|
feff47d2dc | ||
|
|
79b934d6c2 | ||
|
|
365449695b | ||
|
|
55a52093e8 | ||
|
|
e65fdeb1e0 | ||
|
|
a46c1cc775 | ||
|
|
5629343466 | ||
|
|
3718d2dc04 | ||
|
|
38b9ad1d9f | ||
|
|
5a92411006 | ||
|
|
52eaf6c822 | ||
|
|
cc84709151 | ||
|
|
22fca78be9 | ||
|
|
effd257040 | ||
|
|
a38747d90e | ||
|
|
da70682cd1 | ||
|
|
4a3bd84f84 | ||
|
|
7f2869cecb | ||
|
|
cef2ab213b | ||
|
|
cc979c310e | ||
|
|
13d73732ce | ||
|
|
5686fe5d22 | ||
|
|
d8cb82f67a | ||
|
|
cad2e1bcc3 | ||
|
|
52cc2e4fa7 | ||
|
|
8077a2ccba | ||
|
|
4cb8e4a514 | ||
|
|
2f48d45773 | ||
|
|
cff0c7a273 | ||
|
|
793a7d6cda | ||
|
|
4cc2120fed | ||
|
|
93b0f52f26 | ||
|
|
e228045e37 | ||
|
|
6b8c24e1f0 | ||
|
|
8a79bb64dd | ||
|
|
e5f9aab28f | ||
|
|
7d05b69aac | ||
|
|
868e66e866 | ||
|
|
40ad3c9234 | ||
|
|
e2cd0604c2 | ||
|
|
78c3065fbb | ||
|
|
af2a9f0374 | ||
|
|
bfcfb56336 | ||
|
|
c48306d117 | ||
|
|
6efec6b4b5 | ||
|
|
2daf26aa88 | ||
|
|
21c151bcf8 | ||
|
|
b6b0b7d318 | ||
|
|
0ecc1d599f | ||
|
|
3456fc6695 | ||
|
|
c302dc7b8e | ||
|
|
d24ddd4f1c | ||
|
|
572616d390 | ||
|
|
2187310dbc | ||
|
|
26345bb21b | ||
|
|
e0455df504 | ||
|
|
1dfbbf0e90 | ||
|
|
d43d58dee2 | ||
|
|
9eb4b12041 | ||
|
|
3a45a4ee77 | ||
|
|
43393f034b | ||
|
|
bafa80513b | ||
|
|
8d08140421 | ||
|
|
3d29e27d54 | ||
|
|
199f1d4d10 | ||
|
|
227e938db6 | ||
|
|
739cfd84ed | ||
|
|
8dbb041a34 | ||
|
|
af2d26daf2 | ||
|
|
90d502ab2b | ||
|
|
d51af3378e | ||
|
|
87e2b97813 | ||
|
|
d9e44c1f2d | ||
|
|
dfa4503f24 | ||
|
|
f7fb32893b | ||
|
|
66d0758b13 | ||
|
|
46ad0fe0be | ||
|
|
6b637e3b2e | ||
|
|
3354945119 | ||
|
|
19c4416f10 | ||
|
|
2077db9091 | ||
|
|
800f0ed249 | ||
|
|
6161040c67 | ||
|
|
1d785e61c6 | ||
|
|
0329d24867 | ||
|
|
fb6f3623ee | ||
|
|
eb448bd043 | ||
|
|
ea88839db9 | ||
|
|
cb95f6977a | ||
|
|
9067df92a7 | ||
|
|
bfa2ab63ad | ||
|
|
505054b0eb | ||
|
|
f95ce13b82 | ||
|
|
5315f16a48 | ||
|
|
d054f3e001 | ||
|
|
b158b840bd | ||
|
|
b16f1807b3 | ||
|
|
d0cce1bf7a | ||
|
|
9892cd20ab | ||
|
|
d1f31dd327 | ||
|
|
94743246a1 | ||
|
|
39ad1bc593 | ||
|
|
d97f833d2a | ||
|
|
948fa911e2 | ||
|
|
6073a0f63d | ||
|
|
91268bca70 | ||
|
|
23dbb0b926 | ||
|
|
97cc1f9e2b | ||
|
|
8c415be7c7 | ||
|
|
e87165cfc8 | ||
|
|
fc4fa2e8b6 | ||
|
|
44ae76503e | ||
|
|
ae1634a4d5 | ||
|
|
bdf9864f69 | ||
|
|
72839d6bf5 | ||
|
|
2c4b1093ed | ||
|
|
d1c55d5aa7 | ||
|
|
c8aa35c9c6 | ||
|
|
6037f37b87 | ||
|
|
1b478903d8 | ||
|
|
4f5ac7a10b | ||
|
|
e81ba62234 | ||
|
|
a19060c7cb | ||
|
|
96812f676b | ||
|
|
04f0458b5c | ||
|
|
fd0bcd9a17 | ||
|
|
01a5958307 | ||
|
|
be88b00278 | ||
|
|
1bd0245e7a | ||
|
|
cc84bd37cf | ||
|
|
8302fcf805 | ||
|
|
391a533ce1 | ||
|
|
57431a59ad | ||
|
|
88a4736520 | ||
|
|
2cb6ff69ae | ||
|
|
e1e5943a3e | ||
|
|
3875896c1e | ||
|
|
7e2f265420 | ||
|
|
53ef179e9b | ||
|
|
376ef0ed14 | ||
|
|
ca183be336 | ||
|
|
e5da57a005 | ||
|
|
e4e225db32 | ||
|
|
a1add992ee | ||
|
|
2aac265ed4 | ||
|
|
2dc755f529 | ||
|
|
0dd474d5fc | ||
|
|
6998451e97 | ||
|
|
9175e5b664 | ||
|
|
dbc6b0dc45 | ||
|
|
31b7000f6a | ||
|
|
d25eaa65cd | ||
|
|
f5bcd00652 | ||
|
|
0d5f49e40a | ||
|
|
3527e070a0 | ||
|
|
0108b58db4 | ||
|
|
976b5766a5 | ||
|
|
a92d20162a | ||
|
|
204b1c2b8c | ||
|
|
49fb269170 | ||
|
|
c532a5d54d | ||
|
|
89df80baca | ||
|
|
d988ac814c | ||
|
|
e4b25055d5 | ||
|
|
4123d47174 | ||
|
|
fbdd5a926d | ||
|
|
92b6fda0f6 | ||
|
|
6a7ac35e65 | ||
|
|
fc137b9f76 | ||
|
|
11dbd5ba9a | ||
|
|
19942a8bd4 | ||
|
|
f9ee8a68cb | ||
|
|
f241336ad7 | ||
|
|
8b64d113fb | ||
|
|
a8800c4d5c | ||
|
|
75fc9ab9f7 | ||
|
|
d06da76c3d | ||
|
|
bc399837cc | ||
|
|
265abfe102 | ||
|
|
12acb24dbc | ||
|
|
ba1ddc7e50 | ||
|
|
59e07a35aa | ||
|
|
cabe830f55 | ||
|
|
78af5daec3 | ||
|
|
6c76913f71 | ||
|
|
5a0d1bcb6e | ||
|
|
37232faa07 | ||
|
|
4d9c81ef96 | ||
|
|
b0d87f60ae | ||
|
|
a5499219d1 | ||
|
|
6a813a1f8c | ||
|
|
e4cf244cf8 | ||
|
|
f5a6415e57 | ||
|
|
13e871043c | ||
|
|
a8699d0b87 | ||
|
|
6621d693de | ||
|
|
dc3131c683 | ||
|
|
042a8d0ad6 | ||
|
|
44abfb3430 | ||
|
|
53b8424a1f | ||
|
|
23c2ba3a2b | ||
|
|
3a9ffedce4 | ||
|
|
03f005389f | ||
|
|
69a8346d05 | ||
|
|
546512a0ea | ||
|
|
c4a307b9ec | ||
|
|
d731c3c934 | ||
|
|
4a68dd65cd | ||
|
|
d59148890e | ||
|
|
7f52755e32 | ||
|
|
eaa6f50085 | ||
|
|
f35a5f9a47 | ||
|
|
7481b229a4 | ||
|
|
39e485ae82 | ||
|
|
764c64e67c | ||
|
|
e755a7331d | ||
|
|
6d9d595f86 | ||
|
|
d52058d2ae | ||
|
|
bcfbfc6947 | ||
|
|
75699c4a26 | ||
|
|
3e8bfb52a8 | ||
|
|
bbbd857a45 | ||
|
|
498900df76 | ||
|
|
7e3c1a6581 | ||
|
|
6e28043dba | ||
|
|
cb200687dc | ||
|
|
23bb0ee450 | ||
|
|
117259dfc5 | ||
|
|
e71d0476f0 | ||
|
|
b5d26767b2 | ||
|
|
5c4e22288e | ||
|
|
3ac4be64b8 | ||
|
|
97db54b6b9 | ||
|
|
3a19d4c7c8 | ||
|
|
a60be2b2ab | ||
|
|
06ef97a080 | ||
|
|
167c1b0f1b | ||
|
|
7d0eae230e | ||
|
|
901867e8bb | ||
|
|
b7be1943fa | ||
|
|
bbbda1982f | ||
|
|
e593f5be5b | ||
|
|
0918757e85 | ||
|
|
ce0d45a70b | ||
|
|
c4096788b2 | ||
|
|
523186f895 | ||
|
|
ef373ca736 | ||
|
|
721a681ff1 | ||
|
|
8b1c4b0c75 | ||
|
|
540f22f8bd | ||
|
|
79f81f1356 | ||
|
|
4e145f71b5 | ||
|
|
104f975a2f | ||
|
|
71bb400559 | ||
|
|
93c3c78d42 | ||
|
|
dd51bbbabf | ||
|
|
5318519bf8 | ||
|
|
d7c40459c0 | ||
|
|
de2932b5fb | ||
|
|
f4c873ffe6 | ||
|
|
97c7f2631a | ||
|
|
93f0425759 | ||
|
|
6a00657e42 | ||
|
|
88130bf020 | ||
|
|
5e99007fc9 | ||
|
|
66aca3124c | ||
|
|
61deb75c84 | ||
|
|
b8db07db4d | ||
|
|
a681c267b3 | ||
|
|
5fb6ea0ab4 | ||
|
|
0f6b7984d4 | ||
|
|
ba9d6e5d78 | ||
|
|
a4524e9996 | ||
|
|
b469928780 | ||
|
|
dc6fe13f75 | ||
|
|
8227762988 | ||
|
|
d92b072ed0 | ||
|
|
1161310f81 | ||
|
|
48ba5f91ed | ||
|
|
53df2c2704 | ||
|
|
78066da208 | ||
|
|
60096468fe | ||
|
|
39d6bc10f7 | ||
|
|
177f2f2f11 | ||
|
|
79b393afee | ||
|
|
5bb12a30d4 | ||
|
|
fdb68bf9c8 | ||
|
|
37748850c8 | ||
|
|
8968396ae5 | ||
|
|
f5395f15f9 | ||
|
|
73e44df867 | ||
|
|
0b575ccf84 | ||
|
|
9b7f465a47 | ||
|
|
b1fe28fb83 | ||
|
|
530d054adb | ||
|
|
a2b9f9baaf | ||
|
|
a2d20fcb63 | ||
|
|
b118a3bb76 | ||
|
|
280867d0cb | ||
|
|
30fa2f7d81 | ||
|
|
518288691d | ||
|
|
ffa54247cd | ||
|
|
0199ad9aaa | ||
|
|
b9d171718f | ||
|
|
e841d0ba8e | ||
|
|
e5a9594f90 | ||
|
|
c542929835 | ||
|
|
86dea71efd | ||
|
|
9e536850fd | ||
|
|
fddd4a12b8 | ||
|
|
2d6fae32be | ||
|
|
741cff99df | ||
|
|
cad9c28e92 | ||
|
|
524cf4dda5 | ||
|
|
077a1cb8b7 | ||
|
|
00efdf1d03 | ||
|
|
aa543f1abb | ||
|
|
1d1d3049bd | ||
|
|
4f497d44a5 | ||
|
|
369de36987 | ||
|
|
e3f28e8b4c | ||
|
|
3373174c65 | ||
|
|
2fb79e4092 | ||
|
|
5846e337c7 | ||
|
|
44f4de1440 | ||
|
|
27adeb4620 | ||
|
|
5c107db43b | ||
|
|
27187b3a54 | ||
|
|
14fcedcc5d | ||
|
|
e7c015f288 | ||
|
|
c4819602ec | ||
|
|
dea03cdd15 | ||
|
|
21f394847e | ||
|
|
9bef9691fb | ||
|
|
141f22a707 | ||
|
|
02329d342a | ||
|
|
b9d3e2184c | ||
|
|
28caf8550e | ||
|
|
79159dc809 | ||
|
|
63081641d6 | ||
|
|
698f24f762 | ||
|
|
5499e62d7f | ||
|
|
f8905ae64c | ||
|
|
a42594859f | ||
|
|
46e0bc1a39 | ||
|
|
ffe2330238 | ||
|
|
ec53616dc8 | ||
|
|
067276d739 | ||
|
|
468ceb6b71 | ||
|
|
b31a317585 | ||
|
|
396b6fb65f | ||
|
|
be637fca81 | ||
|
|
374928e719 | ||
|
|
5c103e8cd3 | ||
|
|
85b86e8831 | ||
|
|
08864686f3 | ||
|
|
dc06eb9948 | ||
|
|
b068202e74 | ||
|
|
cb16567c7b | ||
|
|
4eb725d47a | ||
|
|
ce72a172b0 | ||
|
|
5521962e0c | ||
|
|
37b8b09cc0 | ||
|
|
482eb61168 | ||
|
|
8819a8697b | ||
|
|
85cb68eb66 | ||
|
|
b25b5f0249 | ||
|
|
947dcf6e75 | ||
|
|
113c27db73 | ||
|
|
badfe34755 | ||
|
|
a5f9f61381 | ||
|
|
2ce8c93ead | ||
|
|
da41ac7275 | ||
|
|
fd0c70a827 | ||
|
|
c4a6f07672 | ||
|
|
a67f541171 | ||
|
|
192968bac8 | ||
|
|
23d4488b64 | ||
|
|
23f4684e1d | ||
|
|
1a91e7b0f9 | ||
|
|
811999b6cc | ||
|
|
7786018051 | ||
|
|
6c72f86d03 | ||
|
|
5b151f4ec4 | ||
|
|
e9b7d1266f | ||
|
|
2d4998228c | ||
|
|
d3ed6c348b | ||
|
|
a22e05dcc1 | ||
|
|
0ac2b69f5a | ||
|
|
d090e9c860 | ||
|
|
8ebb158765 | ||
|
|
ea2f053630 | ||
|
|
988b14c6b5 | ||
|
|
a9e72ac3cb | ||
|
|
498cd02d49 | ||
|
|
a389842f59 | ||
|
|
6c69daa666 | ||
|
|
53c89bbe89 | ||
|
|
9442aa9f7a | ||
|
|
8a195715d0 | ||
|
|
b985bab3f3 | ||
|
|
477a090aa0 | ||
|
|
e082cf10e0 | ||
|
|
3215b88eae | ||
|
|
9703f3f712 | ||
|
|
140737b2f6 | ||
|
|
b285144a64 | ||
|
|
49c6ce2221 | ||
|
|
2398e69012 | ||
|
|
ade9de8256 | ||
|
|
1bf5497d08 | ||
|
|
cf10738f45 | ||
|
|
ac00713c20 | ||
|
|
febb27f765 | ||
|
|
49a981f787 | ||
|
|
34b1945180 | ||
|
|
b320cca789 | ||
|
|
b38654a45a | ||
|
|
f77fafae24 | ||
|
|
8b6b5ffe81 | ||
|
|
a147fa3e0b | ||
|
|
9d03665523 | ||
|
|
0106c7f7fa | ||
|
|
6713dad0af | ||
|
|
6ef2b51782 | ||
|
|
1732cd8538 | ||
|
|
a10548fe73 | ||
|
|
f6a7888f83 | ||
|
|
93efaa5459 | ||
|
|
0bfe683108 | ||
|
|
8a4758c22d | ||
|
|
ee3b46e91c | ||
|
|
37744d6cd7 | ||
|
|
98defe617b | ||
|
|
96cbf51ca0 | ||
|
|
22b57fdd23 | ||
|
|
b68e291f37 | ||
|
|
9960b4933b | ||
|
|
432a5496f2 | ||
|
|
45db4deb6b | ||
|
|
3f53591751 | ||
|
|
d7569684f6 | ||
|
|
a616127909 | ||
|
|
f2e2b960ff | ||
|
|
fbc603876f | ||
|
|
9ea77c63d1 | ||
|
|
53243a30f3 | ||
|
|
cbdeb91ee8 | ||
|
|
2dd1dc582f | ||
|
|
f3d4b45a0f | ||
|
|
2ee4aebd96 | ||
|
|
150e3e30d5 | ||
|
|
1055d7781b | ||
|
|
1c296e9b6f | ||
|
|
3d80ec721f | ||
|
|
43d849086f | ||
|
|
69b144d80f | ||
|
|
52a66ef044 | ||
|
|
ec0a8e16f7 | ||
|
|
80a8000057 | ||
|
|
77091a3ae5 | ||
|
|
983da685a2 | ||
|
|
3d567c3d45 | ||
|
|
440d87d70c | ||
|
|
e4208d7fd9 | ||
|
|
4de716fef3 | ||
|
|
070aa8a65f | ||
|
|
684cbdb951 | ||
|
|
9aec69ef47 | ||
|
|
98411ef67b | ||
|
|
71279f548d | ||
|
|
0096e47351 | ||
|
|
814d3f749b | ||
|
|
ec0f457c7f | ||
|
|
0033ae1ff1 | ||
|
|
d06d7c5c09 | ||
|
|
23c4fd8183 | ||
|
|
e3558894c3 | ||
|
|
2fd2d88d20 | ||
|
|
d0c424db0a | ||
|
|
6a9d1e0fe5 | ||
|
|
938e8e2699 | ||
|
|
620383cf33 | ||
|
|
de6cd380eb | ||
|
|
7e0bce2d0f | ||
|
|
1461268a51 | ||
|
|
5ec49dc883 | ||
|
|
5c89705d9e | ||
|
|
06e3b8481f | ||
|
|
81a8b91e3f | ||
|
|
56787fab90 | ||
|
|
1319216625 | ||
|
|
6fe5c44c1c | ||
|
|
981908b0b6 | ||
|
|
03a281cb5d | ||
|
|
a8e541159b | ||
|
|
577bf91d25 | ||
|
|
329a6a8132 | ||
|
|
fba0866cd6 | ||
|
|
aab6a799fe | ||
|
|
b94d06fb07 | ||
|
|
f9cc6ed064 | ||
|
|
4cc9137637 | ||
|
|
d145ab780c | ||
|
|
687830697e | ||
|
|
111d1a5786 | ||
|
|
775dd9eb57 | ||
|
|
8f6c295c40 | ||
|
|
2f31e35315 | ||
|
|
b6d6aa9d04 | ||
|
|
f40d44fa1c | ||
|
|
3b2820cbe3 | ||
|
|
764e88f603 | ||
|
|
7f298efebc | ||
|
|
0fc48bb6cd | ||
|
|
c3b3840994 | ||
|
|
eacc3fae5a | ||
|
|
ce7a2e924b | ||
|
|
ece060d03d | ||
|
|
1276da4daa | ||
|
|
616629ef99 | ||
|
|
b633ecdcf2 | ||
|
|
a12ba7fb85 | ||
|
|
08a0092974 | ||
|
|
bb04b10e8b | ||
|
|
ea1414dfd0 | ||
|
|
32a8a028d5 | ||
|
|
0fe34c2f53 | ||
|
|
dc57c476b7 | ||
|
|
a7cb202ee9 | ||
|
|
e5e264628e | ||
|
|
8d4127f744 | ||
|
|
1305899060 | ||
|
|
411a85c7ab | ||
|
|
f39358e122 | ||
|
|
a84752bbb5 | ||
|
|
e9d8ab8cdb | ||
|
|
d12088e8e7 | ||
|
|
c62588f9bc | ||
|
|
16cd09d175 | ||
|
|
7318ee6e3a | ||
|
|
3459ef1479 | ||
|
|
ca6b27f922 | ||
|
|
e528e8883b | ||
|
|
b7cd604e56 | ||
|
|
3c2fd574a6 | ||
|
|
a9de7d3aef | ||
|
|
9820801634 | ||
|
|
c6e422c3a8 | ||
|
|
bc8e9cfd64 | ||
|
|
c1eae9fcd8 | ||
|
|
6dae6e4954 | ||
|
|
559a91e8ee | ||
|
|
b0aaf09ef1 | ||
|
|
7e2f67c49a | ||
|
|
e584a6a111 | ||
|
|
6700d2e244 | ||
|
|
0c5c308071 | ||
|
|
0b859197da | ||
|
|
3078409343 | ||
|
|
bbf2db2e00 | ||
|
|
0c7b911ce7 | ||
|
|
2cc55715ac | ||
|
|
c829bf1769 | ||
|
|
ec956c12ca | ||
|
|
d3d4646c56 | ||
|
|
669ac7c618 | ||
|
|
6715efd781 | ||
|
|
953be4a7b6 | ||
|
|
943cc43427 | ||
|
|
1e5ce7a045 | ||
|
|
7a85b74573 | ||
|
|
7e349c1768 | ||
|
|
b19be2df88 | ||
|
|
fc3866db1c | ||
|
|
bf2bb31e41 | ||
|
|
ec8bd6f01d | ||
|
|
98722fd681 | ||
|
|
221c55aa93 | ||
|
|
988b26b3c2 | ||
|
|
7e3c361ce7 | ||
|
|
a637707e77 | ||
|
|
7970edeaa7 | ||
|
|
9da2f0775f | ||
|
|
739a9bcd0d | ||
|
|
fb0949b9ed | ||
|
|
27ed901167 | ||
|
|
ceab662b88 | ||
|
|
05b2f00057 | ||
|
|
8073dfa88c | ||
|
|
1eeeb64a0c | ||
|
|
f5e0461cae | ||
|
|
a0c5eb241f | ||
|
|
4d8edcc446 | ||
|
|
2b23c04f49 | ||
|
|
e60ee52d91 | ||
|
|
c54b54ca19 | ||
|
|
f0e097e138 | ||
|
|
25ec1bdfa8 | ||
|
|
ea7718d7b7 | ||
|
|
463fa8b636 | ||
|
|
11895902f4 | ||
|
|
15269d3315 | ||
|
|
4468859795 | ||
|
|
914128a78a | ||
|
|
e5a189e0f4 | ||
|
|
a07216d0e1 | ||
|
|
fec54944dd | ||
|
|
a2db61cc1a | ||
|
|
134541acde | ||
|
|
59fca0342e | ||
|
|
abfc464155 | ||
|
|
a41f6880a2 | ||
|
|
d12117324c | ||
|
|
1a6c9fbf69 | ||
|
|
dd60d79af9 | ||
|
|
73d314c7fe | ||
|
|
27959e0f6f | ||
|
|
47f40c5b24 | ||
|
|
2ff9020884 | ||
|
|
abaf4ca8d9 | ||
|
|
8ff0cfd6ec | ||
|
|
7a2a40edcc | ||
|
|
b7a001ea39 | ||
|
|
891e8e21d8 | ||
|
|
80b0d26813 | ||
|
|
db4ac60bb6 | ||
|
|
33a922f026 | ||
|
|
9f65053d04 | ||
|
|
be969e5efa | ||
|
|
9156bd426b | ||
|
|
fe4a4328aa | ||
|
|
9899022bcd | ||
|
|
1a9d02be46 | ||
|
|
eafaa135b4 | ||
|
|
6746551447 | ||
|
|
3cb46c3628 | ||
|
|
558bcf95d6 | ||
|
|
bb937c30c1 | ||
|
|
8dfdf7f767 | ||
|
|
62b2082e82 | ||
|
|
a1806439f8 | ||
|
|
01e58158b7 | ||
|
|
15427ad9d6 | ||
|
|
d058f78dc6 | ||
|
|
fd9dbf8251 | ||
|
|
3220a04fa9 | ||
|
|
f06a4990bd | ||
|
|
9df7de5f27 | ||
|
|
56c808c091 | ||
|
|
9fd2421564 | ||
|
|
689d45c7fa | ||
|
|
c24343bd53 | ||
|
|
979f43638d | ||
|
|
685a4514cd | ||
|
|
a05ca3af24 | ||
|
|
c6f301ff9e | ||
|
|
d7b2bcf288 | ||
|
|
67ac3d6d21 | ||
|
|
912d5c6a7f | ||
|
|
32fbb5b534 | ||
|
|
21004f3009 | ||
|
|
463bacd53b | ||
|
|
78dc660041 | ||
|
|
2fb9674171 | ||
|
|
55c522d3b7 | ||
|
|
f879170663 | ||
|
|
12e5d9b583 | ||
|
|
eefa1e6df4 | ||
|
|
026fb207b3 | ||
|
|
ea10f8e615 | ||
|
|
74b058aa3f | ||
|
|
6c628d7893 | ||
|
|
a38896e4d8 | ||
|
|
5f054c4989 | ||
|
|
fb16d8cee6 | ||
|
|
5e4ba4f338 | ||
|
|
ca47af2ee1 | ||
|
|
59da104463 | ||
|
|
c5bb916651 | ||
|
|
e98264f957 | ||
|
|
6a952952a8 | ||
|
|
ba8a0f36be | ||
|
|
b5e9084e5d | ||
|
|
55d5ae10f2 | ||
|
|
6986dad295 | ||
|
|
949feb18af | ||
|
|
d1f88ca9b8 | ||
|
|
bfe8e5f3e7 | ||
|
|
702ee6acd0 | ||
|
|
0a9587901a | ||
|
|
577bd6ce58 | ||
|
|
3c4112dd44 | ||
|
|
b7a37126ad | ||
|
|
8669d5bb0d | ||
|
|
aee3ea4981 | ||
|
|
516f4b7569 | ||
|
|
7d7ca10481 | ||
|
|
a9d4978a0f | ||
|
|
09f40bb5ce | ||
|
|
a6f803aff1 | ||
|
|
fc9528be43 | ||
|
|
58e8f9f90b | ||
|
|
e850e33f37 | ||
|
|
d7110ff8bf | ||
|
|
f923a8f0d7 | ||
|
|
7bfb74ba18 | ||
|
|
38f031bc86 | ||
|
|
5c441d195c | ||
|
|
0639564d27 | ||
|
|
6c647818ca | ||
|
|
8bc73d17aa | ||
|
|
1f37c80177 | ||
|
|
7924fca403 | ||
|
|
bd06996bab | ||
|
|
19ab168b12 | ||
|
|
854a74b73e | ||
|
|
beefb0b432 | ||
|
|
d8969e6652 | ||
|
|
666ff48837 | ||
|
|
0a0c1b4788 | ||
|
|
438c999e11 | ||
|
|
a193ceb33d | ||
|
|
caec1d1bac | ||
|
|
0d48da24dc | ||
|
|
de9eeaa1ef | ||
|
|
ae6e35ee73 | ||
|
|
a58df645bf | ||
|
|
68417a2d7a | ||
|
|
9511fae9d9 | ||
|
|
347d3d2b53 | ||
|
|
6edfc08b28 | ||
|
|
bc1c4d32f0 | ||
|
|
96250aa70a | ||
|
|
3d4ca1adb1 | ||
|
|
ba97458edd | ||
|
|
855259c6e7 | ||
|
|
28297e06f7 | ||
|
|
f3aed0b6a8 | ||
|
|
35e1f8538e | ||
|
|
30a14ff54a | ||
|
|
1ab7a54133 | ||
|
|
0e2dad35f3 | ||
|
|
d31077a510 | ||
|
|
eee9b8b9fe | ||
|
|
91cb5f393a | ||
|
|
807aea5ec7 | ||
|
|
1c42b6e395 | ||
|
|
49a73f8138 | ||
|
|
55784c68a3 | ||
|
|
8080b10b3b | ||
|
|
cd7589775c | ||
|
|
0a8c2a35fe | ||
|
|
d1e734e4ce | ||
|
|
68f032b54d | ||
|
|
1780620ef4 | ||
|
|
5c968ed1ce | ||
|
|
4016fc0f65 | ||
|
|
463b3ad976 | ||
|
|
b817a55f9f | ||
|
|
2c2ddfbb92 | ||
|
|
cadb533595 | ||
|
|
a3b0f1fc74 | ||
|
|
c391af4552 | ||
|
|
6ebca6dbe7 | ||
|
|
d505a4bf2d | ||
|
|
812bc5f6b2 | ||
|
|
f6f4d44444 | ||
|
|
926e73ed1b | ||
|
|
65716af89e | ||
|
|
d9c4f401e3 | ||
|
|
58aa7dba6a | ||
|
|
29fc820578 | ||
|
|
d0ac265c91 | ||
|
|
3562c36817 | ||
|
|
7884e10ca3 | ||
|
|
12dee8afd3 | ||
|
|
ac4b870309 | ||
|
|
b9140e2d5a | ||
|
|
501f0dc74f | ||
|
|
a932b76fba | ||
|
|
0f57ac297b | ||
|
|
edc6aa0d50 | ||
|
|
ebc0e0f2c9 | ||
|
|
63dd2e781e | ||
|
|
b01ba792bb | ||
|
|
98fb9f25b0 | ||
|
|
cc456f265f | ||
|
|
7058a34f87 | ||
|
|
8e6755845f | ||
|
|
967fa4be68 | ||
|
|
805cf20d04 | ||
|
|
2a8001f490 | ||
|
|
451fc9034f | ||
|
|
0e14a2597e | ||
|
|
ff87c4ea33 | ||
|
|
4f5396c70e | ||
|
|
3c30222fce | ||
|
|
2d04731622 | ||
|
|
e0d2bc3dc9 | ||
|
|
0bda29f143 | ||
|
|
05703720c5 | ||
|
|
cc566bf31f | ||
|
|
e93d8c19d9 | ||
|
|
f2e3182a69 | ||
|
|
f934531083 | ||
|
|
e1c0af345f | ||
|
|
3b3bfe39f9 | ||
|
|
18cc952f8e | ||
|
|
43439bc8c6 | ||
|
|
9a2800e3b3 | ||
|
|
fdaad2b608 | ||
|
|
2d43fe0b39 | ||
|
|
5d776a3ce6 | ||
|
|
5ec7a54bf8 | ||
|
|
0c118477e8 | ||
|
|
c858d0e0b0 | ||
|
|
9cffb43265 | ||
|
|
51a76518ad | ||
|
|
08dbbab70e | ||
|
|
0ec22ae6ff | ||
|
|
ec3c24ba68 | ||
|
|
ed688efdbb | ||
|
|
06543a01d3 | ||
|
|
70c372c3f7 | ||
|
|
b1b3184e75 | ||
|
|
5349fa7ff3 | ||
|
|
9147225956 | ||
|
|
11f3af1ede | ||
|
|
0aa4df40c6 | ||
|
|
7caa885131 | ||
|
|
f4b69cad9b | ||
|
|
fb1db7823b | ||
|
|
10e66f8020 | ||
|
|
4c8648d323 | ||
|
|
02e692a300 | ||
|
|
34151c0095 | ||
|
|
c7cea331e2 | ||
|
|
8ede4993af | ||
|
|
d04dd33d8b | ||
|
|
8cb21253f6 | ||
|
|
7fc697b711 | ||
|
|
80e6e7f0a7 | ||
|
|
d29fc88d68 | ||
|
|
225e9cf70a | ||
|
|
c57c6e37dd | ||
|
|
4d860525bf | ||
|
|
a64263f812 | ||
|
|
95ab2472ce | ||
|
|
54e4747dbc | ||
|
|
2389d47c34 | ||
|
|
9c4f0f042e | ||
|
|
e25e210b06 | ||
|
|
df61a536c1 | ||
|
|
47da3cdaa0 | ||
|
|
8d246f2d98 | ||
|
|
44cd55e55f | ||
|
|
6b42d35223 | ||
|
|
c84150cede | ||
|
|
de2689ac39 | ||
|
|
88c0856d17 | ||
|
|
319031da28 | ||
|
|
d20f3eb039 | ||
|
|
3e13e61d8f | ||
|
|
1260354b36 | ||
|
|
af79fdedf2 | ||
|
|
02333f2f0c | ||
|
|
79bd58e0e6 | ||
|
|
de73ff0e60 | ||
|
|
a9d662f1bd | ||
|
|
65dcbd2236 | ||
|
|
6455734807 | ||
|
|
2eefeaffa7 | ||
|
|
04eaad1c80 | ||
|
|
9f084a0799 | ||
|
|
293b9f1036 | ||
|
|
437376c472 | ||
|
|
cc528c5d8c | ||
|
|
54e2055ffb | ||
|
|
983a30a2e0 | ||
|
|
37d0157d41 | ||
|
|
d4dc236770 | ||
|
|
596742d782 | ||
|
|
ce921c00cd | ||
|
|
3830e443b0 | ||
|
|
9092cad631 | ||
|
|
0b5ecca5c8 | ||
|
|
3d9b305bbb | ||
|
|
0217e359e7 | ||
|
|
695a612e77 | ||
|
|
645d53e2c6 | ||
|
|
73b9d73f64 | ||
|
|
c6675ee4e6 | ||
|
|
6f0b7f3f24 | ||
|
|
776a682fae | ||
|
|
96a3db21a1 | ||
|
|
c33d537ac1 | ||
|
|
5214d48486 | ||
|
|
e360b06d12 | ||
|
|
3c871c38df | ||
|
|
7df043fb15 | ||
|
|
cb542ae46a | ||
|
|
3699177837 | ||
|
|
3a6846b32c | ||
|
|
50586a9716 | ||
|
|
9201992140 | ||
|
|
eb39e9e044 | ||
|
|
5b27f939b8 | ||
|
|
69ee6a6f7e | ||
|
|
bf6d5e529b | ||
|
|
55fd31f575 | ||
|
|
05c063ac24 | ||
|
|
38da63e73c | ||
|
|
cb13d693e6 | ||
|
|
d699774179 | ||
|
|
84a7fdcd07 | ||
|
|
2cd6f9df8e | ||
|
|
eea2e1d271 | ||
|
|
48c5bd942c | ||
|
|
d01d63d82a | ||
|
|
e4fd9cca92 | ||
|
|
8d531b8880 | ||
|
|
b1589e11eb | ||
|
|
b32a772a77 | ||
|
|
7e4562efe1 | ||
|
|
3a6ab4cfc6 | ||
|
|
fba4801a41 | ||
|
|
da21c92815 | ||
|
|
66c15578b1 | ||
|
|
f272be67ab | ||
|
|
e4c36d407f | ||
|
|
4c1915b014 | ||
|
|
6c2b172aae | ||
|
|
95f4f4cb6d | ||
|
|
511aefb706 | ||
|
|
1003639e5b | ||
|
|
fe53e90d37 | ||
|
|
8c73cb5395 | ||
|
|
06ebc04032 | ||
|
|
0ee98e2582 | ||
|
|
d25508fa56 | ||
|
|
916a55b633 | ||
|
|
a6c7b95f97 | ||
|
|
4f8dd771bc | ||
|
|
e0028f5eed | ||
|
|
6d6cbc7e6f | ||
|
|
ee8c2650c3 | ||
|
|
f3ea39d20c | ||
|
|
e78d9e5d2b | ||
|
|
19209718ea | ||
|
|
e75d26260a | ||
|
|
6572ab69ce | ||
|
|
8db87a7559 | ||
|
|
0dcccfc19c | ||
|
|
96219442f5 | ||
|
|
903745c540 | ||
|
|
df741805cd | ||
|
|
ee5c3f3f39 | ||
|
|
714f69be7b | ||
|
|
0d12972e92 | ||
|
|
78b62c28ab | ||
|
|
5c26335fd6 | ||
|
|
7edaeafea5 | ||
|
|
336f3f7a7b | ||
|
|
47dc3715f9 | ||
|
|
7503e05a4a | ||
|
|
b89cf1de07 | ||
|
|
be87078c25 | ||
|
|
faf352acc5 | ||
|
|
0db61dd658 | ||
|
|
ebe8ad8669 | ||
|
|
2e01f0d10e | ||
|
|
754fa1e745 | ||
|
|
8b9e0ba96b | ||
|
|
b0656aca36 | ||
|
|
623b4fee17 | ||
|
|
1b1de1dd01 | ||
|
|
968d8646b2 | ||
|
|
94eef7dceb | ||
|
|
fe647939ce | ||
|
|
984a69cb4b | ||
|
|
098a1ece68 | ||
|
|
ad6f2ad2e1 | ||
|
|
2d55252261 | ||
|
|
30ea3a1335 | ||
|
|
b7d78d1e27 | ||
|
|
3d5a645a3b | ||
|
|
4ad21e7781 | ||
|
|
b99a0c3ca2 | ||
|
|
e1842f6b80 | ||
|
|
0781a3835d | ||
|
|
98a99f0215 | ||
|
|
681b086de0 | ||
|
|
cdcc0b39e2 | ||
|
|
8eb68ba817 | ||
|
|
8d1ae4ea08 | ||
|
|
9c8ea027ef | ||
|
|
aaa56d3354 | ||
|
|
b45c49d3a4 | ||
|
|
5b3202cc89 | ||
|
|
5280f872dc | ||
|
|
fd61b963d5 | ||
|
|
a8937d3046 | ||
|
|
32b05047dc | ||
|
|
117ee509cf | ||
|
|
daf3d374b5 | ||
|
|
337ee2faef | ||
|
|
989fec72bf | ||
|
|
76eb606335 | ||
|
|
c6146a9149 | ||
|
|
f191488338 | ||
|
|
da7336a9a4 | ||
|
|
b3806070ac | ||
|
|
c7b9a77b4a | ||
|
|
4c4ad8320d | ||
|
|
89d29c2519 | ||
|
|
98f962f818 | ||
|
|
5989c4ff34 | ||
|
|
1de76e4da9 | ||
|
|
4e62c255b3 | ||
|
|
7ee54cb089 | ||
|
|
bea03635a1 | ||
|
|
2bc4cd9337 | ||
|
|
ed9ceaefe1 | ||
|
|
3dec2fdc18 | ||
|
|
31e4813df9 | ||
|
|
263f804ab8 | ||
|
|
d383de256b | ||
|
|
28d24cc913 | ||
|
|
bd5c706317 | ||
|
|
fba0021e22 | ||
|
|
aba17e2bc1 | ||
|
|
dd939b5c7e | ||
|
|
eeba21bf0d | ||
|
|
5e47406e09 | ||
|
|
fd883a3211 | ||
|
|
312412ffe4 | ||
|
|
295a69c5f7 | ||
|
|
a8a8f39963 | ||
|
|
90f8eba02d | ||
|
|
2cca1c9136 | ||
|
|
c2eebd61a1 | ||
|
|
59566f61d7 | ||
|
|
7e4c9c91cd | ||
|
|
430ee616db | ||
|
|
2e3a323528 | ||
|
|
09e8408a3d | ||
|
|
2998bbf4b9 | ||
|
|
404382f2e0 | ||
|
|
71db1f62a9 | ||
|
|
07dc6bf7cd | ||
|
|
2de3f6772d | ||
|
|
3f623570fd | ||
|
|
a5dfe54a33 | ||
|
|
7c4a6fea02 | ||
|
|
ff4af6bb4e | ||
|
|
5bdede5596 | ||
|
|
ed052b0e6a | ||
|
|
16b1d0e1f0 | ||
|
|
fea2a8cdbe | ||
|
|
9d55238cef | ||
|
|
8427d63872 | ||
|
|
e8a7b7ee9c | ||
|
|
f8bc87eb4e | ||
|
|
3e6ef9e666 | ||
|
|
ef3d323f63 | ||
|
|
aad9201b24 | ||
|
|
46f090361e | ||
|
|
1ae6adff8e |
134
.github/workflows/build.yml
vendored
134
.github/workflows/build.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Build
|
name: Build
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -9,18 +9,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
env:
|
env:
|
||||||
MYSQL_DATABASE: casdoor
|
MYSQL_DATABASE: casdoor
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache-dependency-path: ./go.mod
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: |
|
run: |
|
||||||
go test -v $(go list ./...) -tags skipCi
|
go test -v $(go list ./...) -tags skipCi
|
||||||
@@ -31,14 +32,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 20
|
||||||
# cache
|
cache: 'yarn'
|
||||||
- uses: c-hive/gha-yarn-cache@v2
|
cache-dependency-path: ./web/yarn.lock
|
||||||
with:
|
|
||||||
directory: ./web
|
|
||||||
- run: yarn install && CI=false yarn run build
|
- run: yarn install && CI=false yarn run build
|
||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
|
|
||||||
@@ -47,10 +46,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache-dependency-path: ./go.mod
|
||||||
- run: go version
|
- run: go version
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
@@ -63,9 +63,10 @@ jobs:
|
|||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
|
cache: false
|
||||||
|
|
||||||
# gen a dummy config file
|
# gen a dummy config file
|
||||||
- run: touch dummy.yml
|
- run: touch dummy.yml
|
||||||
@@ -82,42 +83,43 @@ jobs:
|
|||||||
needs: [ go-tests ]
|
needs: [ go-tests ]
|
||||||
services:
|
services:
|
||||||
mysql:
|
mysql:
|
||||||
image: mysql:5.7
|
image: mysql:5.7
|
||||||
env:
|
env:
|
||||||
MYSQL_DATABASE: casdoor
|
MYSQL_DATABASE: casdoor
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.5'
|
go-version: '^1.16.5'
|
||||||
- uses: actions/setup-node@v2
|
cache-dependency-path: ./go.mod
|
||||||
with:
|
- name: start backend
|
||||||
node-version: 16
|
|
||||||
- name: back start
|
|
||||||
run: nohup go run ./main.go &
|
run: nohup go run ./main.go &
|
||||||
working-directory: ./
|
working-directory: ./
|
||||||
- name: front install
|
- uses: actions/setup-node@v3
|
||||||
run: yarn install
|
|
||||||
working-directory: ./web
|
|
||||||
- name: front start
|
|
||||||
run: nohup yarn start &
|
|
||||||
working-directory: ./web
|
|
||||||
- uses: cypress-io/github-action@v4
|
|
||||||
with:
|
with:
|
||||||
working-directory: ./web
|
node-version: 20
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: ./web/yarn.lock
|
||||||
|
- run: yarn install
|
||||||
|
working-directory: ./web
|
||||||
|
- uses: cypress-io/github-action@v5
|
||||||
|
with:
|
||||||
|
browser: chrome
|
||||||
|
start: yarn start
|
||||||
wait-on: 'http://localhost:7001'
|
wait-on: 'http://localhost:7001'
|
||||||
wait-on-timeout: 180
|
wait-on-timeout: 210
|
||||||
|
working-directory: ./web
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: cypress-screenshots
|
name: cypress-screenshots
|
||||||
path: ./web/cypress/screenshots
|
path: ./web/cypress/screenshots
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: cypress-videos
|
name: cypress-videos
|
||||||
@@ -130,13 +132,13 @@ jobs:
|
|||||||
needs: [ frontend, backend, linter, e2e ]
|
needs: [ frontend, backend, linter, e2e ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: -1
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 20
|
||||||
|
|
||||||
- name: Fetch Previous version
|
- name: Fetch Previous version
|
||||||
id: get-previous-tag
|
id: get-previous-tag
|
||||||
@@ -145,7 +147,7 @@ jobs:
|
|||||||
- name: Release
|
- name: Release
|
||||||
run: yarn global add semantic-release@17.4.4 && semantic-release
|
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Fetch Current version
|
- name: Fetch Current version
|
||||||
id: get-current-tag
|
id: get-current-tag
|
||||||
@@ -166,10 +168,8 @@ jobs:
|
|||||||
elif [ ${old_array[1]} != ${new_array[1]} ]
|
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||||
then
|
then
|
||||||
echo ::set-output name=push::'true'
|
echo ::set-output name=push::'true'
|
||||||
|
|
||||||
else
|
else
|
||||||
echo ::set-output name=push::'false'
|
echo ::set-output name=push::'false'
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
@@ -192,6 +192,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
target: STANDARD
|
target: STANDARD
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
@@ -201,7 +202,38 @@ jobs:
|
|||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
target: ALLINONE
|
target: ALLINONE
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
if: steps.should_push.outputs.push=='true'
|
||||||
|
with:
|
||||||
|
repository: casdoor/casdoor-helm
|
||||||
|
ref: 'master'
|
||||||
|
token: ${{ secrets.GH_BOT_TOKEN }}
|
||||||
|
|
||||||
|
- name: Update Helm Chart
|
||||||
|
if: steps.should_push.outputs.push=='true'
|
||||||
|
run: |
|
||||||
|
# Set the appVersion and version of the chart to the current tag
|
||||||
|
sed -i "s/appVersion: .*/appVersion: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
|
||||||
|
sed -i "s/version: .*/version: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
|
||||||
|
|
||||||
|
REGISTRY=oci://registry-1.docker.io/casbin
|
||||||
|
cd charts/casdoor
|
||||||
|
helm package .
|
||||||
|
PKG_NAME=$(ls *.tgz)
|
||||||
|
helm repo index . --url $REGISTRY --merge index.yaml
|
||||||
|
helm push $PKG_NAME $REGISTRY
|
||||||
|
rm $PKG_NAME
|
||||||
|
|
||||||
|
# Commit and push the changes back to the repository
|
||||||
|
git config --global user.name "casbin-bot"
|
||||||
|
git config --global user.email "bot@casbin.org"
|
||||||
|
git add Chart.yaml index.yaml
|
||||||
|
git commit -m "chore(helm): bump helm charts appVersion to ${{steps.get-current-tag.outputs.tag }}"
|
||||||
|
git tag ${{steps.get-current-tag.outputs.tag }}
|
||||||
|
git push origin HEAD:master --follow-tags
|
||||||
|
|||||||
61
.github/workflows/migrate.yml
vendored
61
.github/workflows/migrate.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: Migration Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'object/migrator**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'object/migrator**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
db-migrator-test:
|
|
||||||
name: db-migrator-test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
mysql:
|
|
||||||
image: mysql:5.7
|
|
||||||
env:
|
|
||||||
MYSQL_DATABASE: casdoor
|
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: '^1.16.5'
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
- name: pull casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install git
|
|
||||||
sudo apt install net-tools
|
|
||||||
sudo mkdir tmp
|
|
||||||
cd tmp
|
|
||||||
sudo git clone https://github.com/casdoor/casdoor.git
|
|
||||||
cd ..
|
|
||||||
working-directory: ./
|
|
||||||
- name: run casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo nohup go run main.go &
|
|
||||||
sudo sleep 2m
|
|
||||||
working-directory: ./tmp/casdoor
|
|
||||||
- name: stop casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo kill -9 `sudo netstat -anltp | grep 8000 | awk '{print $7}' | cut -d / -f 1`
|
|
||||||
working-directory: ./
|
|
||||||
- name: run casdoor-current-version
|
|
||||||
run: |
|
|
||||||
sudo nohup go run ./main.go &
|
|
||||||
sudo sleep 2m
|
|
||||||
working-directory: ./
|
|
||||||
- name: test port-8000
|
|
||||||
run: |
|
|
||||||
if [[ `sudo netstat -anltp | grep 8000 | awk '{print $7}'` == "" ]];then echo 'db-migrator-test fail' && exit 1;fi;
|
|
||||||
echo 'db-migrator-test pass'
|
|
||||||
working-directory: ./
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -18,7 +18,7 @@ bin/
|
|||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
.vscode/
|
.vscode/settings.json
|
||||||
|
|
||||||
tmp/
|
tmp/
|
||||||
tmpFiles/
|
tmpFiles/
|
||||||
@@ -30,3 +30,7 @@ commentsRouter*.go
|
|||||||
|
|
||||||
# ignore build result
|
# ignore build result
|
||||||
casdoor
|
casdoor
|
||||||
|
server
|
||||||
|
|
||||||
|
# include helm-chart
|
||||||
|
!manifests/casdoor
|
||||||
|
|||||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceFolder}",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"debugAdapter": "dlv-dap",
|
||||||
|
"args": ["--createDatabase=true"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
Dockerfile
12
Dockerfile
@@ -1,15 +1,14 @@
|
|||||||
FROM node:16.13.0 AS FRONT
|
FROM --platform=$BUILDPLATFORM node:18.19.0 AS FRONT
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web .
|
COPY ./web .
|
||||||
RUN yarn config set registry https://registry.npmmirror.com
|
RUN yarn install --frozen-lockfile --network-timeout 1000000 && NODE_OPTIONS="--max-old-space-size=4096" yarn run build
|
||||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM golang:1.17.5 AS BACK
|
FROM --platform=$BUILDPLATFORM golang:1.21.13 AS BACK
|
||||||
WORKDIR /go/src/casdoor
|
WORKDIR /go/src/casdoor
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN ./build.sh
|
RUN ./build.sh
|
||||||
|
RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go > version_info.txt
|
||||||
|
|
||||||
FROM alpine:latest AS STANDARD
|
FROM alpine:latest AS STANDARD
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
@@ -20,6 +19,7 @@ ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
|||||||
|
|
||||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||||
RUN apk add --update sudo
|
RUN apk add --update sudo
|
||||||
|
RUN apk add tzdata
|
||||||
RUN apk add curl
|
RUN apk add curl
|
||||||
RUN apk add ca-certificates && update-ca-certificates
|
RUN apk add ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ WORKDIR /
|
|||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||||
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
||||||
|
|
||||||
ENTRYPOINT ["/server"]
|
ENTRYPOINT ["/server"]
|
||||||
@@ -61,6 +62,7 @@ COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
|||||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
|
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||||
COPY --from=FRONT /web/build ./web/build
|
COPY --from=FRONT /web/build ./web/build
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash"]
|
ENTRYPOINT ["/bin/bash"]
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -86,6 +86,9 @@ docker-build: ## Build docker image with the manager.
|
|||||||
docker-push: ## Push docker image with the manager.
|
docker-push: ## Push docker image with the manager.
|
||||||
docker push ${REGISTRY}/${IMG}:${IMG_TAG}
|
docker push ${REGISTRY}/${IMG}:${IMG_TAG}
|
||||||
|
|
||||||
|
deps: ## Run dependencies for local development
|
||||||
|
docker compose up -d db
|
||||||
|
|
||||||
lint-install: ## Install golangci-lint
|
lint-install: ## Install golangci-lint
|
||||||
@# The following installs a specific version of golangci-lint, which is appropriate for a CI server to avoid different results from build to build
|
@# The following installs a specific version of golangci-lint, which is appropriate for a CI server to avoid different results from build to build
|
||||||
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.40.1
|
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.40.1
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -1,5 +1,5 @@
|
|||||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||||
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
|
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#badge">
|
<a href="#badge">
|
||||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
|
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -23,25 +23,39 @@
|
|||||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
|
||||||
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
|
<img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/issues">
|
<a href="https://github.com/casdoor/casdoor/issues">
|
||||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/casdoor/casdoor/network">
|
<a href="https://github.com/casdoor/casdoor/network">
|
||||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://crowdin.com/project/casdoor-site">
|
<a href="https://crowdin.com/project/casdoor-site">
|
||||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://gitter.im/casbin/casdoor">
|
<a href="https://discord.gg/5rPsrAzK7S">
|
||||||
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
|
<img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<sup>Sponsored by</sup>
|
||||||
|
<br>
|
||||||
|
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
|
||||||
|
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
|
||||||
|
</picture>
|
||||||
|
</a><br/>
|
||||||
|
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Online demo
|
## Online demo
|
||||||
|
|
||||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||||
@@ -55,6 +69,7 @@ https://casdoor.org
|
|||||||
|
|
||||||
- By source code: https://casdoor.org/docs/basic/server-installation
|
- By source code: https://casdoor.org/docs/basic/server-installation
|
||||||
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
- By Docker: https://casdoor.org/docs/basic/try-with-docker
|
||||||
|
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
|
||||||
|
|
||||||
## How to connect to Casdoor?
|
## How to connect to Casdoor?
|
||||||
|
|
||||||
@@ -71,9 +86,8 @@ https://casdoor.org/docs/category/integrations
|
|||||||
|
|
||||||
## How to contact?
|
## How to contact?
|
||||||
|
|
||||||
- Gitter: https://gitter.im/casbin/casdoor
|
- Discord: https://discord.gg/5rPsrAzK7S
|
||||||
- Forum: https://forum.casbin.com
|
- Contact: https://casdoor.org/help
|
||||||
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
|
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
|
|
||||||
|
|||||||
103
authz/authz.go
103
authz/authz.go
@@ -15,63 +15,24 @@
|
|||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
"github.com/casdoor/casdoor/util"
|
||||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Enforcer *casbin.Enforcer
|
var Enforcer *casbin.Enforcer
|
||||||
|
|
||||||
func InitAuthz() {
|
func InitApi() {
|
||||||
var err error
|
e, err := object.GetInitializedEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
|
||||||
|
|
||||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
|
||||||
driverName := conf.GetConfigString("driverName")
|
|
||||||
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
|
|
||||||
a, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, "casbin_rule", tableNamePrefix, true)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
modelText := `
|
|
||||||
[request_definition]
|
|
||||||
r = subOwner, subName, method, urlPath, objOwner, objName
|
|
||||||
|
|
||||||
[policy_definition]
|
|
||||||
p = subOwner, subName, method, urlPath, objOwner, objName
|
|
||||||
|
|
||||||
[role_definition]
|
|
||||||
g = _, _
|
|
||||||
|
|
||||||
[policy_effect]
|
|
||||||
e = some(where (p.eft == allow))
|
|
||||||
|
|
||||||
[matchers]
|
|
||||||
m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
|
||||||
(r.subName == p.subName || p.subName == "*" || r.subName != "anonymous" && p.subName == "!anonymous") && \
|
|
||||||
(r.method == p.method || p.method == "*") && \
|
|
||||||
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
|
||||||
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
|
||||||
(r.objName == p.objName || p.objName == "*") || \
|
|
||||||
(r.subOwner == r.objOwner && r.subName == r.objName)
|
|
||||||
`
|
|
||||||
|
|
||||||
m, err := model.NewModelFromString(modelText)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
Enforcer, err = casbin.NewEnforcer(m, a)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Enforcer = e.Enforcer
|
||||||
Enforcer.ClearPolicy()
|
Enforcer.ClearPolicy()
|
||||||
|
|
||||||
// if len(Enforcer.GetPolicy()) == 0 {
|
// if len(Enforcer.GetPolicy()) == 0 {
|
||||||
@@ -85,10 +46,16 @@ p, *, *, POST, /api/login, *, *
|
|||||||
p, *, *, GET, /api/get-app-login, *, *
|
p, *, *, GET, /api/get-app-login, *, *
|
||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/logout, *, *
|
p, *, *, GET, /api/logout, *, *
|
||||||
|
p, *, *, POST, /api/callback, *, *
|
||||||
|
p, *, *, POST, /api/device-auth, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/webhook, *, *
|
p, *, *, GET, /api/user, *, *
|
||||||
|
p, *, *, GET, /api/health, *, *
|
||||||
|
p, *, *, *, /api/webhook, *, *
|
||||||
|
p, *, *, GET, /api/get-qrcode, *, *
|
||||||
p, *, *, GET, /api/get-webhook-event, *, *
|
p, *, *, GET, /api/get-webhook-event, *, *
|
||||||
|
p, *, *, GET, /api/get-captcha-status, *, *
|
||||||
p, *, *, *, /api/login/oauth, *, *
|
p, *, *, *, /api/login/oauth, *, *
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
p, *, *, GET, /api/get-organization-applications, *, *
|
p, *, *, GET, /api/get-organization-applications, *, *
|
||||||
@@ -107,22 +74,40 @@ p, *, *, POST, /api/set-password, *, *
|
|||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
p, *, *, GET, /api/get-captcha, *, *
|
p, *, *, GET, /api/get-captcha, *, *
|
||||||
p, *, *, POST, /api/verify-captcha, *, *
|
p, *, *, POST, /api/verify-captcha, *, *
|
||||||
|
p, *, *, POST, /api/verify-code, *, *
|
||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
p, *, *, GET, /.well-known/webfinger, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
p, *, *, GET, /api/saml/metadata, *, *
|
p, *, *, GET, /api/saml/metadata, *, *
|
||||||
|
p, *, *, *, /api/saml/redirect, *, *
|
||||||
p, *, *, *, /cas, *, *
|
p, *, *, *, /cas, *, *
|
||||||
|
p, *, *, *, /scim, *, *
|
||||||
p, *, *, *, /api/webauthn, *, *
|
p, *, *, *, /api/webauthn, *, *
|
||||||
p, *, *, GET, /api/get-release, *, *
|
p, *, *, GET, /api/get-release, *, *
|
||||||
p, *, *, GET, /api/get-default-application, *, *
|
p, *, *, GET, /api/get-default-application, *, *
|
||||||
|
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||||
|
p, *, *, *, /api/metrics, *, *
|
||||||
|
p, *, *, GET, /api/get-pricing, *, *
|
||||||
|
p, *, *, GET, /api/get-plan, *, *
|
||||||
|
p, *, *, GET, /api/get-subscription, *, *
|
||||||
|
p, *, *, GET, /api/get-provider, *, *
|
||||||
|
p, *, *, GET, /api/get-organization-names, *, *
|
||||||
|
p, *, *, GET, /api/get-all-objects, *, *
|
||||||
|
p, *, *, GET, /api/get-all-actions, *, *
|
||||||
|
p, *, *, GET, /api/get-all-roles, *, *
|
||||||
|
p, *, *, GET, /api/run-casbin-command, *, *
|
||||||
|
p, *, *, POST, /api/refresh-engines, *, *
|
||||||
|
p, *, *, GET, /api/get-invitation-info, *, *
|
||||||
|
p, *, *, GET, /api/faceid-signin-begin, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
sa := stringadapter.NewAdapter(ruleText)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
// load all rules from string adapter to enforcer's memory
|
// load all rules from string adapter to enforcer's memory
|
||||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
err = sa.LoadPolicy(Enforcer.GetModel())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -144,12 +129,25 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", subOwner, subName)
|
user, err := object.GetUser(util.GetId(subOwner, subName))
|
||||||
user := object.GetUser(userId)
|
if err != nil {
|
||||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if subOwner == "app" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
if user.IsDeleted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -160,11 +158,16 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
|
|
||||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
if method == "POST" {
|
if method == "POST" {
|
||||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
|
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" || urlPath == "/api/refresh-engines" {
|
||||||
return true
|
return true
|
||||||
} else if urlPath == "/api/update-user" {
|
} else if urlPath == "/api/update-user" {
|
||||||
// Allow ordinary users to update their own information
|
// Allow ordinary users to update their own information
|
||||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if urlPath == "/api/upload-resource" {
|
||||||
|
if subOwner == "app" && subName == "app-casibase" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
1
build.sh
1
build.sh
@@ -8,5 +8,6 @@ else
|
|||||||
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||||
export GOPROXY="https://goproxy.cn,direct"
|
export GOPROXY="https://goproxy.cn,direct"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
||||||
|
|||||||
@@ -15,22 +15,51 @@
|
|||||||
package captcha
|
package captcha
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
"errors"
|
openapiutil "github.com/alibabacloud-go/openapi-util/service"
|
||||||
"fmt"
|
teaUtil "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
"io"
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
|
const AliyunCaptchaVerifyUrl = "captcha.cn-shanghai.aliyuncs.com"
|
||||||
|
|
||||||
|
type VerifyCaptchaRequest struct {
|
||||||
|
CaptchaVerifyParam *string `json:"CaptchaVerifyParam,omitempty" xml:"CaptchaVerifyParam,omitempty"`
|
||||||
|
SceneId *string `json:"SceneId,omitempty" xml:"SceneId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyCaptchaResponseBodyResult struct {
|
||||||
|
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyCaptchaResponseBody struct {
|
||||||
|
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||||
|
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||||
|
// Id of the request
|
||||||
|
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||||
|
Result *VerifyCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||||
|
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponseBodyResult struct {
|
||||||
|
VerifyCode *string `json:"VerifyCode,omitempty" xml:"VerifyCode,omitempty"`
|
||||||
|
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponseBody struct {
|
||||||
|
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
|
||||||
|
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
|
||||||
|
// Id of the request
|
||||||
|
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||||
|
Result *VerifyIntelligentCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
|
||||||
|
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyIntelligentCaptchaResponse struct {
|
||||||
|
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty" require:"true"`
|
||||||
|
StatusCode *int32 `json:"statusCode,omitempty" xml:"statusCode,omitempty" require:"true"`
|
||||||
|
Body *VerifyIntelligentCaptchaResponseBody `json:"body,omitempty" xml:"body,omitempty" require:"true"`
|
||||||
|
}
|
||||||
type AliyunCaptchaProvider struct{}
|
type AliyunCaptchaProvider struct{}
|
||||||
|
|
||||||
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
||||||
@@ -38,67 +67,69 @@ func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func contentEscape(str string) string {
|
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
str = strings.Replace(str, " ", "%20", -1)
|
config := &openapi.Config{}
|
||||||
str = url.QueryEscape(str)
|
|
||||||
return str
|
config.Endpoint = tea.String(AliyunCaptchaVerifyUrl)
|
||||||
}
|
config.ConnectTimeout = tea.Int(5000)
|
||||||
|
config.ReadTimeout = tea.Int(5000)
|
||||||
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
config.AccessKeyId = tea.String(clientId)
|
||||||
pathData, err := url.ParseQuery(token)
|
config.AccessKeySecret = tea.String(clientSecret)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
client := new(openapi.Client)
|
||||||
}
|
err := client.Init(config)
|
||||||
|
if err != nil {
|
||||||
pathData["Action"] = []string{"AuthenticateSig"}
|
return false, err
|
||||||
pathData["Format"] = []string{"json"}
|
}
|
||||||
pathData["SignatureMethod"] = []string{"HMAC-SHA1"}
|
|
||||||
pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)}
|
request := VerifyCaptchaRequest{CaptchaVerifyParam: tea.String(token), SceneId: tea.String(clientId2)}
|
||||||
pathData["SignatureVersion"] = []string{"1.0"}
|
|
||||||
pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")}
|
err = teaUtil.ValidateModel(&request)
|
||||||
pathData["Version"] = []string{"2018-01-12"}
|
if err != nil {
|
||||||
|
return false, err
|
||||||
var keys []string
|
}
|
||||||
for k := range pathData {
|
|
||||||
keys = append(keys, k)
|
runtime := &teaUtil.RuntimeOptions{}
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
body := map[string]interface{}{}
|
||||||
|
if !tea.BoolValue(teaUtil.IsUnset(request.CaptchaVerifyParam)) {
|
||||||
sortQuery := ""
|
body["CaptchaVerifyParam"] = request.CaptchaVerifyParam
|
||||||
for _, k := range keys {
|
}
|
||||||
sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&"
|
|
||||||
}
|
if !tea.BoolValue(teaUtil.IsUnset(request.SceneId)) {
|
||||||
sortQuery = strings.TrimSuffix(sortQuery, "&")
|
body["SceneId"] = request.SceneId
|
||||||
|
}
|
||||||
stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery))
|
|
||||||
|
req := &openapi.OpenApiRequest{
|
||||||
signature := util.GetHmacSha1(clientSecret+"&", stringToSign)
|
Body: openapiutil.ParseToMap(body),
|
||||||
|
}
|
||||||
resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature)))
|
params := &openapi.Params{
|
||||||
if err != nil {
|
Action: tea.String("VerifyIntelligentCaptcha"),
|
||||||
return false, err
|
Version: tea.String("2023-03-05"),
|
||||||
}
|
Protocol: tea.String("HTTPS"),
|
||||||
|
Pathname: tea.String("/"),
|
||||||
defer resp.Body.Close()
|
Method: tea.String("POST"),
|
||||||
body, err := io.ReadAll(resp.Body)
|
AuthType: tea.String("AK"),
|
||||||
if err != nil {
|
Style: tea.String("RPC"),
|
||||||
return false, err
|
ReqBodyType: tea.String("formData"),
|
||||||
}
|
BodyType: tea.String("json"),
|
||||||
|
}
|
||||||
type captchaResponse struct {
|
|
||||||
Code int `json:"Code"`
|
res := &VerifyIntelligentCaptchaResponse{}
|
||||||
Msg string `json:"Msg"`
|
|
||||||
}
|
resBody, err := client.CallApi(params, req, runtime)
|
||||||
captchaResp := &captchaResponse{}
|
if err != nil {
|
||||||
|
return false, err
|
||||||
err = json.Unmarshal(body, captchaResp)
|
}
|
||||||
if err != nil {
|
|
||||||
return false, err
|
err = tea.Convert(resBody, &res)
|
||||||
}
|
if err != nil {
|
||||||
|
return false, err
|
||||||
if captchaResp.Code != 100 {
|
}
|
||||||
return false, errors.New(captchaResp.Msg)
|
|
||||||
}
|
if res.Body.Result.VerifyResult != nil && *res.Body.Result.VerifyResult {
|
||||||
|
return true, nil
|
||||||
return true, nil
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
return object.VerifyCaptcha(clientSecret, token), nil
|
return object.VerifyCaptcha(clientSecret, token), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
pathData, err := url.ParseQuery(token)
|
pathData, err := url.ParseQuery(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewHCaptchaProvider() *HCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ package captcha
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type CaptchaProvider interface {
|
type CaptchaProvider interface {
|
||||||
VerifyCaptcha(token, clientSecret string) (bool, error)
|
VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||||
@@ -26,6 +26,10 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return NewDefaultCaptchaProvider()
|
return NewDefaultCaptchaProvider()
|
||||||
case "reCAPTCHA":
|
case "reCAPTCHA":
|
||||||
return NewReCaptchaProvider()
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v2":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v3":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
case "Aliyun Captcha":
|
case "Aliyun Captcha":
|
||||||
return NewAliyunCaptchaProvider()
|
return NewAliyunCaptchaProvider()
|
||||||
case "hCaptcha":
|
case "hCaptcha":
|
||||||
@@ -39,11 +43,11 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
|
func VerifyCaptchaByCaptchaType(captchaType, token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
provider := GetCaptchaProvider(captchaType)
|
provider := GetCaptchaProvider(captchaType)
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.VerifyCaptcha(token, clientSecret)
|
return provider.VerifyCaptcha(token, clientId, clientSecret, clientId2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewReCaptchaProvider() *ReCaptchaProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider {
|
|||||||
return captcha
|
return captcha
|
||||||
}
|
}
|
||||||
|
|
||||||
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
|
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
|
||||||
reqData := url.Values{
|
reqData := url.Values{
|
||||||
"secret": {clientSecret},
|
"secret": {clientSecret},
|
||||||
"response": {token},
|
"response": {token},
|
||||||
|
|||||||
@@ -13,12 +13,25 @@ isCloudIntranet = false
|
|||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
socks5Proxy = "127.0.0.1:10808"
|
socks5Proxy = "127.0.0.1:10808"
|
||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 2000
|
initScore = 0
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
|
isUsernameLowered = false
|
||||||
origin =
|
origin =
|
||||||
|
originFrontend =
|
||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
enableErrorMask = false
|
||||||
|
enableGzip = true
|
||||||
|
inactiveTimeoutMinutes =
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
languages = en,zh,es,fr,de,ja,ko,ru,vi
|
ldapsCertId = ""
|
||||||
|
ldapsServerPort = 636
|
||||||
|
radiusServerPort = 1812
|
||||||
|
radiusDefaultOrganization = "built-in"
|
||||||
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
|
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
|
initDataNewOnly = false
|
||||||
|
initDataFile = "./init_data.json"
|
||||||
|
frontendBaseDir = "../cc_0"
|
||||||
65
conf/conf.go
65
conf/conf.go
@@ -15,7 +15,6 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -25,15 +24,6 @@ import (
|
|||||||
"github.com/beego/beego"
|
"github.com/beego/beego"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Quota struct {
|
|
||||||
Organization int `json:"organization"`
|
|
||||||
User int `json:"user"`
|
|
||||||
Application int `json:"application"`
|
|
||||||
Provider int `json:"provider"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var quota = &Quota{-1, -1, -1, -1}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// this array contains the beego configuration items that may be modified via env
|
// this array contains the beego configuration items that may be modified via env
|
||||||
presetConfigItems := []string{"httpport", "appname"}
|
presetConfigItems := []string{"httpport", "appname"}
|
||||||
@@ -45,17 +35,6 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initQuota()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initQuota() {
|
|
||||||
res := beego.AppConfig.String("quota")
|
|
||||||
if res != "" {
|
|
||||||
err := json.Unmarshal([]byte(res), quota)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigString(key string) string {
|
func GetConfigString(key string) string {
|
||||||
@@ -67,31 +46,39 @@ func GetConfigString(key string) string {
|
|||||||
if res == "" {
|
if res == "" {
|
||||||
if key == "staticBaseUrl" {
|
if key == "staticBaseUrl" {
|
||||||
res = "https://cdn.casbin.org"
|
res = "https://cdn.casbin.org"
|
||||||
|
} else if key == "logConfig" {
|
||||||
|
res = fmt.Sprintf("{\"filename\": \"logs/%s.log\", \"maxdays\":99999, \"perm\":\"0770\"}", beego.AppConfig.String("appname"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigBool(key string) (bool, error) {
|
func GetConfigBool(key string) bool {
|
||||||
value := GetConfigString(key)
|
value := GetConfigString(key)
|
||||||
if value == "true" {
|
if value == "true" {
|
||||||
return true, nil
|
return true
|
||||||
} else if value == "false" {
|
} else {
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
return false, fmt.Errorf("value %s cannot be converted into bool", value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigInt64(key string) (int64, error) {
|
func GetConfigInt64(key string) (int64, error) {
|
||||||
value := GetConfigString(key)
|
value := GetConfigString(key)
|
||||||
num, err := strconv.ParseInt(value, 10, 64)
|
num, err := strconv.ParseInt(value, 10, 64)
|
||||||
return num, err
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("GetConfigInt64(%s) error, %s", key, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return num, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigDataSourceName() string {
|
func GetConfigDataSourceName() string {
|
||||||
dataSourceName := GetConfigString("dataSourceName")
|
dataSourceName := GetConfigString("dataSourceName")
|
||||||
|
return ReplaceDataSourceNameByDocker(dataSourceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceDataSourceNameByDocker(dataSourceName string) string {
|
||||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||||
if runningInDocker == "true" {
|
if runningInDocker == "true" {
|
||||||
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
|
||||||
@@ -101,7 +88,6 @@ func GetConfigDataSourceName() string {
|
|||||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataSourceName
|
return dataSourceName
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,15 +96,10 @@ func GetLanguage(language string) string {
|
|||||||
return "en"
|
return "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(language) < 2 {
|
if len(language) != 2 || language == "nu" {
|
||||||
return "en"
|
return "en"
|
||||||
}
|
|
||||||
|
|
||||||
language = language[0:2]
|
|
||||||
if strings.Contains(GetConfigString("languages"), language) {
|
|
||||||
return language
|
|
||||||
} else {
|
} else {
|
||||||
return "en"
|
return language
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,17 +114,3 @@ func GetConfigBatchSize() int {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetConfigQuota() *Quota {
|
|
||||||
return quota
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigRealDataSourceName(driverName string) string {
|
|
||||||
var dataSourceName string
|
|
||||||
if driverName != "mysql" {
|
|
||||||
dataSourceName = GetConfigDataSourceName()
|
|
||||||
} else {
|
|
||||||
dataSourceName = GetConfigDataSourceName() + GetConfigString("dbName")
|
|
||||||
}
|
|
||||||
return dataSourceName
|
|
||||||
}
|
|
||||||
|
|||||||
48
conf/conf_quota.go
Normal file
48
conf/conf_quota.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 conf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Quota struct {
|
||||||
|
Organization int `json:"organization"`
|
||||||
|
User int `json:"user"`
|
||||||
|
Application int `json:"application"`
|
||||||
|
Provider int `json:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var quota = &Quota{-1, -1, -1, -1}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
initQuota()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initQuota() {
|
||||||
|
res := beego.AppConfig.String("quota")
|
||||||
|
if res != "" {
|
||||||
|
err := json.Unmarshal([]byte(res), quota)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfigQuota() *Quota {
|
||||||
|
return quota
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ func TestGetConfBool(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
for _, scenery := range scenarios {
|
for _, scenery := range scenarios {
|
||||||
t.Run(scenery.description, func(t *testing.T) {
|
t.Run(scenery.description, func(t *testing.T) {
|
||||||
actual, err := GetConfigBool(scenery.input)
|
actual := GetConfigBool(scenery.input)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, scenery.expected, actual)
|
assert.Equal(t, scenery.expected, actual)
|
||||||
})
|
})
|
||||||
@@ -109,3 +109,19 @@ func TestGetConfigQuota(t *testing.T) {
|
|||||||
assert.Equal(t, scenery.expected, quota)
|
assert.Equal(t, scenery.expected, quota)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetConfigLogs(t *testing.T) {
|
||||||
|
scenarios := []struct {
|
||||||
|
description string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"Default log config", `{"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := beego.LoadAppConfig("ini", "app.conf")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
for _, scenery := range scenarios {
|
||||||
|
quota := GetConfigString("logConfig")
|
||||||
|
assert.Equal(t, scenery.expected, quota)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/form"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@@ -32,45 +32,9 @@ const (
|
|||||||
ResponseTypeIdToken = "id_token"
|
ResponseTypeIdToken = "id_token"
|
||||||
ResponseTypeSaml = "saml"
|
ResponseTypeSaml = "saml"
|
||||||
ResponseTypeCas = "cas"
|
ResponseTypeCas = "cas"
|
||||||
|
ResponseTypeDevice = "device"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestForm struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
|
|
||||||
Organization string `json:"organization"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FirstName string `json:"firstName"`
|
|
||||||
LastName string `json:"lastName"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Phone string `json:"phone"`
|
|
||||||
Affiliation string `json:"affiliation"`
|
|
||||||
IdCard string `json:"idCard"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
|
|
||||||
Application string `json:"application"`
|
|
||||||
Provider string `json:"provider"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
State string `json:"state"`
|
|
||||||
RedirectUri string `json:"redirectUri"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
|
|
||||||
EmailCode string `json:"emailCode"`
|
|
||||||
PhoneCode string `json:"phoneCode"`
|
|
||||||
CountryCode string `json:"countryCode"`
|
|
||||||
|
|
||||||
AutoSignin bool `json:"autoSignin"`
|
|
||||||
|
|
||||||
RelayState string `json:"relayState"`
|
|
||||||
SamlRequest string `json:"samlRequest"`
|
|
||||||
SamlResponse string `json:"samlResponse"`
|
|
||||||
|
|
||||||
CaptchaType string `json:"captchaType"`
|
|
||||||
CaptchaToken string `json:"captchaToken"`
|
|
||||||
ClientSecret string `json:"clientSecret"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
@@ -78,9 +42,12 @@ type Response struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
|
Data3 interface{} `json:"data3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Captcha struct {
|
type Captcha struct {
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
Scene string `json:"scene"`
|
Scene string `json:"scene"`
|
||||||
@@ -93,6 +60,17 @@ type Captcha struct {
|
|||||||
SubType string `json:"subType"`
|
SubType string `json:"subType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||||
|
// https://github.com/FriendsOfFlarum/passport
|
||||||
|
type LaravelResponse struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerifiedAt string `json:"email_verified_at"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
// Signup
|
// Signup
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title Signup
|
// @Title Signup
|
||||||
@@ -103,95 +81,160 @@ type Captcha struct {
|
|||||||
// @router /signup [post]
|
// @router /signup [post]
|
||||||
func (c *ApiController) Signup() {
|
func (c *ApiController) Signup() {
|
||||||
if c.GetSessionUsername() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
c.ResponseError(c.T("account:Please sign out first before signing up"), c.GetSessionUsername())
|
c.ResponseError(c.T("account:Please sign out first"), c.GetSessionUsername())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var form RequestForm
|
var authForm form.AuthForm
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !application.EnableSignUp {
|
if !application.EnableSignUp {
|
||||||
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
organization, err := object.GetOrganization(util.GetId("admin", authForm.Organization))
|
||||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.CountryCode, form.Affiliation, c.GetAcceptLanguage())
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), authForm.Organization))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
err = object.CheckEntryIp(clientIp, nil, application, organization, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
|
invitation, msg := object.CheckInvitationCode(application, organization, &authForm, c.GetAcceptLanguage())
|
||||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode, c.GetAcceptLanguage())
|
if msg != "" {
|
||||||
if len(checkResult) != 0 {
|
c.ResponseError(msg)
|
||||||
c.ResponseError(c.T("account:Email: %s"), checkResult)
|
return
|
||||||
|
}
|
||||||
|
invitationName := ""
|
||||||
|
if invitation != nil {
|
||||||
|
invitationName = invitation.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
userEmailVerified := false
|
||||||
|
|
||||||
|
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
|
||||||
|
var checkResult *object.VerifyResult
|
||||||
|
checkResult, err = object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if checkResult.Code != object.VerificationSuccess {
|
||||||
|
c.ResponseError(checkResult.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userEmailVerified = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkPhone string
|
var checkPhone string
|
||||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" {
|
||||||
checkPhone, _ = util.GetE164Number(form.Phone, form.CountryCode)
|
checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode)
|
||||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
|
|
||||||
if len(checkResult) != 0 {
|
var checkResult *object.VerifyResult
|
||||||
c.ResponseError(c.T("account:Phone: %s"), checkResult)
|
checkResult, err = object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if checkResult.Code != object.VerificationSuccess {
|
||||||
|
c.ResponseError(checkResult.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id := util.GenerateId()
|
id, err := object.GenerateIdForNewUser(application)
|
||||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
if err != nil {
|
||||||
lastUser := object.GetLastUser(form.Organization)
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
lastIdInt := -1
|
|
||||||
if lastUser != nil {
|
|
||||||
lastIdInt = util.ParseInt(lastUser.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
id = strconv.Itoa(lastIdInt + 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
username := form.Username
|
username := authForm.Username
|
||||||
if !application.IsSignupItemVisible("Username") {
|
if !application.IsSignupItemVisible("Username") {
|
||||||
username = id
|
if organization.UseEmailAsUsername && application.IsSignupItemVisible("Email") {
|
||||||
|
username = authForm.Email
|
||||||
|
} else {
|
||||||
|
username = id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initScore, err := getInitScore(organization)
|
initScore, err := organization.GetInitScore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userType := "normal-user"
|
||||||
|
if authForm.Plan != "" && authForm.Pricing != "" {
|
||||||
|
err = object.CheckPricingAndPlan(authForm.Organization, authForm.Pricing, authForm.Plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userType = "paid-user"
|
||||||
|
}
|
||||||
|
|
||||||
user := &object.User{
|
user := &object.User{
|
||||||
Owner: form.Organization,
|
Owner: authForm.Organization,
|
||||||
Name: username,
|
Name: username,
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Id: id,
|
Id: id,
|
||||||
Type: "normal-user",
|
Type: userType,
|
||||||
Password: form.Password,
|
Password: authForm.Password,
|
||||||
DisplayName: form.Name,
|
DisplayName: authForm.Name,
|
||||||
|
Gender: authForm.Gender,
|
||||||
|
Bio: authForm.Bio,
|
||||||
|
Tag: authForm.Tag,
|
||||||
|
Education: authForm.Education,
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: form.Email,
|
Email: authForm.Email,
|
||||||
Phone: form.Phone,
|
Phone: authForm.Phone,
|
||||||
CountryCode: form.CountryCode,
|
CountryCode: authForm.CountryCode,
|
||||||
Address: []string{},
|
Address: []string{},
|
||||||
Affiliation: form.Affiliation,
|
Affiliation: authForm.Affiliation,
|
||||||
IdCard: form.IdCard,
|
IdCard: authForm.IdCard,
|
||||||
Region: form.Region,
|
Region: authForm.Region,
|
||||||
Score: initScore,
|
Score: initScore,
|
||||||
IsAdmin: false,
|
IsAdmin: false,
|
||||||
IsGlobalAdmin: false,
|
|
||||||
IsForbidden: false,
|
IsForbidden: false,
|
||||||
IsDeleted: false,
|
IsDeleted: false,
|
||||||
SignupApplication: application.Name,
|
SignupApplication: application.Name,
|
||||||
Properties: map[string]string{},
|
Properties: map[string]string{},
|
||||||
Karma: 0,
|
Karma: 0,
|
||||||
|
Invitation: invitationName,
|
||||||
|
InvitationCode: authForm.InvitationCode,
|
||||||
|
EmailVerified: userEmailVerified,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(organization.Tags) > 0 {
|
if len(organization.Tags) > 0 {
|
||||||
@@ -202,33 +245,69 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if application.GetSignupItemRule("Display name") == "First, last" {
|
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||||
if form.FirstName != "" || form.LastName != "" {
|
if authForm.FirstName != "" || authForm.LastName != "" {
|
||||||
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName)
|
||||||
user.FirstName = form.FirstName
|
user.FirstName = authForm.FirstName
|
||||||
user.LastName = form.LastName
|
user.LastName = authForm.LastName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.AddUser(user)
|
if invitation != nil && invitation.SignupGroup != "" {
|
||||||
if !affected {
|
user.Groups = []string{invitation.SignupGroup}
|
||||||
c.ResponseError(c.T("account:Invalid information"), util.StructToJson(user))
|
}
|
||||||
|
|
||||||
|
if application.DefaultGroup != "" && user.Groups == nil {
|
||||||
|
user.Groups = []string{application.DefaultGroup}
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.AddUser(user, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
object.AddUserToOriginalDatabase(user)
|
if !affected {
|
||||||
|
c.ResponseError(c.T("account:Failed to add user"), util.StructToJson(user))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if application.HasPromptPage() {
|
err = object.AddUserToOriginalDatabase(user)
|
||||||
// The prompt page needs the user to be signed in
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if invitation != nil {
|
||||||
|
invitation.UsedCount += 1
|
||||||
|
_, err := object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Type == "normal-user" {
|
||||||
c.SetSessionUsername(user.GetId())
|
c.SetSessionUsername(user.GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
object.DisableVerificationCode(form.Email)
|
if authForm.Email != "" {
|
||||||
object.DisableVerificationCode(checkPhone)
|
err = object.DisableVerificationCode(authForm.Email)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
if checkPhone != "" {
|
||||||
record.Organization = application.Organization
|
err = object.DisableVerificationCode(checkPhone)
|
||||||
record.User = user.Name
|
if err != nil {
|
||||||
util.SafeGoroutine(func() { object.AddRecord(record) })
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ctx.Input.SetParam("recordUserId", user.GetId())
|
||||||
|
c.Ctx.Input.SetParam("recordSignup", "true")
|
||||||
|
|
||||||
userId := user.GetId()
|
userId := user.GetId()
|
||||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
@@ -244,21 +323,31 @@ func (c *ApiController) Signup() {
|
|||||||
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||||
// @Param state query string false "state"
|
// @Param state query string false "state"
|
||||||
// @Success 200 {object} controllers.Response The Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /logout [get,post]
|
// @router /logout [post]
|
||||||
func (c *ApiController) Logout() {
|
func (c *ApiController) Logout() {
|
||||||
user := c.GetSessionUsername()
|
|
||||||
|
|
||||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
|
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
|
||||||
accessToken := c.Input().Get("id_token_hint")
|
accessToken := c.Input().Get("id_token_hint")
|
||||||
redirectUri := c.Input().Get("post_logout_redirect_uri")
|
redirectUri := c.Input().Get("post_logout_redirect_uri")
|
||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
|
|
||||||
if accessToken == "" && redirectUri == "" {
|
user := c.GetSessionUsername()
|
||||||
c.ClearUserSession()
|
|
||||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
if accessToken == "" && redirectUri == "" {
|
||||||
owner, username := util.GetOwnerAndNameFromId(user)
|
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||||
|
if user == "" {
|
||||||
|
c.ResponseOk()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ClearUserSession()
|
||||||
|
c.ClearTokenSession()
|
||||||
|
owner, username := util.GetOwnerAndNameFromId(user)
|
||||||
|
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
|
||||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||||
|
|
||||||
application := c.GetSessionApplication()
|
application := c.GetSessionApplication()
|
||||||
@@ -269,43 +358,66 @@ func (c *ApiController) Logout() {
|
|||||||
c.ResponseOk(user, application.HomepageUrl)
|
c.ResponseOk(user, application.HomepageUrl)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if redirectUri == "" {
|
// "post_logout_redirect_uri" has been made optional, see: https://github.com/casdoor/casdoor/issues/2151
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
|
// if redirectUri == "" {
|
||||||
return
|
// c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
|
||||||
}
|
// return
|
||||||
|
// }
|
||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": id_token_hint")
|
c.ResponseError(c.T("general:Missing parameter") + ": id_token_hint")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, application, token := object.ExpireTokenByAccessToken(accessToken)
|
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||||
if !affected {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == nil {
|
||||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.IsRedirectUriValid(redirectUri) {
|
if user == "" {
|
||||||
if user == "" {
|
user = util.GetId(token.Organization, token.User)
|
||||||
user = util.GetId(token.Organization, token.User)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
c.ClearUserSession()
|
c.ClearUserSession()
|
||||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
c.ClearTokenSession()
|
||||||
owner, username := util.GetOwnerAndNameFromId(user)
|
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||||
|
owner, username := util.GetOwnerAndNameFromId(user)
|
||||||
|
|
||||||
object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
_, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
|
||||||
} else {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||||
|
|
||||||
|
if redirectUri == "" {
|
||||||
|
c.ResponseOk()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if application.IsRedirectUriValid(redirectUri) {
|
||||||
|
redirectUrl := redirectUri
|
||||||
|
if state != "" {
|
||||||
|
if strings.Contains(redirectUri, "?") {
|
||||||
|
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||||
|
} else {
|
||||||
|
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||||
|
} else {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +428,7 @@ func (c *ApiController) Logout() {
|
|||||||
// @Success 200 {object} controllers.Response The Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /get-account [get]
|
// @router /get-account [get]
|
||||||
func (c *ApiController) GetAccount() {
|
func (c *ApiController) GetAccount() {
|
||||||
|
var err error
|
||||||
user, ok := c.RequireSignedInUser()
|
user, ok := c.RequireSignedInUser()
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -323,20 +436,58 @@ func (c *ApiController) GetAccount() {
|
|||||||
|
|
||||||
managedAccounts := c.Input().Get("managedAccounts")
|
managedAccounts := c.Input().Get("managedAccounts")
|
||||||
if managedAccounts == "1" {
|
if managedAccounts == "1" {
|
||||||
user = object.ExtendManagedAccountsWithUser(user)
|
user, err = object.ExtendManagedAccountsWithUser(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object.ExtendUserWithRolesAndPermissions(user)
|
err = object.ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user.Permissions = object.GetMaskedPermissions(user.Permissions)
|
if user != nil {
|
||||||
user.Roles = object.GetMaskedRoles(user.Roles)
|
user.Permissions = object.GetMaskedPermissions(user.Permissions)
|
||||||
|
user.Roles = object.GetMaskedRoles(user.Roles)
|
||||||
|
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, err := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdminOrSelf := c.IsAdminOrSelf(user)
|
||||||
|
u, err := object.GetMaskedUser(user, isAdminOrSelf)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization != nil && len(organization.CountryCodes) == 1 && u != nil && u.CountryCode == "" {
|
||||||
|
u.CountryCode = organization.CountryCodes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken := c.GetSessionToken()
|
||||||
|
if accessToken == "" {
|
||||||
|
accessToken, err = object.GetAccessTokenByUser(user, c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetSessionToken(accessToken)
|
||||||
|
}
|
||||||
|
u.AccessToken = accessToken
|
||||||
|
|
||||||
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
|
|
||||||
resp := Response{
|
resp := Response{
|
||||||
Status: "ok",
|
Status: "ok",
|
||||||
Sub: user.Id,
|
Sub: user.Id,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Data: object.GetMaskedUser(user),
|
Data: u,
|
||||||
Data2: organization,
|
Data2: organization,
|
||||||
}
|
}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
@@ -358,16 +509,48 @@ func (c *ApiController) GetUserinfo() {
|
|||||||
|
|
||||||
scope, aud := c.GetSessionOidc()
|
scope, aud := c.GetSessionOidc()
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
userInfo := object.GetUserInfo(user, scope, aud, host)
|
|
||||||
|
userInfo, err := object.GetUserInfo(user, scope, aud, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = userInfo
|
c.Data["json"] = userInfo
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserinfo2
|
||||||
|
// LaravelResponse
|
||||||
|
// @Title UserInfo2
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description return Laravel compatible user information according to OAuth 2.0
|
||||||
|
// @Success 200 {object} controllers.LaravelResponse The Response object
|
||||||
|
// @router /user [get]
|
||||||
|
func (c *ApiController) GetUserinfo2() {
|
||||||
|
user, ok := c.RequireSignedInUser()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := LaravelResponse{
|
||||||
|
Id: user.Id,
|
||||||
|
Name: user.Name,
|
||||||
|
Email: user.Email,
|
||||||
|
EmailVerifiedAt: user.CreatedTime,
|
||||||
|
CreatedAt: user.CreatedTime,
|
||||||
|
UpdatedAt: user.UpdatedTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = response
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
// GetCaptcha ...
|
// GetCaptcha ...
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Title GetCaptcha
|
// @Title GetCaptcha
|
||||||
// @router /api/get-captcha [get]
|
// @router /get-captcha [get]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
func (c *ApiController) GetCaptcha() {
|
func (c *ApiController) GetCaptcha() {
|
||||||
applicationId := c.Input().Get("applicationId")
|
applicationId := c.Input().Get("applicationId")
|
||||||
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||||
@@ -380,15 +563,22 @@ func (c *ApiController) GetCaptcha() {
|
|||||||
|
|
||||||
if captchaProvider != nil {
|
if captchaProvider != nil {
|
||||||
if captchaProvider.Type == "Default" {
|
if captchaProvider.Type == "Default" {
|
||||||
id, img := object.GetCaptcha()
|
id, img, err := object.GetCaptcha()
|
||||||
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(Captcha{Owner: captchaProvider.Owner, Name: captchaProvider.Name, Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
|
||||||
return
|
return
|
||||||
} else if captchaProvider.Type != "" {
|
} else if captchaProvider.Type != "" {
|
||||||
c.ResponseOk(Captcha{
|
c.ResponseOk(Captcha{
|
||||||
|
Owner: captchaProvider.Owner,
|
||||||
|
Name: captchaProvider.Name,
|
||||||
Type: captchaProvider.Type,
|
Type: captchaProvider.Type,
|
||||||
SubType: captchaProvider.SubType,
|
SubType: captchaProvider.SubType,
|
||||||
ClientId: captchaProvider.ClientId,
|
ClientId: captchaProvider.ClientId,
|
||||||
ClientSecret: captchaProvider.ClientSecret,
|
ClientSecret: "***",
|
||||||
ClientId2: captchaProvider.ClientId2,
|
ClientId2: captchaProvider.ClientId2,
|
||||||
ClientSecret2: captchaProvider.ClientSecret2,
|
ClientSecret2: captchaProvider.ClientSecret2,
|
||||||
})
|
})
|
||||||
|
|||||||
145
controllers/adapter.go
Normal file
145
controllers/adapter.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetAdapters
|
||||||
|
// @Title GetAdapters
|
||||||
|
// @Tag Adapter API
|
||||||
|
// @Description get adapters
|
||||||
|
// @Param owner query string true "The owner of adapters"
|
||||||
|
// @Success 200 {array} object.Adapter The Response object
|
||||||
|
// @router /get-adapters [get]
|
||||||
|
func (c *ApiController) GetAdapters() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
adapters, err := object.GetAdapters(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(adapters)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetAdapterCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
adapters, err := object.GetPaginationAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(adapters, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdapter
|
||||||
|
// @Title GetAdapter
|
||||||
|
// @Tag Adapter API
|
||||||
|
// @Description get adapter
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the adapter"
|
||||||
|
// @Success 200 {object} object.Adapter The Response object
|
||||||
|
// @router /get-adapter [get]
|
||||||
|
func (c *ApiController) GetAdapter() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
adapter, err := object.GetAdapter(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAdapter
|
||||||
|
// @Title UpdateAdapter
|
||||||
|
// @Tag Adapter API
|
||||||
|
// @Description update adapter
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the adapter"
|
||||||
|
// @Param body body object.Adapter true "The details of the adapter"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-adapter [post]
|
||||||
|
func (c *ApiController) UpdateAdapter() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var adapter object.Adapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateAdapter(id, &adapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAdapter
|
||||||
|
// @Title AddAdapter
|
||||||
|
// @Tag Adapter API
|
||||||
|
// @Description add adapter
|
||||||
|
// @Param body body object.Adapter true "The details of the adapter"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-adapter [post]
|
||||||
|
func (c *ApiController) AddAdapter() {
|
||||||
|
var adapter object.Adapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddAdapter(&adapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAdapter
|
||||||
|
// @Title DeleteAdapter
|
||||||
|
// @Tag Adapter API
|
||||||
|
// @Description delete adapter
|
||||||
|
// @Param body body object.Adapter true "The details of the adapter"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-adapter [post]
|
||||||
|
func (c *ApiController) DeleteAdapter() {
|
||||||
|
var adapter object.Adapter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &adapter)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteAdapter(&adapter))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
@@ -40,21 +40,35 @@ func (c *ApiController) GetApplications() {
|
|||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
organization := c.Input().Get("organization")
|
organization := c.Input().Get("organization")
|
||||||
|
var err error
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
var applications []*object.Application
|
var applications []*object.Application
|
||||||
if organization == "" {
|
if organization == "" {
|
||||||
applications = object.GetApplications(owner)
|
applications, err = object.GetApplications(owner)
|
||||||
} else {
|
} else {
|
||||||
applications = object.GetOrganizationApplications(owner, organization)
|
applications, err = object.GetOrganizationApplications(owner, organization)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
c.ResponseError(err.Error())
|
||||||
c.ServeJSON()
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
count, err := object.GetApplicationCount(owner, field, value)
|
||||||
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
application, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applications := object.GetMaskedApplications(application, userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +84,36 @@ func (c *ApiController) GetApplication() {
|
|||||||
userId := c.GetSessionUsername()
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
|
application, err := object.GetApplication(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
|
||||||
|
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert == nil {
|
||||||
|
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert != nil {
|
||||||
|
application.CertPublicKey = cert.Certificate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserApplication
|
// GetUserApplication
|
||||||
@@ -84,14 +126,28 @@ func (c *ApiController) GetApplication() {
|
|||||||
func (c *ApiController) GetUserApplication() {
|
func (c *ApiController) GetUserApplication() {
|
||||||
userId := c.GetSessionUsername()
|
userId := c.GetSessionUsername()
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
user := object.GetUser(id)
|
|
||||||
|
user, err := object.GetUser(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
|
application, err := object.GetApplicationByUser(user)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), user.Owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrganizationApplications
|
// GetOrganizationApplications
|
||||||
@@ -118,14 +174,42 @@ func (c *ApiController) GetOrganizationApplications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
var applications []*object.Application
|
applications, err := object.GetOrganizationApplications(owner, organization)
|
||||||
applications = object.GetOrganizationApplications(owner, organization)
|
if err != nil {
|
||||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
c.ResponseError(err.Error())
|
||||||
c.ServeJSON()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationApplicationCount(owner, organization, field, value)))
|
|
||||||
applications := object.GetMaskedApplications(object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
count, err := object.GetOrganizationApplicationCount(owner, organization, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
applications, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applications = object.GetMaskedApplications(applications, userId)
|
||||||
c.ResponseOk(applications, paginator.Nums())
|
c.ResponseOk(applications, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,6 +232,11 @@ func (c *ApiController) UpdateApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -167,8 +256,18 @@ func (c *ApiController) AddApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := object.GetApplicationCount("", "", "")
|
count, err := object.GetApplicationCount("", "", "")
|
||||||
if err := checkQuotaForApplication(count); err != nil {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkQuotaForApplication(int(count)); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
1133
controllers/auth.go
1133
controllers/auth.go
File diff suppressed because it is too large
Load Diff
@@ -41,18 +41,65 @@ type SessionData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) IsGlobalAdmin() bool {
|
func (c *ApiController) IsGlobalAdmin() bool {
|
||||||
username := c.GetSessionUsername()
|
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||||
if strings.HasPrefix(username, "app/") {
|
|
||||||
// e.g., "app/app-casnode"
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
user := object.GetUser(username)
|
return isGlobalAdmin
|
||||||
if user == nil {
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) IsAdmin() bool {
|
||||||
|
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||||
|
if !isGlobalAdmin && user == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return user.Owner == "built-in" || user.IsGlobalAdmin
|
return isGlobalAdmin || user.IsAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
|
||||||
|
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||||
|
if isGlobalAdmin || (user != nil && user.IsAdmin) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil || user2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Owner == user2.Owner && user.Name == user2.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
|
||||||
|
username := c.GetSessionUsername()
|
||||||
|
if object.IsAppUser(username) {
|
||||||
|
// e.g., "app/app-casnode"
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.getCurrentUser()
|
||||||
|
if user == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.IsGlobalAdmin(), user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) getCurrentUser() *object.User {
|
||||||
|
var user *object.User
|
||||||
|
var err error
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
if userId == "" {
|
||||||
|
user = nil
|
||||||
|
} else {
|
||||||
|
user, err = object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSessionUsername ...
|
// GetSessionUsername ...
|
||||||
@@ -75,12 +122,26 @@ func (c *ApiController) GetSessionUsername() string {
|
|||||||
return user.(string)
|
return user.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionToken() string {
|
||||||
|
accessToken := c.GetSession("accessToken")
|
||||||
|
if accessToken == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessToken.(string)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetSessionApplication() *object.Application {
|
func (c *ApiController) GetSessionApplication() *object.Application {
|
||||||
clientId := c.GetSession("aud")
|
clientId := c.GetSession("aud")
|
||||||
if clientId == nil {
|
if clientId == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
application := object.GetApplicationByClientId(clientId.(string))
|
application, err := object.GetApplicationByClientId(clientId.(string))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +150,10 @@ func (c *ApiController) ClearUserSession() {
|
|||||||
c.SetSessionData(nil)
|
c.SetSessionData(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) ClearTokenSession() {
|
||||||
|
c.SetSessionToken("")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||||
sessionData := c.GetSessionData()
|
sessionData := c.GetSessionData()
|
||||||
if sessionData != nil &&
|
if sessionData != nil &&
|
||||||
@@ -115,6 +180,10 @@ func (c *ApiController) SetSessionUsername(user string) {
|
|||||||
c.SetSession("username", user)
|
c.SetSession("username", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) SetSessionToken(accessToken string) {
|
||||||
|
c.SetSession("accessToken", accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
// GetSessionData ...
|
// GetSessionData ...
|
||||||
func (c *ApiController) GetSessionData() *SessionData {
|
func (c *ApiController) GetSessionData() *SessionData {
|
||||||
session := c.GetSession("SessionData")
|
session := c.GetSession("SessionData")
|
||||||
@@ -142,8 +211,30 @@ func (c *ApiController) SetSessionData(s *SessionData) {
|
|||||||
c.SetSession("SessionData", util.StructToJson(s))
|
c.SetSession("SessionData", util.StructToJson(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapActionResponse(affected bool) *Response {
|
func (c *ApiController) setMfaUserSession(userId string) {
|
||||||
if affected {
|
c.SetSession(object.MfaSessionUserId, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) getMfaUserSession() string {
|
||||||
|
userId := c.Ctx.Input.CruSession.Get(object.MfaSessionUserId)
|
||||||
|
if userId == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return userId.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) setExpireForSession() {
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
timestamp += 3600 * 24
|
||||||
|
c.SetSessionData(&SessionData{
|
||||||
|
ExpireTime: timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapActionResponse(affected bool, e ...error) *Response {
|
||||||
|
if len(e) != 0 && e[0] != nil {
|
||||||
|
return &Response{Status: "error", Msg: e[0].Error()}
|
||||||
|
} else if affected {
|
||||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||||
} else {
|
} else {
|
||||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||||
@@ -157,3 +248,14 @@ func wrapErrorResponse(err error) *Response {
|
|||||||
return &Response{Status: "error", Msg: err.Error()}
|
return &Response{Status: "error", Msg: err.Error()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) Finish() {
|
||||||
|
if strings.HasPrefix(c.Ctx.Input.URL(), "/api") {
|
||||||
|
startTime := c.Ctx.Input.GetData("startTime")
|
||||||
|
if startTime != nil {
|
||||||
|
latency := time.Since(startTime.(time.Time)).Milliseconds()
|
||||||
|
object.ApiLatency.WithLabelValues(c.Ctx.Input.URL(), c.Ctx.Input.Method()).Observe(float64(latency))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Controller.Finish()
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ const (
|
|||||||
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func queryUnescape(service string) string {
|
||||||
|
s, _ := url.QueryUnescape(service)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RootController) CasValidate() {
|
func (c *RootController) CasValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@@ -60,19 +65,25 @@ func (c *RootController) CasServiceValidate() {
|
|||||||
if !strings.HasPrefix(ticket, "ST") {
|
if !strings.HasPrefix(ticket, "ST") {
|
||||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
}
|
}
|
||||||
c.CasP3ServiceAndProxyValidate()
|
c.CasP3ProxyValidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RootController) CasProxyValidate() {
|
func (c *RootController) CasProxyValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
// https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-Specification.html#26-proxyvalidate-cas-20
|
||||||
format := c.Input().Get("format")
|
// "/proxyValidate" should accept both service tickets and proxy tickets.
|
||||||
if !strings.HasPrefix(ticket, "PT") {
|
c.CasP3ProxyValidate()
|
||||||
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
|
||||||
}
|
|
||||||
c.CasP3ServiceAndProxyValidate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RootController) CasP3ServiceAndProxyValidate() {
|
func (c *RootController) CasP3ServiceValidate() {
|
||||||
|
ticket := c.Input().Get("ticket")
|
||||||
|
format := c.Input().Get("format")
|
||||||
|
if !strings.HasPrefix(ticket, "ST") {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
|
||||||
|
}
|
||||||
|
c.CasP3ProxyValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RootController) CasP3ProxyValidate() {
|
||||||
ticket := c.Input().Get("ticket")
|
ticket := c.Input().Get("ticket")
|
||||||
format := c.Input().Get("format")
|
format := c.Input().Get("format")
|
||||||
service := c.Input().Get("service")
|
service := c.Input().Get("service")
|
||||||
@@ -91,7 +102,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
|||||||
// find the token
|
// find the token
|
||||||
if ok {
|
if ok {
|
||||||
// check whether service is the one for which we previously issued token
|
// check whether service is the one for which we previously issued token
|
||||||
if strings.HasPrefix(service, issuedService) {
|
if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
|
||||||
serviceResponse.Success = response
|
serviceResponse.Success = response
|
||||||
} else {
|
} else {
|
||||||
// service not match
|
// service not match
|
||||||
@@ -110,15 +121,17 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
|
|||||||
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
pgtiou := serviceResponse.Success.ProxyGrantingTicket
|
||||||
// todo: check whether it is https
|
// todo: check whether it is https
|
||||||
pgtUrlObj, err := url.Parse(pgtUrl)
|
pgtUrlObj, err := url.Parse(pgtUrl)
|
||||||
|
if err != nil {
|
||||||
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if pgtUrlObj.Scheme != "https" {
|
if pgtUrlObj.Scheme != "https" {
|
||||||
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a request to pgturl passing pgt and pgtiou
|
// make a request to pgturl passing pgt and pgtiou
|
||||||
if err != nil {
|
|
||||||
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
param := pgtUrlObj.Query()
|
param := pgtUrlObj.Query()
|
||||||
param.Add("pgtId", pgt)
|
param.Add("pgtId", pgt)
|
||||||
param.Add("pgtIou", pgtiou)
|
param.Add("pgtIou", pgtiou)
|
||||||
@@ -258,7 +271,6 @@ func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format stri
|
|||||||
Message: msg,
|
Message: msg,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "json" {
|
if format == "json" {
|
||||||
c.Data["json"] = serviceResponse
|
c.Data["json"] = serviceResponse
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
// Copyright 2022 The Casdoor Authors. 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 controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
|
||||||
"github.com/casdoor/casdoor/object"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *ApiController) GetCasbinAdapters() {
|
|
||||||
owner := c.Input().Get("owner")
|
|
||||||
limit := c.Input().Get("pageSize")
|
|
||||||
page := c.Input().Get("p")
|
|
||||||
field := c.Input().Get("field")
|
|
||||||
value := c.Input().Get("value")
|
|
||||||
sortField := c.Input().Get("sortField")
|
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
|
||||||
if limit == "" || page == "" {
|
|
||||||
adapters := object.GetCasbinAdapters(owner)
|
|
||||||
c.ResponseOk(adapters)
|
|
||||||
} else {
|
|
||||||
limit := util.ParseInt(limit)
|
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
|
|
||||||
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
|
||||||
c.ResponseOk(adapters, paginator.Nums())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) GetCasbinAdapter() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
adapter := object.GetCasbinAdapter(id)
|
|
||||||
c.ResponseOk(adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) UpdateCasbinAdapter() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
|
|
||||||
var casbinAdapter object.CasbinAdapter
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateCasbinAdapter(id, &casbinAdapter))
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) AddCasbinAdapter() {
|
|
||||||
var casbinAdapter object.CasbinAdapter
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddCasbinAdapter(&casbinAdapter))
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) DeleteCasbinAdapter() {
|
|
||||||
var casbinAdapter object.CasbinAdapter
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.DeleteCasbinAdapter(&casbinAdapter))
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) SyncPolicies() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
adapter := object.GetCasbinAdapter(id)
|
|
||||||
|
|
||||||
policies, err := object.SyncPolicies(adapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ResponseOk(policies)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) UpdatePolicy() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
adapter := object.GetCasbinAdapter(id)
|
|
||||||
var policies []xormadapter.CasbinRule
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
affected, err := object.UpdatePolicy(util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]), adapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data["json"] = wrapActionResponse(affected)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) AddPolicy() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
adapter := object.GetCasbinAdapter(id)
|
|
||||||
var policy xormadapter.CasbinRule
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
affected, err := object.AddPolicy(util.CasbinToSlice(policy), adapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data["json"] = wrapActionResponse(affected)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiController) RemovePolicy() {
|
|
||||||
id := c.Input().Get("id")
|
|
||||||
adapter := object.GetCasbinAdapter(id)
|
|
||||||
var policy xormadapter.CasbinRule
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
affected, err := object.RemovePolicy(util.CasbinToSlice(policy), adapter)
|
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data["json"] = wrapActionResponse(affected)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
353
controllers/casbin_api.go
Normal file
353
controllers/casbin_api.go
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enforce
|
||||||
|
// @Title Enforce
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description Call Casbin Enforce API
|
||||||
|
// @Param body body []string true "Casbin request"
|
||||||
|
// @Param permissionId query string false "permission id"
|
||||||
|
// @Param modelId query string false "model id"
|
||||||
|
// @Param resourceId query string false "resource id"
|
||||||
|
// @Param owner query string false "owner"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /enforce [post]
|
||||||
|
func (c *ApiController) Enforce() {
|
||||||
|
permissionId := c.Input().Get("permissionId")
|
||||||
|
modelId := c.Input().Get("modelId")
|
||||||
|
resourceId := c.Input().Get("resourceId")
|
||||||
|
enforcerId := c.Input().Get("enforcerId")
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
|
params := []string{permissionId, modelId, resourceId, enforcerId, owner}
|
||||||
|
nonEmpty := 0
|
||||||
|
for _, param := range params {
|
||||||
|
if param != "" {
|
||||||
|
nonEmpty++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonEmpty > 1 {
|
||||||
|
c.ResponseError("Only one of the parameters (permissionId, modelId, resourceId, enforcerId, owner) should be provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Ctx.Input.RequestBody) == 0 {
|
||||||
|
c.ResponseError("The request body should not be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var request []string
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enforcerId != "" {
|
||||||
|
enforcer, err := object.GetInitializedEnforcer(enforcerId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
|
||||||
|
// type transformation
|
||||||
|
interfaceRequest := util.StringToInterfaceArray(request)
|
||||||
|
|
||||||
|
enforceResult, err := enforcer.Enforce(interfaceRequest...)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if permissionId != "" {
|
||||||
|
permission, err := object.GetPermission(permissionId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if permission == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
|
||||||
|
enforceResult, err := object.Enforce(permission, request)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions := []*object.Permission{}
|
||||||
|
if modelId != "" {
|
||||||
|
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||||
|
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if resourceId != "" {
|
||||||
|
permissions, err = object.GetPermissionsByResource(resourceId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if owner != "" {
|
||||||
|
permissions, err = object.GetPermissions(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||||
|
for key, permissionIds := range listPermissionIdMap {
|
||||||
|
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchEnforce
|
||||||
|
// @Title BatchEnforce
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description Call Casbin BatchEnforce API
|
||||||
|
// @Param body body []string true "array of casbin requests"
|
||||||
|
// @Param permissionId query string false "permission id"
|
||||||
|
// @Param modelId query string false "model id"
|
||||||
|
// @Param owner query string false "owner"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /batch-enforce [post]
|
||||||
|
func (c *ApiController) BatchEnforce() {
|
||||||
|
permissionId := c.Input().Get("permissionId")
|
||||||
|
modelId := c.Input().Get("modelId")
|
||||||
|
enforcerId := c.Input().Get("enforcerId")
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
|
params := []string{permissionId, modelId, enforcerId, owner}
|
||||||
|
nonEmpty := 0
|
||||||
|
for _, param := range params {
|
||||||
|
if param != "" {
|
||||||
|
nonEmpty++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonEmpty > 1 {
|
||||||
|
c.ResponseError("Only one of the parameters (permissionId, modelId, enforcerId, owner) should be provided")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var requests [][]string
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enforcerId != "" {
|
||||||
|
enforcer, err := object.GetInitializedEnforcer(enforcerId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := [][]bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
|
||||||
|
// type transformation
|
||||||
|
interfaceRequests := util.StringToInterfaceArray2d(requests)
|
||||||
|
|
||||||
|
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if permissionId != "" {
|
||||||
|
permission, err := object.GetPermission(permissionId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if permission == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := [][]bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
|
||||||
|
enforceResult, err := object.BatchEnforce(permission, requests)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions := []*object.Permission{}
|
||||||
|
if modelId != "" {
|
||||||
|
owner, modelName := util.GetOwnerAndNameFromId(modelId)
|
||||||
|
permissions, err = object.GetPermissionsByModel(owner, modelName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if owner != "" {
|
||||||
|
permissions, err = object.GetPermissions(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := [][]bool{}
|
||||||
|
keyRes := []string{}
|
||||||
|
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||||
|
for _, permissionIds := range listPermissionIdMap {
|
||||||
|
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, enforceResult)
|
||||||
|
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(res, keyRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetAllObjects() {
|
||||||
|
userId := c.Input().Get("userId")
|
||||||
|
if userId == "" {
|
||||||
|
userId = c.GetSessionUsername()
|
||||||
|
if userId == "" {
|
||||||
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objects, err := object.GetAllObjects(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetAllActions() {
|
||||||
|
userId := c.Input().Get("userId")
|
||||||
|
if userId == "" {
|
||||||
|
userId = c.GetSessionUsername()
|
||||||
|
if userId == "" {
|
||||||
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions, err := object.GetAllActions(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetAllRoles() {
|
||||||
|
userId := c.Input().Get("userId")
|
||||||
|
if userId == "" {
|
||||||
|
userId = c.GetSessionUsername()
|
||||||
|
if userId == "" {
|
||||||
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := object.GetAllRoles(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(roles)
|
||||||
|
}
|
||||||
247
controllers/casbin_cli_api.go
Normal file
247
controllers/casbin_cli_api.go
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CLIVersionInfo struct {
|
||||||
|
Version string
|
||||||
|
BinaryPath string
|
||||||
|
BinaryTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cliVersionCache = make(map[string]*CLIVersionInfo)
|
||||||
|
cliVersionMutex sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// getCLIVersion
|
||||||
|
// @Title getCLIVersion
|
||||||
|
// @Description Get CLI version with cache mechanism
|
||||||
|
// @Param language string The language of CLI (go/java/rust etc.)
|
||||||
|
// @Return string The version string of CLI
|
||||||
|
// @Return error Error if CLI execution fails
|
||||||
|
func getCLIVersion(language string) (string, error) {
|
||||||
|
binaryName := fmt.Sprintf("casbin-%s-cli", language)
|
||||||
|
|
||||||
|
binaryPath, err := exec.LookPath(binaryName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("executable file not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(binaryPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get binary info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cliVersionMutex.RLock()
|
||||||
|
if info, exists := cliVersionCache[language]; exists {
|
||||||
|
if info.BinaryPath == binaryPath && info.BinaryTime == fileInfo.ModTime() {
|
||||||
|
cliVersionMutex.RUnlock()
|
||||||
|
return info.Version, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cliVersionMutex.RUnlock()
|
||||||
|
|
||||||
|
cmd := exec.Command(binaryName, "--version")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get CLI version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version := strings.TrimSpace(string(output))
|
||||||
|
|
||||||
|
cliVersionMutex.Lock()
|
||||||
|
cliVersionCache[language] = &CLIVersionInfo{
|
||||||
|
Version: version,
|
||||||
|
BinaryPath: binaryPath,
|
||||||
|
BinaryTime: fileInfo.ModTime(),
|
||||||
|
}
|
||||||
|
cliVersionMutex.Unlock()
|
||||||
|
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func processArgsToTempFiles(args []string) ([]string, []string, error) {
|
||||||
|
tempFiles := []string{}
|
||||||
|
newArgs := []string{}
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
if (args[i] == "-m" || args[i] == "-p") && i+1 < len(args) {
|
||||||
|
pattern := fmt.Sprintf("casbin_temp_%s_*.conf", args[i])
|
||||||
|
tempFile, err := os.CreateTemp("", pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tempFile.WriteString(args[i+1])
|
||||||
|
if err != nil {
|
||||||
|
tempFile.Close()
|
||||||
|
return nil, nil, fmt.Errorf("failed to write to temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile.Close()
|
||||||
|
tempFiles = append(tempFiles, tempFile.Name())
|
||||||
|
newArgs = append(newArgs, args[i], tempFile.Name())
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
newArgs = append(newArgs, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempFiles, newArgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCasbinCommand
|
||||||
|
// @Title RunCasbinCommand
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description Call Casbin CLI commands
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /run-casbin-command [get]
|
||||||
|
func (c *ApiController) RunCasbinCommand() {
|
||||||
|
if err := validateIdentifier(c); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
language := c.Input().Get("language")
|
||||||
|
argString := c.Input().Get("args")
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = "go"
|
||||||
|
}
|
||||||
|
// use "casbin-go-cli" by default, can be also "casbin-java-cli", "casbin-node-cli", etc.
|
||||||
|
// the pre-built binary of "casbin-go-cli" can be found at: https://github.com/casbin/casbin-go-cli/releases
|
||||||
|
binaryName := fmt.Sprintf("casbin-%s-cli", language)
|
||||||
|
|
||||||
|
_, err := exec.LookPath(binaryName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("executable file: %s not found in PATH", binaryName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RBAC model & policy example:
|
||||||
|
// https://door.casdoor.com/api/run-casbin-command?language=go&args=["enforce", "-m", "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) %26%26 r.obj == p.obj %26%26 r.act == p.act", "-p", "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin", "alice", "data1", "read"]
|
||||||
|
// Casbin CLI usage:
|
||||||
|
// https://github.com/jcasbin/casbin-java-cli?tab=readme-ov-file#get-started
|
||||||
|
var args []string
|
||||||
|
err = json.Unmarshal([]byte(argString), &args)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 && args[0] == "--version" {
|
||||||
|
version, err := getCLIVersion(language)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFiles, processedArgs, err := processArgsToTempFiles(args)
|
||||||
|
defer func() {
|
||||||
|
for _, file := range tempFiles {
|
||||||
|
os.Remove(file)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
command := exec.Command(binaryName, processedArgs...)
|
||||||
|
outputBytes, err := command.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
errorString := err.Error()
|
||||||
|
if outputBytes != nil {
|
||||||
|
output := string(outputBytes)
|
||||||
|
errorString = fmt.Sprintf("%s, error: %s", output, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseError(errorString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := string(outputBytes)
|
||||||
|
output = strings.TrimSuffix(output, "\n")
|
||||||
|
c.ResponseOk(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateIdentifier
|
||||||
|
// @Title validateIdentifier
|
||||||
|
// @Description Validate the request hash and timestamp
|
||||||
|
// @Param hash string The SHA-256 hash string
|
||||||
|
// @Return error Returns error if validation fails, nil if successful
|
||||||
|
func validateIdentifier(c *ApiController) error {
|
||||||
|
language := c.Input().Get("language")
|
||||||
|
args := c.Input().Get("args")
|
||||||
|
hash := c.Input().Get("m")
|
||||||
|
timestamp := c.Input().Get("t")
|
||||||
|
|
||||||
|
if hash == "" || timestamp == "" || language == "" || args == "" {
|
||||||
|
return fmt.Errorf("invalid identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
requestTime, err := time.Parse(time.RFC3339, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid identifier")
|
||||||
|
}
|
||||||
|
timeDiff := time.Since(requestTime)
|
||||||
|
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
|
||||||
|
return fmt.Errorf("invalid identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
"language": language,
|
||||||
|
"args": args,
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for k := range params {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var paramParts []string
|
||||||
|
for _, k := range keys {
|
||||||
|
paramParts = append(paramParts, fmt.Sprintf("%s=%s", k, params[k]))
|
||||||
|
}
|
||||||
|
paramString := strings.Join(paramParts, "&")
|
||||||
|
|
||||||
|
version := "casbin-editor-v1"
|
||||||
|
rawString := fmt.Sprintf("%s|%s|%s", version, timestamp, paramString)
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(rawString))
|
||||||
|
|
||||||
|
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
|
||||||
|
if calculatedHash != strings.ToLower(hash) {
|
||||||
|
return fmt.Errorf("invalid identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -37,13 +37,71 @@ func (c *ApiController) GetCerts() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedCerts(object.GetCerts(owner))
|
certs, err := object.GetMaskedCerts(object.GetCerts(owner))
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(certs)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
|
count, err := object.GetCertCount(owner, field, value)
|
||||||
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(certs, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalCerts
|
||||||
|
// @Title GetGlobalCerts
|
||||||
|
// @Tag Cert API
|
||||||
|
// @Description get global certs
|
||||||
|
// @Success 200 {array} object.Cert The Response object
|
||||||
|
// @router /get-global-certs [get]
|
||||||
|
func (c *ApiController) GetGlobalCerts() {
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
certs, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(certs)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetGlobalCertsCount(field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(certs, paginator.Nums())
|
c.ResponseOk(certs, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,9 +115,13 @@ func (c *ApiController) GetCerts() {
|
|||||||
// @router /get-cert [get]
|
// @router /get-cert [get]
|
||||||
func (c *ApiController) GetCert() {
|
func (c *ApiController) GetCert() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
cert, err := object.GetCert(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
|
c.ResponseOk(object.GetMaskedCert(cert))
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCert
|
// UpdateCert
|
||||||
|
|||||||
541
controllers/cli_downloader.go
Normal file
541
controllers/cli_downloader.go
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego"
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
javaCliRepo = "https://api.github.com/repos/jcasbin/casbin-java-cli/releases/latest"
|
||||||
|
goCliRepo = "https://api.github.com/repos/casbin/casbin-go-cli/releases/latest"
|
||||||
|
rustCliRepo = "https://api.github.com/repos/casbin-rs/casbin-rust-cli/releases/latest"
|
||||||
|
pythonCliRepo = "https://api.github.com/repos/casbin/casbin-python-cli/releases/latest"
|
||||||
|
dotnetCliRepo = "https://api.github.com/repos/casbin-net/casbin-dotnet-cli/releases/latest"
|
||||||
|
downloadFolder = "bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReleaseInfo struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Assets []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
URL string `json:"browser_download_url"`
|
||||||
|
} `json:"assets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title getBinaryNames
|
||||||
|
// @Description Get binary names for different platforms and architectures
|
||||||
|
// @Success 200 {map[string]string} map[string]string "Binary names map"
|
||||||
|
func getBinaryNames() map[string]string {
|
||||||
|
const (
|
||||||
|
golang = "go"
|
||||||
|
java = "java"
|
||||||
|
rust = "rust"
|
||||||
|
python = "python"
|
||||||
|
dotnet = "dotnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
arch := runtime.GOARCH
|
||||||
|
archMap := map[string]struct{ goArch, rustArch string }{
|
||||||
|
"amd64": {"x86_64", "x86_64"},
|
||||||
|
"arm64": {"arm64", "aarch64"},
|
||||||
|
}
|
||||||
|
|
||||||
|
archNames, ok := archMap[arch]
|
||||||
|
if !ok {
|
||||||
|
archNames = struct{ goArch, rustArch string }{arch, arch}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
return map[string]string{
|
||||||
|
golang: fmt.Sprintf("casbin-go-cli_Windows_%s.zip", archNames.goArch),
|
||||||
|
java: "casbin-java-cli.jar",
|
||||||
|
rust: fmt.Sprintf("casbin-rust-cli-%s-pc-windows-gnu", archNames.rustArch),
|
||||||
|
python: fmt.Sprintf("casbin-python-cli-windows-%s.exe", archNames.goArch),
|
||||||
|
dotnet: fmt.Sprintf("casbin-dotnet-cli-windows-%s.exe", archNames.goArch),
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
return map[string]string{
|
||||||
|
golang: fmt.Sprintf("casbin-go-cli_Darwin_%s.tar.gz", archNames.goArch),
|
||||||
|
java: "casbin-java-cli.jar",
|
||||||
|
rust: fmt.Sprintf("casbin-rust-cli-%s-apple-darwin", archNames.rustArch),
|
||||||
|
python: fmt.Sprintf("casbin-python-cli-darwin-%s", archNames.goArch),
|
||||||
|
dotnet: fmt.Sprintf("casbin-dotnet-cli-darwin-%s", archNames.goArch),
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
return map[string]string{
|
||||||
|
golang: fmt.Sprintf("casbin-go-cli_Linux_%s.tar.gz", archNames.goArch),
|
||||||
|
java: "casbin-java-cli.jar",
|
||||||
|
rust: fmt.Sprintf("casbin-rust-cli-%s-unknown-linux-gnu", archNames.rustArch),
|
||||||
|
python: fmt.Sprintf("casbin-python-cli-linux-%s", archNames.goArch),
|
||||||
|
dotnet: fmt.Sprintf("casbin-dotnet-cli-linux-%s", archNames.goArch),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title getFinalBinaryName
|
||||||
|
// @Description Get final binary name for specific language
|
||||||
|
// @Param lang string true "Language type (go/java/rust)"
|
||||||
|
// @Success 200 {string} string "Final binary name"
|
||||||
|
func getFinalBinaryName(lang string) string {
|
||||||
|
switch lang {
|
||||||
|
case "go":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "casbin-go-cli.exe"
|
||||||
|
}
|
||||||
|
return "casbin-go-cli"
|
||||||
|
case "java":
|
||||||
|
return "casbin-java-cli.jar"
|
||||||
|
case "rust":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "casbin-rust-cli.exe"
|
||||||
|
}
|
||||||
|
return "casbin-rust-cli"
|
||||||
|
case "python":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "casbin-python-cli.exe"
|
||||||
|
}
|
||||||
|
return "casbin-python-cli"
|
||||||
|
case "dotnet":
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "casbin-dotnet-cli.exe"
|
||||||
|
}
|
||||||
|
return "casbin-dotnet-cli"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title getLatestCLIURL
|
||||||
|
// @Description Get latest CLI download URL from GitHub
|
||||||
|
// @Param repoURL string true "GitHub repository URL"
|
||||||
|
// @Param language string true "Language type"
|
||||||
|
// @Success 200 {string} string "Download URL and version"
|
||||||
|
func getLatestCLIURL(repoURL string, language string) (string, string, error) {
|
||||||
|
client := proxy.GetHttpClient(repoURL)
|
||||||
|
resp, err := client.Get(repoURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to fetch release info: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var release ReleaseInfo
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryNames := getBinaryNames()
|
||||||
|
if binaryNames == nil {
|
||||||
|
return "", "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
binaryName := binaryNames[language]
|
||||||
|
for _, asset := range release.Assets {
|
||||||
|
if asset.Name == binaryName {
|
||||||
|
return asset.URL, release.TagName, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", "", fmt.Errorf("no suitable binary found for OS: %s, language: %s", runtime.GOOS, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title extractGoCliFile
|
||||||
|
// @Description Extract the Go CLI file
|
||||||
|
// @Param filePath string true "The file path"
|
||||||
|
// @Success 200 {string} string "The extracted file path"
|
||||||
|
// @router /extractGoCliFile [post]
|
||||||
|
func extractGoCliFile(filePath string) error {
|
||||||
|
tempDir := filepath.Join(downloadFolder, "temp")
|
||||||
|
if err := os.MkdirAll(tempDir, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if err := unzipFile(filePath, tempDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := untarFile(filePath, tempDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
execName := "casbin-go-cli"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
execName += ".exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
var execPath string
|
||||||
|
err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.Name() == execName {
|
||||||
|
execPath = path
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPath := filepath.Join(downloadFolder, execName)
|
||||||
|
if err := os.Rename(execPath, finalPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title unzipFile
|
||||||
|
// @Description Unzip the file
|
||||||
|
// @Param zipPath string true "The zip file path"
|
||||||
|
// @Param destDir string true "The destination directory"
|
||||||
|
// @Success 200 {string} string "The extracted file path"
|
||||||
|
// @router /unzipFile [post]
|
||||||
|
func unzipFile(zipPath, destDir string) error {
|
||||||
|
r, err := zip.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
for _, f := range r.File {
|
||||||
|
fpath := filepath.Join(destDir, f.Name)
|
||||||
|
|
||||||
|
if f.FileInfo().IsDir() {
|
||||||
|
os.MkdirAll(fpath, os.ModePerm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(outFile, rc)
|
||||||
|
outFile.Close()
|
||||||
|
rc.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title untarFile
|
||||||
|
// @Description Untar the file
|
||||||
|
// @Param tarPath string true "The tar file path"
|
||||||
|
// @Param destDir string true "The destination directory"
|
||||||
|
// @Success 200 {string} string "The extracted file path"
|
||||||
|
// @router /untarFile [post]
|
||||||
|
func untarFile(tarPath, destDir string) error {
|
||||||
|
file, err := os.Open(tarPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
gzr, err := gzip.NewReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer gzr.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gzr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(destDir, header.Name)
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
outFile, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(outFile, tr); err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title createJavaCliWrapper
|
||||||
|
// @Description Create the Java CLI wrapper
|
||||||
|
// @Param binPath string true "The binary path"
|
||||||
|
// @Success 200 {string} string "The created file path"
|
||||||
|
// @router /createJavaCliWrapper [post]
|
||||||
|
func createJavaCliWrapper(binPath string) error {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// Create a Windows CMD file
|
||||||
|
cmdPath := filepath.Join(binPath, "casbin-java-cli.cmd")
|
||||||
|
cmdContent := fmt.Sprintf(`@echo off
|
||||||
|
java -jar "%s\casbin-java-cli.jar" %%*`, binPath)
|
||||||
|
|
||||||
|
err := os.WriteFile(cmdPath, []byte(cmdContent), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create Unix shell script
|
||||||
|
shPath := filepath.Join(binPath, "casbin-java-cli")
|
||||||
|
shContent := fmt.Sprintf(`#!/bin/sh
|
||||||
|
java -jar "%s/casbin-java-cli.jar" "$@"`, binPath)
|
||||||
|
|
||||||
|
err := os.WriteFile(shPath, []byte(shContent), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title downloadCLI
|
||||||
|
// @Description Download and setup CLI tools
|
||||||
|
// @Success 200 {error} error "Error if any"
|
||||||
|
func downloadCLI() error {
|
||||||
|
pathEnv := os.Getenv("PATH")
|
||||||
|
binPath, err := filepath.Abs(downloadFolder)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get absolute path to download directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(pathEnv, binPath) {
|
||||||
|
newPath := fmt.Sprintf("%s%s%s", binPath, string(os.PathListSeparator), pathEnv)
|
||||||
|
if err := os.Setenv("PATH", newPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to update PATH environment variable: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(downloadFolder, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create download directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := map[string]string{
|
||||||
|
"java": javaCliRepo,
|
||||||
|
"go": goCliRepo,
|
||||||
|
"rust": rustCliRepo,
|
||||||
|
"python": pythonCliRepo,
|
||||||
|
"dotnet": dotnetCliRepo,
|
||||||
|
}
|
||||||
|
|
||||||
|
for lang, repo := range repos {
|
||||||
|
cliURL, version, err := getLatestCLIURL(repo, lang)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to get %s CLI URL: %v\n", lang, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
originalPath := filepath.Join(downloadFolder, getBinaryNames()[lang])
|
||||||
|
fmt.Printf("downloading %s CLI: %s\n", lang, cliURL)
|
||||||
|
|
||||||
|
client := proxy.GetHttpClient(cliURL)
|
||||||
|
resp, err := client.Get(cliURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(originalPath), 0o755); err != nil {
|
||||||
|
fmt.Printf("failed to create directory for %s CLI: %v\n", lang, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile := originalPath + ".tmp"
|
||||||
|
out, err := os.Create(tmpFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to create or write %s CLI: %v\n", lang, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
out.Close()
|
||||||
|
os.Remove(tmpFile)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err = io.Copy(out, resp.Body); err != nil ||
|
||||||
|
out.Close() != nil ||
|
||||||
|
os.Rename(tmpFile, originalPath) != nil {
|
||||||
|
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if lang == "go" {
|
||||||
|
if err := extractGoCliFile(originalPath); err != nil {
|
||||||
|
fmt.Printf("failed to extract Go CLI: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
|
||||||
|
if err := os.Rename(originalPath, finalPath); err != nil {
|
||||||
|
fmt.Printf("failed to rename %s CLI: %v\n", lang, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
execPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
|
||||||
|
if err := os.Chmod(execPath, 0o755); err != nil {
|
||||||
|
fmt.Printf("failed to set %s CLI execution permission: %v\n", lang, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("downloaded %s CLI version: %s\n", lang, version)
|
||||||
|
|
||||||
|
if lang == "java" {
|
||||||
|
if err := createJavaCliWrapper(binPath); err != nil {
|
||||||
|
fmt.Printf("failed to create Java CLI wrapper: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title RefreshEngines
|
||||||
|
// @Tag CLI API
|
||||||
|
// @Description Refresh all CLI engines
|
||||||
|
// @Param m query string true "Hash for request validation"
|
||||||
|
// @Param t query string true "Timestamp for request validation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /refresh-engines [post]
|
||||||
|
func (c *ApiController) RefreshEngines() {
|
||||||
|
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
|
||||||
|
c.ResponseError("refresh engines is only available in demo mode")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := c.Input().Get("m")
|
||||||
|
timestamp := c.Input().Get("t")
|
||||||
|
|
||||||
|
if hash == "" || timestamp == "" {
|
||||||
|
c.ResponseError("invalid identifier")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestTime, err := time.Parse(time.RFC3339, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError("invalid identifier")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDiff := time.Since(requestTime)
|
||||||
|
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
|
||||||
|
c.ResponseError("invalid identifier")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version := "casbin-editor-v1"
|
||||||
|
rawString := fmt.Sprintf("%s|%s", version, timestamp)
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(rawString))
|
||||||
|
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
|
||||||
|
|
||||||
|
if calculatedHash != strings.ToLower(hash) {
|
||||||
|
c.ResponseError("invalid identifier")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = downloadCLI()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("failed to refresh engines: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(map[string]string{
|
||||||
|
"status": "success",
|
||||||
|
"message": "CLI engines updated successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title ScheduleCLIUpdater
|
||||||
|
// @Description Start periodic CLI update scheduler
|
||||||
|
func ScheduleCLIUpdater() {
|
||||||
|
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
err := downloadCLI()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to update CLI: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("CLI updated successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DownloadCLI
|
||||||
|
// @Description Download the CLI
|
||||||
|
// @Success 200 {string} string "The downloaded file path"
|
||||||
|
// @router /downloadCLI [post]
|
||||||
|
func DownloadCLI() error {
|
||||||
|
return downloadCLI()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title InitCLIDownloader
|
||||||
|
// @Description Initialize CLI downloader and start update scheduler
|
||||||
|
func InitCLIDownloader() {
|
||||||
|
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.SafeGoroutine(func() {
|
||||||
|
err := DownloadCLI()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to initialize CLI downloader: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScheduleCLIUpdater()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -16,63 +16,290 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *ApiController) Enforce() {
|
// GetEnforcers
|
||||||
var permissionRule object.PermissionRule
|
// @Title GetEnforcers
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
|
// @Tag Enforcer API
|
||||||
|
// @Description get enforcers
|
||||||
|
// @Param owner query string true "The owner of enforcers"
|
||||||
|
// @Success 200 {array} object.Enforcer
|
||||||
|
// @router /get-enforcers [get]
|
||||||
|
func (c *ApiController) GetEnforcers() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
enforcers, err := object.GetEnforcers(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(enforcers)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetEnforcerCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
enforcers, err := object.GetPaginationEnforcers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(enforcers, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnforcer
|
||||||
|
// @Title GetEnforcer
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description get enforcer
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Success 200 {object} object.Enforcer
|
||||||
|
// @router /get-enforcer [get]
|
||||||
|
func (c *ApiController) GetEnforcer() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
loadModelCfg := c.Input().Get("loadModelCfg")
|
||||||
|
|
||||||
|
enforcer, err := object.GetEnforcer(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.Enforce(&permissionRule)
|
if loadModelCfg == "true" && enforcer.Model != "" {
|
||||||
c.ServeJSON()
|
err := enforcer.LoadModelCfg()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(enforcer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) BatchEnforce() {
|
// UpdateEnforcer
|
||||||
var permissionRules []object.PermissionRule
|
// @Title UpdateEnforcer
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
|
// @Tag Enforcer API
|
||||||
|
// @Description update enforcer
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param enforcer body object true "The enforcer object"
|
||||||
|
// @Success 200 {object} object.Enforcer
|
||||||
|
// @router /update-enforcer [post]
|
||||||
|
func (c *ApiController) UpdateEnforcer() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
enforcer := object.Enforcer{}
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.BatchEnforce(permissionRules)
|
c.Data["json"] = wrapActionResponse(object.UpdateEnforcer(id, &enforcer))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetAllObjects() {
|
// AddEnforcer
|
||||||
userId := c.GetSessionUsername()
|
// @Title AddEnforcer
|
||||||
if userId == "" {
|
// @Tag Enforcer API
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
// @Description add enforcer
|
||||||
|
// @Param enforcer body object true "The enforcer object"
|
||||||
|
// @Success 200 {object} object.Enforcer
|
||||||
|
// @router /add-enforcer [post]
|
||||||
|
func (c *ApiController) AddEnforcer() {
|
||||||
|
enforcer := object.Enforcer{}
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetAllObjects(userId)
|
c.Data["json"] = wrapActionResponse(object.AddEnforcer(&enforcer))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetAllActions() {
|
// DeleteEnforcer
|
||||||
userId := c.GetSessionUsername()
|
// @Title DeleteEnforcer
|
||||||
if userId == "" {
|
// @Tag Enforcer API
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
// @Description delete enforcer
|
||||||
|
// @Param body body object.Enforcer true "The enforcer object"
|
||||||
|
// @Success 200 {object} object.Enforcer
|
||||||
|
// @router /delete-enforcer [post]
|
||||||
|
func (c *ApiController) DeleteEnforcer() {
|
||||||
|
var enforcer object.Enforcer
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetAllActions(userId)
|
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetAllRoles() {
|
// GetPolicies
|
||||||
userId := c.GetSessionUsername()
|
// @Title GetPolicies
|
||||||
if userId == "" {
|
// @Tag Enforcer API
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
// @Description get policies
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param adapterId query string false "The adapter id"
|
||||||
|
// @Success 200 {array} xormadapter.CasbinRule
|
||||||
|
// @router /get-policies [get]
|
||||||
|
func (c *ApiController) GetPolicies() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
adapterId := c.Input().Get("adapterId")
|
||||||
|
|
||||||
|
if adapterId != "" {
|
||||||
|
adapter, err := object.GetAdapter(adapterId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if adapter == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("enforcer:the adapter: %s is not found"), adapterId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = adapter.InitAdapter()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetAllRoles(userId)
|
policies, err := object.GetPolicies(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(policies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilteredPolicies
|
||||||
|
// @Title GetFilteredPolicies
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description get filtered policies with support for multiple filters via POST body
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param body body []object.Filter true "Array of filter objects for multiple filters"
|
||||||
|
// @Success 200 {array} xormadapter.CasbinRule
|
||||||
|
// @router /get-filtered-policies [post]
|
||||||
|
func (c *ApiController) GetFilteredPolicies() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var filters []object.Filter
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &filters)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredPolicies, err := object.GetFilteredPoliciesMulti(id, filters)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(filteredPolicies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePolicy
|
||||||
|
// @Title UpdatePolicy
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description update policy
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param body body []xormadapter.CasbinRule true "Array containing old and new policy"
|
||||||
|
// @Success 200 {object} Response
|
||||||
|
// @router /update-policy [post]
|
||||||
|
func (c *ApiController) UpdatePolicy() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var policies []xormadapter.CasbinRule
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UpdatePolicy(id, policies[0].Ptype, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPolicy
|
||||||
|
// @Title AddPolicy
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description add policy
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param body body xormadapter.CasbinRule true "The policy to add"
|
||||||
|
// @Success 200 {object} Response
|
||||||
|
// @router /add-policy [post]
|
||||||
|
func (c *ApiController) AddPolicy() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var policy xormadapter.CasbinRule
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.AddPolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePolicy
|
||||||
|
// @Title RemovePolicy
|
||||||
|
// @Tag Enforcer API
|
||||||
|
// @Description remove policy
|
||||||
|
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||||
|
// @Param body body xormadapter.CasbinRule true "The policy to remove"
|
||||||
|
// @Success 200 {object} Response
|
||||||
|
// @router /remove-policy [post]
|
||||||
|
func (c *ApiController) RemovePolicy() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var policy xormadapter.CasbinRule
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.RemovePolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
55
controllers/face.go
Normal file
55
controllers/face.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. 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.
|
||||||
|
|
||||||
|
// Casdoor will expose its providers as services to SDK
|
||||||
|
// We are going to implement those services as APIs here
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FaceIDSigninBegin
|
||||||
|
// @Title FaceIDSigninBegin
|
||||||
|
// @Tag Login API
|
||||||
|
// @Description FaceId Login Flow 1st stage
|
||||||
|
// @Param owner query string true "owner"
|
||||||
|
// @Param name query string true "name"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /faceid-signin-begin [get]
|
||||||
|
func (c *ApiController) FaceIDSigninBegin() {
|
||||||
|
userOwner := c.Input().Get("owner")
|
||||||
|
userName := c.Input().Get("name")
|
||||||
|
|
||||||
|
user, err := object.GetUserByFields(userOwner, userName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(user.FaceIds) == 0 {
|
||||||
|
c.ResponseError(c.T("check:Face data does not exist, cannot log in"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
35
controllers/get-dashboard.go
Normal file
35
controllers/get-dashboard.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import "github.com/casdoor/casdoor/object"
|
||||||
|
|
||||||
|
// GetDashboard
|
||||||
|
// @Title GetDashboard
|
||||||
|
// @Tag System API
|
||||||
|
// @Description get information of dashboard
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /get-dashboard [get]
|
||||||
|
func (c *ApiController) GetDashboard() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
|
data, err := object.GetDashboard(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(data)
|
||||||
|
}
|
||||||
187
controllers/group.go
Normal file
187
controllers/group.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGroups
|
||||||
|
// @Title GetGroups
|
||||||
|
// @Tag Group API
|
||||||
|
// @Description get groups
|
||||||
|
// @Param owner query string true "The owner of groups"
|
||||||
|
// @Success 200 {array} object.Group The Response object
|
||||||
|
// @router /get-groups [get]
|
||||||
|
func (c *ApiController) GetGroups() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
withTree := c.Input().Get("withTree")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
groups, err := object.GetGroups(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.ExtendGroupsWithUsers(groups)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if withTree == "true" {
|
||||||
|
c.ResponseOk(object.ConvertToTreeData(groups, owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(groups)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetGroupCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
groups, err := object.GetPaginationGroups(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groupsHaveChildrenMap, err := object.GetGroupsHaveChildrenMap(groups)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
_, ok := groupsHaveChildrenMap[group.GetId()]
|
||||||
|
if ok {
|
||||||
|
group.HaveChildren = true
|
||||||
|
}
|
||||||
|
|
||||||
|
parent, ok := groupsHaveChildrenMap[fmt.Sprintf("%s/%s", group.Owner, group.ParentId)]
|
||||||
|
if ok {
|
||||||
|
group.ParentName = parent.DisplayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.ExtendGroupsWithUsers(groups)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(groups, paginator.Nums())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroup
|
||||||
|
// @Title GetGroup
|
||||||
|
// @Tag Group API
|
||||||
|
// @Description get group
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the group"
|
||||||
|
// @Success 200 {object} object.Group The Response object
|
||||||
|
// @router /get-group [get]
|
||||||
|
func (c *ApiController) GetGroup() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
group, err := object.GetGroup(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.ExtendGroupWithUsers(group)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateGroup
|
||||||
|
// @Title UpdateGroup
|
||||||
|
// @Tag Group API
|
||||||
|
// @Description update group
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the group"
|
||||||
|
// @Param body body object.Group true "The details of the group"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-group [post]
|
||||||
|
func (c *ApiController) UpdateGroup() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var group object.Group
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateGroup(id, &group))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddGroup
|
||||||
|
// @Title AddGroup
|
||||||
|
// @Tag Group API
|
||||||
|
// @Description add group
|
||||||
|
// @Param body body object.Group true "The details of the group"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-group [post]
|
||||||
|
func (c *ApiController) AddGroup() {
|
||||||
|
var group object.Group
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddGroup(&group))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteGroup
|
||||||
|
// @Title DeleteGroup
|
||||||
|
// @Tag Group API
|
||||||
|
// @Description delete group
|
||||||
|
// @Param body body object.Group true "The details of the group"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-group [post]
|
||||||
|
func (c *ApiController) DeleteGroup() {
|
||||||
|
var group object.Group
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteGroup(&group))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
56
controllers/group_upload.go
Normal file
56
controllers/group_upload.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadGroups() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
defer os.Remove(path)
|
||||||
|
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UploadGroups(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Failed to import groups"))
|
||||||
|
}
|
||||||
|
}
|
||||||
262
controllers/invitation.go
Normal file
262
controllers/invitation.go
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetInvitations
|
||||||
|
// @Title GetInvitations
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description get invitations
|
||||||
|
// @Param owner query string true "The owner of invitations"
|
||||||
|
// @Success 200 {array} object.Invitation The Response object
|
||||||
|
// @router /get-invitations [get]
|
||||||
|
func (c *ApiController) GetInvitations() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
invitations, err := object.GetInvitations(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(invitations)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetInvitationCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
invitations, err := object.GetPaginationInvitations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(invitations, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvitation
|
||||||
|
// @Title GetInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description get invitation
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||||
|
// @Success 200 {object} object.Invitation The Response object
|
||||||
|
// @router /get-invitation [get]
|
||||||
|
func (c *ApiController) GetInvitation() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
invitation, err := object.GetInvitation(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(invitation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInvitationCodeInfo
|
||||||
|
// @Title GetInvitationCodeInfo
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description get invitation code information
|
||||||
|
// @Param code query string true "Invitation code"
|
||||||
|
// @Success 200 {object} object.Invitation The Response object
|
||||||
|
// @router /get-invitation-info [get]
|
||||||
|
func (c *ApiController) GetInvitationCodeInfo() {
|
||||||
|
code := c.Input().Get("code")
|
||||||
|
applicationId := c.Input().Get("applicationId")
|
||||||
|
|
||||||
|
application, err := object.GetApplication(applicationId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invitation, msg := object.GetInvitationByCode(code, application.Organization, c.GetAcceptLanguage())
|
||||||
|
if msg != "" {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedInvitation(invitation))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInvitation
|
||||||
|
// @Title UpdateInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description update invitation
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||||
|
// @Param body body object.Invitation true "The details of the invitation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-invitation [post]
|
||||||
|
func (c *ApiController) UpdateInvitation() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var invitation object.Invitation
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation, c.GetAcceptLanguage()))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInvitation
|
||||||
|
// @Title AddInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description add invitation
|
||||||
|
// @Param body body object.Invitation true "The details of the invitation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-invitation [post]
|
||||||
|
func (c *ApiController) AddInvitation() {
|
||||||
|
var invitation object.Invitation
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation, c.GetAcceptLanguage()))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteInvitation
|
||||||
|
// @Title DeleteInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description delete invitation
|
||||||
|
// @Param body body object.Invitation true "The details of the invitation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-invitation [post]
|
||||||
|
func (c *ApiController) DeleteInvitation() {
|
||||||
|
var invitation object.Invitation
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteInvitation(&invitation))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyInvitation
|
||||||
|
// @Title VerifyInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description verify invitation
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /verify-invitation [get]
|
||||||
|
func (c *ApiController) VerifyInvitation() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
payment, attachInfo, err := object.VerifyInvitation(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payment, attachInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendInvitation
|
||||||
|
// @Title VerifyInvitation
|
||||||
|
// @Tag Invitation API
|
||||||
|
// @Description verify invitation
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||||
|
// @Param body body []string true "The details of the invitation"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /send-invitation [post]
|
||||||
|
func (c *ApiController) SendInvitation() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var destinations []string
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &destinations)
|
||||||
|
|
||||||
|
if !c.IsAdmin() {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invitation, err := object.GetInvitation(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if invitation == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("invitation:Invitation %s does not exist"), id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, err := object.GetOrganization(fmt.Sprintf("admin/%s", invitation.Owner))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplicationByOrganizationName(invitation.Owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), invitation.Owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := application.GetEmailProvider("Invitation")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("verification:please add an Email provider to the \"Providers\" list for the application: %s"), invitation.Owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := provider.Metadata
|
||||||
|
|
||||||
|
content = strings.ReplaceAll(content, "%code", invitation.Code)
|
||||||
|
content = strings.ReplaceAll(content, "%link", invitation.GetInvitationLink(c.Ctx.Request.Host, application.Name))
|
||||||
|
|
||||||
|
err = object.SendEmail(provider, provider.Title, content, destinations, organization.DisplayName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
@@ -21,100 +21,101 @@ import (
|
|||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LdapServer struct {
|
|
||||||
Host string `json:"host"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Admin string `json:"admin"`
|
|
||||||
Passwd string `json:"passwd"`
|
|
||||||
BaseDn string `json:"baseDn"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LdapResp struct {
|
type LdapResp struct {
|
||||||
// Groups []LdapRespGroup `json:"groups"`
|
// Groups []LdapRespGroup `json:"groups"`
|
||||||
Users []object.LdapRespUser `json:"users"`
|
Users []object.LdapUser `json:"users"`
|
||||||
|
ExistUuids []string `json:"existUuids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//type LdapRespGroup struct {
|
// type LdapRespGroup struct {
|
||||||
// GroupId string
|
// GroupId string
|
||||||
// GroupName string
|
// GroupName string
|
||||||
//}
|
// }
|
||||||
|
|
||||||
type LdapSyncResp struct {
|
type LdapSyncResp struct {
|
||||||
Exist []object.LdapRespUser `json:"exist"`
|
Exist []object.LdapUser `json:"exist"`
|
||||||
Failed []object.LdapRespUser `json:"failed"`
|
Failed []object.LdapUser `json:"failed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdapUser
|
// GetLdapUsers
|
||||||
// @Tag Account API
|
|
||||||
// @Title GetLdapser
|
// @Title GetLdapser
|
||||||
// @router /get-ldap-user [post]
|
// @Tag Account API
|
||||||
func (c *ApiController) GetLdapUser() {
|
// @Description get ldap users
|
||||||
ldapServer := LdapServer{}
|
// Param id string true "id"
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
// @Success 200 {object} controllers.LdapResp The Response object
|
||||||
if err != nil || util.IsStringsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
// @router /get-ldap-users [get]
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
func (c *ApiController) GetLdapUsers() {
|
||||||
return
|
id := c.Input().Get("id")
|
||||||
}
|
|
||||||
|
|
||||||
var resp LdapResp
|
_, ldapId := util.GetOwnerAndNameFromId(id)
|
||||||
|
ldapServer, err := object.GetLdap(ldapId)
|
||||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
conn, err := ldapServer.GetLdapConn()
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||||
|
// if err != nil {
|
||||||
// c.ResponseError(err.Error())
|
// c.ResponseError(err.Error())
|
||||||
// return
|
// return
|
||||||
//}
|
// }
|
||||||
|
|
||||||
//for _, group := range groupsMap {
|
// for _, group := range groupsMap {
|
||||||
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
||||||
// GroupId: group.GidNumber,
|
// GroupId: group.GidNumber,
|
||||||
// GroupName: group.Cn,
|
// GroupName: group.Cn,
|
||||||
// })
|
// })
|
||||||
//}
|
// }
|
||||||
|
|
||||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
users, err := conn.GetLdapUsers(ldapServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range users {
|
uuids := make([]string, len(users))
|
||||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
for i, user := range users {
|
||||||
UidNumber: user.UidNumber,
|
uuids[i] = user.GetLdapUuid()
|
||||||
Uid: user.Uid,
|
}
|
||||||
Cn: user.Cn,
|
existUuids, err := object.GetExistUuids(ldapServer.Owner, uuids)
|
||||||
GroupId: user.GidNumber,
|
if err != nil {
|
||||||
// GroupName: groupsMap[user.GidNumber].Cn,
|
c.ResponseError(err.Error())
|
||||||
Uuid: user.Uuid,
|
return
|
||||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
|
||||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
|
||||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = Response{Status: "ok", Data: resp}
|
resp := LdapResp{
|
||||||
c.ServeJSON()
|
Users: object.AutoAdjustLdapUser(users),
|
||||||
|
ExistUuids: existUuids,
|
||||||
|
}
|
||||||
|
c.ResponseOk(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdaps
|
// GetLdaps
|
||||||
// @Tag Account API
|
|
||||||
// @Title GetLdaps
|
// @Title GetLdaps
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description get ldaps
|
||||||
|
// @Param owner query string false "owner"
|
||||||
|
// @Success 200 {array} object.Ldap The Response object
|
||||||
// @router /get-ldaps [get]
|
// @router /get-ldaps [get]
|
||||||
func (c *ApiController) GetLdaps() {
|
func (c *ApiController) GetLdaps() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
|
c.ResponseOk(object.GetMaskedLdaps(object.GetLdaps(owner)))
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLdap
|
// GetLdap
|
||||||
// @Tag Account API
|
|
||||||
// @Title GetLdap
|
// @Title GetLdap
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description get ldap
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} object.Ldap The Response object
|
||||||
// @router /get-ldap [get]
|
// @router /get-ldap [get]
|
||||||
func (c *ApiController) GetLdap() {
|
func (c *ApiController) GetLdap() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
@@ -124,39 +125,52 @@ func (c *ApiController) GetLdap() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
|
_, name := util.GetOwnerAndNameFromId(id)
|
||||||
c.ServeJSON()
|
ldap, err := object.GetLdap(name)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(object.GetMaskedLdap(ldap))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLdap
|
// AddLdap
|
||||||
// @Tag Account API
|
|
||||||
// @Title AddLdap
|
// @Title AddLdap
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description add ldap
|
||||||
|
// @Param body body object.Ldap true "The details of the ldap"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /add-ldap [post]
|
// @router /add-ldap [post]
|
||||||
func (c *ApiController) AddLdap() {
|
func (c *ApiController) AddLdap() {
|
||||||
var ldap object.Ldap
|
var ldap object.Ldap
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
if ok, err := object.CheckLdapExist(&ldap); err != nil {
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
} else if ok {
|
||||||
|
|
||||||
if object.CheckLdapExist(&ldap) {
|
|
||||||
c.ResponseError(c.T("ldap:Ldap server exist"))
|
c.ResponseError(c.T("ldap:Ldap server exist"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.AddLdap(&ldap)
|
resp := wrapActionResponse(object.AddLdap(&ldap))
|
||||||
resp := wrapActionResponse(affected)
|
resp.Data2 = ldap
|
||||||
if affected {
|
|
||||||
resp.Data2 = ldap
|
|
||||||
}
|
|
||||||
if ldap.AutoSync != 0 {
|
if ldap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
err = object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
@@ -164,36 +178,52 @@ func (c *ApiController) AddLdap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLdap
|
// UpdateLdap
|
||||||
// @Tag Account API
|
|
||||||
// @Title UpdateLdap
|
// @Title UpdateLdap
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description update ldap
|
||||||
|
// @Param body body object.Ldap true "The details of the ldap"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /update-ldap [post]
|
// @router /update-ldap [post]
|
||||||
func (c *ApiController) UpdateLdap() {
|
func (c *ApiController) UpdateLdap() {
|
||||||
var ldap object.Ldap
|
var ldap object.Ldap
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||||
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
|
||||||
c.ResponseError(c.T("general:Missing parameter"))
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
prevLdap := object.GetLdap(ldap.Id)
|
prevLdap, err := object.GetLdap(ldap.Id)
|
||||||
affected := object.UpdateLdap(&ldap)
|
if err != nil {
|
||||||
resp := wrapActionResponse(affected)
|
c.ResponseError(err.Error())
|
||||||
if affected {
|
return
|
||||||
resp.Data2 = ldap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
affected, err := object.UpdateLdap(&ldap)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ldap.AutoSync != 0 {
|
if ldap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
err := object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
|
||||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLdap
|
// DeleteLdap
|
||||||
// @Tag Account API
|
|
||||||
// @Title DeleteLdap
|
// @Title DeleteLdap
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description delete ldap
|
||||||
|
// @Param body body object.Ldap true "The details of the ldap"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /delete-ldap [post]
|
// @router /delete-ldap [post]
|
||||||
func (c *ApiController) DeleteLdap() {
|
func (c *ApiController) DeleteLdap() {
|
||||||
var ldap object.Ldap
|
var ldap object.Ldap
|
||||||
@@ -203,49 +233,50 @@ func (c *ApiController) DeleteLdap() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
affected, err := object.DeleteLdap(&ldap)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
|
|
||||||
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncLdapUsers
|
// SyncLdapUsers
|
||||||
// @Tag Account API
|
|
||||||
// @Title SyncLdapUsers
|
// @Title SyncLdapUsers
|
||||||
|
// @Tag Account API
|
||||||
|
// @Description sync ldap users
|
||||||
|
// @Param id query string true "id"
|
||||||
|
// @Success 200 {object} controllers.LdapSyncResp The Response object
|
||||||
// @router /sync-ldap-users [post]
|
// @router /sync-ldap-users [post]
|
||||||
func (c *ApiController) SyncLdapUsers() {
|
func (c *ApiController) SyncLdapUsers() {
|
||||||
owner := c.Input().Get("owner")
|
id := c.Input().Get("id")
|
||||||
ldapId := c.Input().Get("ldapId")
|
|
||||||
var users []object.LdapRespUser
|
owner, ldapId := util.GetOwnerAndNameFromId(id)
|
||||||
|
var users []object.LdapUser
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
object.UpdateLdapSyncTime(ldapId)
|
err = object.UpdateLdapSyncTime(ldapId)
|
||||||
|
|
||||||
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
|
|
||||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
|
||||||
Exist: *exist,
|
|
||||||
Failed: *failed,
|
|
||||||
}}
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckLdapUsersExist
|
|
||||||
// @Tag Account API
|
|
||||||
// @Title CheckLdapUserExist
|
|
||||||
// @router /check-ldap-users-exist [post]
|
|
||||||
func (c *ApiController) CheckLdapUsersExist() {
|
|
||||||
owner := c.Input().Get("owner")
|
|
||||||
var uuids []string
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
exist, failed, err := object.SyncLdapUsers(owner, users, ldapId)
|
||||||
c.Data["json"] = &Response{Status: "ok", Data: exist}
|
if err != nil {
|
||||||
c.ServeJSON()
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(&LdapSyncResp{
|
||||||
|
Exist: exist,
|
||||||
|
Failed: failed,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
// Copyright 2022 The Casdoor Authors. 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 controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
|
||||||
"github.com/casdoor/casdoor/object"
|
|
||||||
"github.com/forestmgy/ldapserver"
|
|
||||||
"github.com/lor00x/goldap/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartLdapServer() {
|
|
||||||
server := ldapserver.NewServer()
|
|
||||||
routes := ldapserver.NewRouteMux()
|
|
||||||
|
|
||||||
routes.Bind(handleBind)
|
|
||||||
routes.Search(handleSearch).Label(" SEARCH****")
|
|
||||||
|
|
||||||
server.Handle(routes)
|
|
||||||
server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
|
||||||
r := m.GetBindRequest()
|
|
||||||
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
|
|
||||||
|
|
||||||
if r.AuthenticationChoice() == "simple" {
|
|
||||||
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
|
|
||||||
if err != "" {
|
|
||||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
|
||||||
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
|
|
||||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bindpassword := string(r.AuthenticationSimple())
|
|
||||||
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword, "en")
|
|
||||||
if err != "" {
|
|
||||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
|
||||||
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
|
|
||||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if bindorg == "built-in" {
|
|
||||||
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
|
|
||||||
} else if binduser.IsAdmin {
|
|
||||||
m.Client.IsOrgAdmin = true
|
|
||||||
}
|
|
||||||
m.Client.IsAuthenticated = true
|
|
||||||
m.Client.UserName = bindusername
|
|
||||||
m.Client.OrgName = bindorg
|
|
||||||
} else {
|
|
||||||
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
|
|
||||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
|
||||||
}
|
|
||||||
w.Write(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
|
||||||
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
|
|
||||||
if !m.Client.IsAuthenticated {
|
|
||||||
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r := m.GetSearchRequest()
|
|
||||||
if r.FilterString() == "(objectClass=*)" {
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
|
|
||||||
if errCode != ldapserver.LDAPResultSuccess {
|
|
||||||
res.SetResultCode(errCode)
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
|
||||||
select {
|
|
||||||
case <-m.Done:
|
|
||||||
log.Print("Leaving handleSearch...")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
users, errCode := object.GetFilteredUsers(m, name, org)
|
|
||||||
if errCode != ldapserver.LDAPResultSuccess {
|
|
||||||
res.SetResultCode(errCode)
|
|
||||||
w.Write(res)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < len(users); i++ {
|
|
||||||
user := users[i]
|
|
||||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
|
||||||
e := ldapserver.NewSearchResultEntry(dn)
|
|
||||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
|
||||||
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
|
||||||
e.AddAttribute("email", message.AttributeValue(user.Email))
|
|
||||||
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
|
||||||
e.AddAttribute("userPassword", message.AttributeValue(getUserPasswordWithType(user)))
|
|
||||||
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
|
||||||
w.Write(e)
|
|
||||||
}
|
|
||||||
w.Write(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get user password with hash type prefix
|
|
||||||
// TODO not handle salt yet
|
|
||||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
|
||||||
func getUserPasswordWithType(user *object.User) string {
|
|
||||||
org := object.GetOrganizationByUser(user)
|
|
||||||
if org.PasswordType == "" || org.PasswordType == "plain" {
|
|
||||||
return user.Password
|
|
||||||
}
|
|
||||||
prefix := org.PasswordType
|
|
||||||
if prefix == "salt" {
|
|
||||||
prefix = "sha256"
|
|
||||||
} else if prefix == "md5-salt" {
|
|
||||||
prefix = "md5"
|
|
||||||
} else if prefix == "pbkdf2-salt" {
|
|
||||||
prefix = "pbkdf2"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
|
||||||
}
|
|
||||||
@@ -26,8 +26,10 @@ type LinkForm struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unlink ...
|
// Unlink ...
|
||||||
// @router /unlink [post]
|
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
|
// @Title Unlink
|
||||||
|
// @router /unlink [post]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
func (c *ApiController) Unlink() {
|
func (c *ApiController) Unlink() {
|
||||||
user, ok := c.RequireSignedInUser()
|
user, ok := c.RequireSignedInUser()
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -45,15 +47,19 @@ func (c *ApiController) Unlink() {
|
|||||||
// the user will be unlinked from the provider
|
// the user will be unlinked from the provider
|
||||||
unlinkedUser := form.User
|
unlinkedUser := form.User
|
||||||
|
|
||||||
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
|
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin() {
|
||||||
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
|
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
|
||||||
c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users"))
|
c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin {
|
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin() {
|
||||||
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
|
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
|
||||||
application := object.GetApplicationByUser(user)
|
application, err := object.GetApplicationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
||||||
return
|
return
|
||||||
@@ -88,8 +94,17 @@ func (c *ApiController) Unlink() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
object.ClearUserOAuthProperties(&unlinkedUser, providerType)
|
_, err = object.ClearUserOAuthProperties(&unlinkedUser, providerType)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = object.LinkUserAccount(&unlinkedUser, providerType, "")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
object.LinkUserAccount(&unlinkedUser, providerType, "")
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|||||||
289
controllers/mfa.go
Normal file
289
controllers/mfa.go
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MfaSetupInitiate
|
||||||
|
// @Title MfaSetupInitiate
|
||||||
|
// @Tag MFA API
|
||||||
|
// @Description setup MFA
|
||||||
|
// @param owner form string true "owner of user"
|
||||||
|
// @param name form string true "name of user"
|
||||||
|
// @param type form string true "MFA auth type"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /mfa/setup/initiate [post]
|
||||||
|
func (c *ApiController) MfaSetupInitiate() {
|
||||||
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
|
userId := util.GetId(owner, name)
|
||||||
|
|
||||||
|
if len(userId) == 0 {
|
||||||
|
c.ResponseError(http.StatusText(http.StatusBadRequest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
MfaUtil := object.GetMfaUtil(mfaType, nil)
|
||||||
|
if MfaUtil == nil {
|
||||||
|
c.ResponseError("Invalid auth type")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError("User doesn't exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, err := object.GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mfaProps, err := MfaUtil.Initiate(user.GetId())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveryCode := uuid.NewString()
|
||||||
|
mfaProps.RecoveryCodes = []string{recoveryCode}
|
||||||
|
mfaProps.MfaRememberInHours = organization.MfaRememberInHours
|
||||||
|
|
||||||
|
resp := mfaProps
|
||||||
|
c.ResponseOk(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MfaSetupVerify
|
||||||
|
// @Title MfaSetupVerify
|
||||||
|
// @Tag MFA API
|
||||||
|
// @Description setup verify totp
|
||||||
|
// @param secret form string true "MFA secret"
|
||||||
|
// @param passcode form string true "MFA passcode"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /mfa/setup/verify [post]
|
||||||
|
func (c *ApiController) MfaSetupVerify() {
|
||||||
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
|
passcode := c.Ctx.Request.Form.Get("passcode")
|
||||||
|
secret := c.Ctx.Request.Form.Get("secret")
|
||||||
|
dest := c.Ctx.Request.Form.Get("dest")
|
||||||
|
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
||||||
|
|
||||||
|
if mfaType == "" || passcode == "" {
|
||||||
|
c.ResponseError("missing auth type or passcode")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &object.MfaProps{
|
||||||
|
MfaType: mfaType,
|
||||||
|
}
|
||||||
|
if mfaType == object.TotpType {
|
||||||
|
if secret == "" {
|
||||||
|
c.ResponseError("totp secret is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Secret = secret
|
||||||
|
} else if mfaType == object.SmsType {
|
||||||
|
if dest == "" {
|
||||||
|
c.ResponseError("destination is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Secret = dest
|
||||||
|
if countryCode == "" {
|
||||||
|
c.ResponseError("country code is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.CountryCode = countryCode
|
||||||
|
} else if mfaType == object.EmailType {
|
||||||
|
if dest == "" {
|
||||||
|
c.ResponseError("destination is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Secret = dest
|
||||||
|
}
|
||||||
|
|
||||||
|
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||||
|
if mfaUtil == nil {
|
||||||
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mfaUtil.SetupVerify(passcode)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
} else {
|
||||||
|
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MfaSetupEnable
|
||||||
|
// @Title MfaSetupEnable
|
||||||
|
// @Tag MFA API
|
||||||
|
// @Description enable totp
|
||||||
|
// @param owner form string true "owner of user"
|
||||||
|
// @param name form string true "name of user"
|
||||||
|
// @param type form string true "MFA auth type"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /mfa/setup/enable [post]
|
||||||
|
func (c *ApiController) MfaSetupEnable() {
|
||||||
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
|
secret := c.Ctx.Request.Form.Get("secret")
|
||||||
|
dest := c.Ctx.Request.Form.Get("dest")
|
||||||
|
countryCode := c.Ctx.Request.Form.Get("secret")
|
||||||
|
recoveryCodes := c.Ctx.Request.Form.Get("recoveryCodes")
|
||||||
|
|
||||||
|
user, err := object.GetUser(util.GetId(owner, name))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError("User doesn't exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &object.MfaProps{
|
||||||
|
MfaType: mfaType,
|
||||||
|
}
|
||||||
|
|
||||||
|
if mfaType == object.TotpType {
|
||||||
|
if secret == "" {
|
||||||
|
c.ResponseError("totp secret is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Secret = secret
|
||||||
|
} else if mfaType == object.EmailType {
|
||||||
|
if user.Email == "" {
|
||||||
|
if dest == "" {
|
||||||
|
c.ResponseError("destination is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Email = dest
|
||||||
|
}
|
||||||
|
} else if mfaType == object.SmsType {
|
||||||
|
if user.Phone == "" {
|
||||||
|
if dest == "" {
|
||||||
|
c.ResponseError("destination is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Phone = dest
|
||||||
|
if countryCode == "" {
|
||||||
|
c.ResponseError("country code is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.CountryCode = countryCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if recoveryCodes == "" {
|
||||||
|
c.ResponseError("recovery codes is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.RecoveryCodes = []string{recoveryCodes}
|
||||||
|
|
||||||
|
mfaUtil := object.GetMfaUtil(mfaType, config)
|
||||||
|
if mfaUtil == nil {
|
||||||
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mfaUtil.Enable(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(http.StatusText(http.StatusOK))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteMfa
|
||||||
|
// @Title DeleteMfa
|
||||||
|
// @Tag MFA API
|
||||||
|
// @Description: Delete MFA
|
||||||
|
// @param owner form string true "owner of user"
|
||||||
|
// @param name form string true "name of user"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-mfa/ [post]
|
||||||
|
func (c *ApiController) DeleteMfa() {
|
||||||
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
|
userId := util.GetId(owner, name)
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError("User doesn't exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.DisabledMultiFactorAuth(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetAllMfaProps(user, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPreferredMfa
|
||||||
|
// @Title SetPreferredMfa
|
||||||
|
// @Tag MFA API
|
||||||
|
// @Description: Set specific Mfa Preferred
|
||||||
|
// @param owner form string true "owner of user"
|
||||||
|
// @param name form string true "name of user"
|
||||||
|
// @param id form string true "id of user's MFA props"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /set-preferred-mfa [post]
|
||||||
|
func (c *ApiController) SetPreferredMfa() {
|
||||||
|
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||||
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
|
userId := util.GetId(owner, name)
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError("User doesn't exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.SetPreferredMultiFactorAuth(user, mfaType)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(object.GetAllMfaProps(user, true))
|
||||||
|
}
|
||||||
@@ -37,13 +37,30 @@ func (c *ApiController) GetModels() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetModels(owner)
|
models, err := object.GetModels(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(models)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
|
count, err := object.GetModelCount(owner, field, value)
|
||||||
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
models, err := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(models, paginator.Nums())
|
c.ResponseOk(models, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,8 +75,13 @@ func (c *ApiController) GetModels() {
|
|||||||
func (c *ApiController) GetModel() {
|
func (c *ApiController) GetModel() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetModel(id)
|
model, err := object.GetModel(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateModel
|
// UpdateModel
|
||||||
|
|||||||
@@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/object"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
// GetOidcDiscovery
|
// GetOidcDiscovery
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
@@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
|||||||
c.Data["json"] = jwks
|
c.Data["json"] = jwks
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebFinger
|
||||||
|
// @Title GetWebFinger
|
||||||
|
// @Tag OIDC API
|
||||||
|
// @Param resource query string true "resource"
|
||||||
|
// @Success 200 {object} object.WebFinger
|
||||||
|
// @router /.well-known/webfinger [get]
|
||||||
|
func (c *RootController) GetWebFinger() {
|
||||||
|
resource := c.Input().Get("resource")
|
||||||
|
rels := []string{}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
for key, value := range c.Input() {
|
||||||
|
if strings.HasPrefix(key, "rel") {
|
||||||
|
rels = append(rels, value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = webfinger
|
||||||
|
c.Ctx.Output.ContentType("application/jrd+json")
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,14 +37,49 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organizationName := c.Input().Get("organizationName")
|
||||||
|
|
||||||
|
isGlobalAdmin := c.IsGlobalAdmin()
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
var organizations []*object.Organization
|
||||||
c.ServeJSON()
|
var err error
|
||||||
|
if isGlobalAdmin {
|
||||||
|
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||||
|
} else {
|
||||||
|
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(organizations)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
if !isGlobalAdmin {
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value)))
|
organizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
|
||||||
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
if err != nil {
|
||||||
c.ResponseOk(organizations, paginator.Nums())
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(organizations)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, organizationName, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(organizations, paginator.Nums())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +92,17 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
// @router /get-organization [get]
|
// @router /get-organization [get]
|
||||||
func (c *ApiController) GetOrganization() {
|
func (c *ApiController) GetOrganization() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
organization, err := object.GetMaskedOrganization(object.GetOrganization(id))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedOrganization(object.GetOrganization(id))
|
if organization != nil && organization.MfaRememberInHours == 0 {
|
||||||
c.ServeJSON()
|
organization.MfaRememberInHours = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOrganization ...
|
// UpdateOrganization ...
|
||||||
@@ -80,7 +123,14 @@ func (c *ApiController) UpdateOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isGlobalAdmin, _ := c.isGlobalAdmin()
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization, isGlobalAdmin))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +149,18 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := object.GetOrganizationCount("", "", "")
|
count, err := object.GetOrganizationCount("", "", "", "")
|
||||||
if err := checkQuotaForOrganization(count); err != nil {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = checkQuotaForOrganization(int(count)); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -133,7 +193,7 @@ func (c *ApiController) DeleteOrganization() {
|
|||||||
// @Tag Organization API
|
// @Tag Organization API
|
||||||
// @Description get default application
|
// @Description get default application
|
||||||
// @Param id query string true "organization id"
|
// @Param id query string true "organization id"
|
||||||
// @Success 200 {object} Response The Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /get-default-application [get]
|
// @router /get-default-application [get]
|
||||||
func (c *ApiController) GetDefaultApplication() {
|
func (c *ApiController) GetDefaultApplication() {
|
||||||
userId := c.GetSessionUsername()
|
userId := c.GetSessionUsername()
|
||||||
@@ -145,6 +205,24 @@ func (c *ApiController) GetDefaultApplication() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maskedApplication := object.GetMaskedApplication(application, userId)
|
application = object.GetMaskedApplication(application, userId)
|
||||||
c.ResponseOk(maskedApplication)
|
c.ResponseOk(application)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrganizationNames ...
|
||||||
|
// @Title GetOrganizationNames
|
||||||
|
// @Tag Organization API
|
||||||
|
// @Param owner query string true "owner"
|
||||||
|
// @Description get all organization name and displayName
|
||||||
|
// @Success 200 {array} object.Organization The Response object
|
||||||
|
// @router /get-organization-names [get]
|
||||||
|
func (c *ApiController) GetOrganizationNames() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
organizationNames, err := object.GetOrganizationsByFields(owner, []string{"name", "display_name"}...)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(organizationNames)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -38,13 +37,30 @@ func (c *ApiController) GetPayments() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetPayments(owner)
|
payments, err := object.GetPayments(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payments)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPaymentCount(owner, field, value)))
|
count, err := object.GetPaymentCount(owner, field, value)
|
||||||
payments := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
payments, err := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(payments, paginator.Nums())
|
c.ResponseOk(payments, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,10 +76,14 @@ func (c *ApiController) GetPayments() {
|
|||||||
// @router /get-user-payments [get]
|
// @router /get-user-payments [get]
|
||||||
func (c *ApiController) GetUserPayments() {
|
func (c *ApiController) GetUserPayments() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
organization := c.Input().Get("organization")
|
|
||||||
user := c.Input().Get("user")
|
user := c.Input().Get("user")
|
||||||
|
|
||||||
payments := object.GetUserPayments(owner, organization, user)
|
payments, err := object.GetUserPayments(owner, user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(payments)
|
c.ResponseOk(payments)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +97,13 @@ func (c *ApiController) GetUserPayments() {
|
|||||||
func (c *ApiController) GetPayment() {
|
func (c *ApiController) GetPayment() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetPayment(id)
|
payment, err := object.GetPayment(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePayment
|
// UpdatePayment
|
||||||
@@ -150,22 +175,17 @@ func (c *ApiController) DeletePayment() {
|
|||||||
// @router /notify-payment [post]
|
// @router /notify-payment [post]
|
||||||
func (c *ApiController) NotifyPayment() {
|
func (c *ApiController) NotifyPayment() {
|
||||||
owner := c.Ctx.Input.Param(":owner")
|
owner := c.Ctx.Input.Param(":owner")
|
||||||
providerName := c.Ctx.Input.Param(":provider")
|
|
||||||
productName := c.Ctx.Input.Param(":product")
|
|
||||||
paymentName := c.Ctx.Input.Param(":payment")
|
paymentName := c.Ctx.Input.Param(":payment")
|
||||||
|
|
||||||
body := c.Ctx.Input.RequestBody
|
body := c.Ctx.Input.RequestBody
|
||||||
|
|
||||||
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
payment, err := object.NotifyPayment(body, owner, paymentName)
|
||||||
if ok {
|
if err != nil {
|
||||||
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
|
c.ResponseError(err.Error())
|
||||||
if err != nil {
|
return
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoicePayment
|
// InvoicePayment
|
||||||
@@ -178,7 +198,12 @@ func (c *ApiController) NotifyPayment() {
|
|||||||
func (c *ApiController) InvoicePayment() {
|
func (c *ApiController) InvoicePayment() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
payment := object.GetPayment(id)
|
payment, err := object.GetPayment(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
invoiceUrl, err := object.InvoicePayment(payment)
|
invoiceUrl, err := object.InvoicePayment(payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
|||||||
@@ -37,13 +37,30 @@ func (c *ApiController) GetPermissions() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetPermissions(owner)
|
permissions, err := object.GetPermissions(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(permissions)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPermissionCount(owner, field, value)))
|
count, err := object.GetPermissionCount(owner, field, value)
|
||||||
permissions := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(permissions, paginator.Nums())
|
c.ResponseOk(permissions, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,9 +77,13 @@ func (c *ApiController) GetPermissionsBySubmitter() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions := object.GetPermissionsBySubmitter(user.Owner, user.Name)
|
permissions, err := object.GetPermissionsBySubmitter(user.Owner, user.Name)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(permissions, len(permissions))
|
c.ResponseOk(permissions, len(permissions))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPermissionsByRole
|
// GetPermissionsByRole
|
||||||
@@ -74,9 +95,13 @@ func (c *ApiController) GetPermissionsBySubmitter() {
|
|||||||
// @router /get-permissions-by-role [get]
|
// @router /get-permissions-by-role [get]
|
||||||
func (c *ApiController) GetPermissionsByRole() {
|
func (c *ApiController) GetPermissionsByRole() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
permissions := object.GetPermissionsByRole(id)
|
permissions, err := object.GetPermissionsByRole(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(permissions, len(permissions))
|
c.ResponseOk(permissions, len(permissions))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPermission
|
// GetPermission
|
||||||
@@ -89,8 +114,13 @@ func (c *ApiController) GetPermissionsByRole() {
|
|||||||
func (c *ApiController) GetPermission() {
|
func (c *ApiController) GetPermission() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetPermission(id)
|
permission, err := object.GetPermission(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePermission
|
// UpdatePermission
|
||||||
|
|||||||
54
controllers/permission_upload.go
Normal file
54
controllers/permission_upload.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadPermissions() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
defer os.Remove(path)
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UploadPermissions(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
|
}
|
||||||
|
}
|
||||||
187
controllers/plan.go
Normal file
187
controllers/plan.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPlans
|
||||||
|
// @Title GetPlans
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description get plans
|
||||||
|
// @Param owner query string true "The owner of plans"
|
||||||
|
// @Success 200 {array} object.Plan The Response object
|
||||||
|
// @router /get-plans [get]
|
||||||
|
func (c *ApiController) GetPlans() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
plans, err := object.GetPlans(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(plans)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetPlanCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
plan, err := object.GetPaginatedPlans(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(plan, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPlan
|
||||||
|
// @Title GetPlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description get plan
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||||
|
// @Param includeOption query bool false "Should include plan's option"
|
||||||
|
// @Success 200 {object} object.Plan The Response object
|
||||||
|
// @router /get-plan [get]
|
||||||
|
func (c *ApiController) GetPlan() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
includeOption := c.Input().Get("includeOption") == "true"
|
||||||
|
|
||||||
|
plan, err := object.GetPlan(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if plan != nil && includeOption {
|
||||||
|
options, err := object.GetPermissionsByRole(plan.Role)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
plan.Options = append(plan.Options, option.DisplayName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(plan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePlan
|
||||||
|
// @Title UpdatePlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description update plan
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the plan"
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-plan [post]
|
||||||
|
func (c *ApiController) UpdatePlan() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
owner := util.GetOwnerFromId(id)
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if plan.Product != "" {
|
||||||
|
productId := util.GetId(owner, plan.Product)
|
||||||
|
product, err := object.GetProduct(productId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if product != nil {
|
||||||
|
object.UpdateProductForPlan(&plan, product)
|
||||||
|
_, err = object.UpdateProduct(productId, product)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPlan
|
||||||
|
// @Title AddPlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description add plan
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-plan [post]
|
||||||
|
func (c *ApiController) AddPlan() {
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create a related product for plan
|
||||||
|
product := object.CreateProductForPlan(&plan)
|
||||||
|
_, err = object.AddProduct(product)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
plan.Product = product.Name
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePlan
|
||||||
|
// @Title DeletePlan
|
||||||
|
// @Tag Plan API
|
||||||
|
// @Description delete plan
|
||||||
|
// @Param body body object.Plan true "The details of the plan"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-plan [post]
|
||||||
|
func (c *ApiController) DeletePlan() {
|
||||||
|
var plan object.Plan
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if plan.Product != "" {
|
||||||
|
_, err = object.DeleteProduct(&object.Product{Owner: plan.Owner, Name: plan.Product})
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
145
controllers/pricing.go
Normal file
145
controllers/pricing.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPricings
|
||||||
|
// @Title GetPricings
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description get pricings
|
||||||
|
// @Param owner query string true "The owner of pricings"
|
||||||
|
// @Success 200 {array} object.Pricing The Response object
|
||||||
|
// @router /get-pricings [get]
|
||||||
|
func (c *ApiController) GetPricings() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
pricings, err := object.GetPricings(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(pricings)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetPricingCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
pricing, err := object.GetPaginatedPricings(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(pricing, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPricing
|
||||||
|
// @Title GetPricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description get pricing
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||||
|
// @Success 200 {object} object.Pricing The Response object
|
||||||
|
// @router /get-pricing [get]
|
||||||
|
func (c *ApiController) GetPricing() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
pricing, err := object.GetPricing(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(pricing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePricing
|
||||||
|
// @Title UpdatePricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description update pricing
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the pricing"
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-pricing [post]
|
||||||
|
func (c *ApiController) UpdatePricing() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdatePricing(id, &pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPricing
|
||||||
|
// @Title AddPricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description add pricing
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-pricing [post]
|
||||||
|
func (c *ApiController) AddPricing() {
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddPricing(&pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePricing
|
||||||
|
// @Title DeletePricing
|
||||||
|
// @Tag Pricing API
|
||||||
|
// @Description delete pricing
|
||||||
|
// @Param body body object.Pricing true "The details of the pricing"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-pricing [post]
|
||||||
|
func (c *ApiController) DeletePricing() {
|
||||||
|
var pricing object.Pricing
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &pricing)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeletePricing(&pricing))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -38,13 +39,30 @@ func (c *ApiController) GetProducts() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetProducts(owner)
|
products, err := object.GetProducts(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(products)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProductCount(owner, field, value)))
|
count, err := object.GetProductCount(owner, field, value)
|
||||||
products := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
products, err := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(products, paginator.Nums())
|
c.ResponseOk(products, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,11 +77,19 @@ func (c *ApiController) GetProducts() {
|
|||||||
func (c *ApiController) GetProduct() {
|
func (c *ApiController) GetProduct() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
product := object.GetProduct(id)
|
product, err := object.GetProduct(id)
|
||||||
object.ExtendProductWithProviders(product)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = product
|
err = object.ExtendProductWithProviders(product)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(product)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProduct
|
// UpdateProduct
|
||||||
@@ -136,26 +162,53 @@ func (c *ApiController) DeleteProduct() {
|
|||||||
// @router /buy-product [post]
|
// @router /buy-product [post]
|
||||||
func (c *ApiController) BuyProduct() {
|
func (c *ApiController) BuyProduct() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
providerName := c.Input().Get("providerName")
|
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
|
providerName := c.Input().Get("providerName")
|
||||||
userId := c.GetSessionUsername()
|
paymentEnv := c.Input().Get("paymentEnv")
|
||||||
if userId == "" {
|
customPriceStr := c.Input().Get("customPrice")
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
if customPriceStr == "" {
|
||||||
return
|
customPriceStr = "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
user := object.GetUser(userId)
|
customPrice, err := strconv.ParseFloat(customPriceStr, 64)
|
||||||
if user == nil {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payUrl, err := object.BuyProduct(id, providerName, user, host)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(payUrl)
|
// buy `pricingName/planName` for `paidUserName`
|
||||||
|
pricingName := c.Input().Get("pricingName")
|
||||||
|
planName := c.Input().Get("planName")
|
||||||
|
paidUserName := c.Input().Get("userName")
|
||||||
|
owner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
|
userId := util.GetId(owner, paidUserName)
|
||||||
|
if paidUserName != "" && paidUserName != c.GetSessionUsername() && !c.IsAdmin() {
|
||||||
|
c.ResponseError(c.T("general:Only admin user can specify user"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if paidUserName == "" {
|
||||||
|
userId = c.GetSessionUsername()
|
||||||
|
}
|
||||||
|
if userId == "" {
|
||||||
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv, customPrice)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payment, attachInfo)
|
||||||
}
|
}
|
||||||
|
|||||||
39
controllers/prometheus.go
Normal file
39
controllers/prometheus.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPrometheusInfo
|
||||||
|
// @Title GetPrometheusInfo
|
||||||
|
// @Tag System API
|
||||||
|
// @Description get Prometheus Info
|
||||||
|
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||||
|
// @router /get-prometheus-info [get]
|
||||||
|
func (c *ApiController) GetPrometheusInfo() {
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prometheusInfo, err := object.GetPrometheusInfo()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(prometheusInfo)
|
||||||
|
}
|
||||||
@@ -37,13 +37,36 @@ func (c *ApiController) GetProviders() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
|
providers, err := object.GetProviders(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
count, err := object.GetProviderCount(owner, field, value)
|
||||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
paginationProviders, err := object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
providers := object.GetMaskedProviders(paginationProviders, isMaskEnabled)
|
||||||
c.ResponseOk(providers, paginator.Nums())
|
c.ResponseOk(providers, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,13 +84,36 @@ func (c *ApiController) GetGlobalProviders() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
|
globalProviders, err := object.GetGlobalProviders()
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
|
count, err := object.GetGlobalProviderCount(field, value)
|
||||||
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
paginationGlobalProviders, err := object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
providers := object.GetMaskedProviders(paginationGlobalProviders, isMaskEnabled)
|
||||||
c.ResponseOk(providers, paginator.Nums())
|
c.ResponseOk(providers, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,8 +127,32 @@ func (c *ApiController) GetGlobalProviders() {
|
|||||||
// @router /get-provider [get]
|
// @router /get-provider [get]
|
||||||
func (c *ApiController) GetProvider() {
|
func (c *ApiController) GetProvider() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
|
||||||
c.ServeJSON()
|
ok, isMaskEnabled := c.IsMaskedEnabled()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
provider, err := object.GetProvider(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) requireProviderPermission(provider *object.Provider) bool {
|
||||||
|
isGlobalAdmin, user := c.isGlobalAdmin()
|
||||||
|
if isGlobalAdmin {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.Owner == "admin" || user.Owner != provider.Owner {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateProvider
|
// UpdateProvider
|
||||||
@@ -103,6 +173,11 @@ func (c *ApiController) UpdateProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -122,12 +197,23 @@ func (c *ApiController) AddProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := object.GetProviderCount("", "", "")
|
count, err := object.GetProviderCount("", "", "")
|
||||||
if err := checkQuotaForProvider(count); err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = checkQuotaForProvider(int(count))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -147,6 +233,11 @@ func (c *ApiController) DeleteProvider() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := c.requireProviderPermission(&provider)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -42,14 +44,35 @@ func (c *ApiController) GetRecords() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organizationName := c.Input().Get("organizationName")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetRecords()
|
records, err := object.GetRecords()
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(records)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
filterRecord := &object.Record{Organization: organization}
|
if c.IsGlobalAdmin() && organizationName != "" {
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value, filterRecord)))
|
organization = organizationName
|
||||||
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
|
}
|
||||||
|
filterRecord := &casvisorsdk.Record{Organization: organization}
|
||||||
|
count, err := object.GetRecordCount(field, value, filterRecord)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
records, err := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(records, paginator.Nums())
|
c.ResponseOk(records, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,17 +85,27 @@ func (c *ApiController) GetRecords() {
|
|||||||
// @Success 200 {object} object.Record The Response object
|
// @Success 200 {object} object.Record The Response object
|
||||||
// @router /get-records-filter [post]
|
// @router /get-records-filter [post]
|
||||||
func (c *ApiController) GetRecordsByFilter() {
|
func (c *ApiController) GetRecordsByFilter() {
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
body := string(c.Ctx.Input.RequestBody)
|
body := string(c.Ctx.Input.RequestBody)
|
||||||
|
|
||||||
record := &object.Record{}
|
record := &casvisorsdk.Record{}
|
||||||
err := util.JsonToStruct(body, record)
|
err := util.JsonToStruct(body, record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetRecordsByField(record)
|
records, err := object.GetRecordsByField(record)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(records)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRecord
|
// AddRecord
|
||||||
@@ -83,7 +116,7 @@ func (c *ApiController) GetRecordsByFilter() {
|
|||||||
// @Success 200 {object} controllers.Response The Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /add-record [post]
|
// @router /add-record [post]
|
||||||
func (c *ApiController) AddRecord() {
|
func (c *ApiController) AddRecord() {
|
||||||
var record object.Record
|
var record casvisorsdk.Record
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -28,9 +30,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetResources
|
// GetResources
|
||||||
// @router /get-resources [get]
|
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title GetResources
|
// @Title GetResources
|
||||||
|
// @Description get resources
|
||||||
|
// @Param owner query string true "Owner"
|
||||||
|
// @Param user query string true "User"
|
||||||
|
// @Param pageSize query integer false "Page Size"
|
||||||
|
// @Param p query integer false "Page Number"
|
||||||
|
// @Param field query string false "Field"
|
||||||
|
// @Param value query string false "Value"
|
||||||
|
// @Param sortField query string false "Sort Field"
|
||||||
|
// @Param sortOrder query string false "Sort Order"
|
||||||
|
// @Success 200 {array} object.Resource The Response object
|
||||||
|
// @router /get-resources [get]
|
||||||
func (c *ApiController) GetResources() {
|
func (c *ApiController) GetResources() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
user := c.Input().Get("user")
|
user := c.Input().Get("user")
|
||||||
@@ -41,21 +53,53 @@ func (c *ApiController) GetResources() {
|
|||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
userObj, ok := c.RequireSignedInUser()
|
isOrgAdmin, ok := c.IsOrgAdmin()
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if userObj.IsAdmin {
|
|
||||||
|
if isOrgAdmin {
|
||||||
user = ""
|
user = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if sortField == "Direct" {
|
||||||
c.Data["json"] = object.GetResources(owner, user)
|
provider, err := c.GetProviderFromContext("Storage")
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := sortOrder
|
||||||
|
resources, err := object.GetDirectResources(owner, user, provider, prefix, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(resources)
|
||||||
|
} else if limit == "" || page == "" {
|
||||||
|
resources, err := object.GetResources(owner, user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(resources)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value)))
|
count, err := object.GetResourceCount(owner, user, field, value)
|
||||||
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
resources, err := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(resources, paginator.Nums())
|
c.ResponseOk(resources, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,17 +107,29 @@ func (c *ApiController) GetResources() {
|
|||||||
// GetResource
|
// GetResource
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title GetResource
|
// @Title GetResource
|
||||||
|
// @Description get resource
|
||||||
|
// @Param id query string true "The id ( owner/name ) of resource"
|
||||||
|
// @Success 200 {object} object.Resource The Response object
|
||||||
// @router /get-resource [get]
|
// @router /get-resource [get]
|
||||||
func (c *ApiController) GetResource() {
|
func (c *ApiController) GetResource() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetResource(id)
|
resource, err := object.GetResource(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateResource
|
// UpdateResource
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title UpdateResource
|
// @Title UpdateResource
|
||||||
|
// @Description get resource
|
||||||
|
// @Param id query string true "The id ( owner/name ) of resource"
|
||||||
|
// @Param resource body object.Resource true "The resource object"
|
||||||
|
// @Success 200 {object} controllers.Response Success or error
|
||||||
// @router /update-resource [post]
|
// @router /update-resource [post]
|
||||||
func (c *ApiController) UpdateResource() {
|
func (c *ApiController) UpdateResource() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
@@ -92,6 +148,8 @@ func (c *ApiController) UpdateResource() {
|
|||||||
// AddResource
|
// AddResource
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title AddResource
|
// @Title AddResource
|
||||||
|
// @Param resource body object.Resource true "Resource object"
|
||||||
|
// @Success 200 {object} controllers.Response Success or error
|
||||||
// @router /add-resource [post]
|
// @router /add-resource [post]
|
||||||
func (c *ApiController) AddResource() {
|
func (c *ApiController) AddResource() {
|
||||||
var resource object.Resource
|
var resource object.Resource
|
||||||
@@ -108,6 +166,8 @@ func (c *ApiController) AddResource() {
|
|||||||
// DeleteResource
|
// DeleteResource
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title DeleteResource
|
// @Title DeleteResource
|
||||||
|
// @Param resource body object.Resource true "Resource object"
|
||||||
|
// @Success 200 {object} controllers.Response Success or error
|
||||||
// @router /delete-resource [post]
|
// @router /delete-resource [post]
|
||||||
func (c *ApiController) DeleteResource() {
|
func (c *ApiController) DeleteResource() {
|
||||||
var resource object.Resource
|
var resource object.Resource
|
||||||
@@ -117,10 +177,21 @@ func (c *ApiController) DeleteResource() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
if resource.Provider != "" {
|
||||||
if !ok {
|
c.Input().Set("provider", resource.Provider)
|
||||||
|
}
|
||||||
|
c.Input().Set("fullFilePath", resource.Name)
|
||||||
|
provider, err := c.GetProviderFromContext("Storage")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_, resource.Name = refineFullFilePath(resource.Name)
|
||||||
|
|
||||||
|
tag := c.Input().Get("tag")
|
||||||
|
if tag == "Direct" {
|
||||||
|
resource.Name = path.Join(provider.PathPrefix, resource.Name)
|
||||||
|
}
|
||||||
|
|
||||||
err = object.DeleteFile(provider, resource.Name, c.GetAcceptLanguage())
|
err = object.DeleteFile(provider, resource.Name, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,6 +206,16 @@ func (c *ApiController) DeleteResource() {
|
|||||||
// UploadResource
|
// UploadResource
|
||||||
// @Tag Resource API
|
// @Tag Resource API
|
||||||
// @Title UploadResource
|
// @Title UploadResource
|
||||||
|
// @Param owner query string true "Owner"
|
||||||
|
// @Param user query string true "User"
|
||||||
|
// @Param application query string true "Application"
|
||||||
|
// @Param tag query string false "Tag"
|
||||||
|
// @Param parent query string false "Parent"
|
||||||
|
// @Param fullFilePath query string true "Full File Path"
|
||||||
|
// @Param createdTime query string false "Created Time"
|
||||||
|
// @Param description query string false "Description"
|
||||||
|
// @Param file formData file true "Resource file"
|
||||||
|
// @Success 200 {object} object.Resource FileUrl, objectKey
|
||||||
// @router /upload-resource [post]
|
// @router /upload-resource [post]
|
||||||
func (c *ApiController) UploadResource() {
|
func (c *ApiController) UploadResource() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
@@ -165,28 +246,33 @@ func (c *ApiController) UploadResource() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
provider, err := c.GetProviderFromContext("Storage")
|
||||||
if !ok {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
_, fullFilePath = refineFullFilePath(fullFilePath)
|
||||||
|
|
||||||
fileType := "unknown"
|
fileType := "unknown"
|
||||||
contentType := header.Header.Get("Content-Type")
|
contentType := header.Header.Get("Content-Type")
|
||||||
fileType, _ = util.GetOwnerAndNameFromId(contentType)
|
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(contentType + "/")
|
||||||
|
|
||||||
if fileType != "image" && fileType != "video" {
|
if fileType != "image" && fileType != "video" {
|
||||||
ext := filepath.Ext(filename)
|
ext := filepath.Ext(filename)
|
||||||
mimeType := mime.TypeByExtension(ext)
|
mimeType := mime.TypeByExtension(ext)
|
||||||
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
|
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175)
|
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
|
||||||
if tag != "avatar" && tag != "termsOfUse" {
|
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
||||||
ext := filepath.Ext(filepath.Base(fullFilePath))
|
ext := filepath.Ext(filepath.Base(fullFilePath))
|
||||||
index := len(fullFilePath) - len(ext)
|
index := len(fullFilePath) - len(ext)
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
_, objectKey := object.GetUploadFileUrl(provider, fullFilePath, true)
|
_, objectKey := object.GetUploadFileUrl(provider, fullFilePath, true)
|
||||||
if object.GetResourceCount(owner, username, "name", objectKey) == 0 {
|
if count, err := object.GetResourceCount(owner, username, "name", objectKey); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
} else if count == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,12 +281,17 @@ func (c *ApiController) UploadResource() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
|
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if username == "Built-in-Untracked" {
|
||||||
|
c.ResponseOk(fileUrl, objectKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if createdTime == "" {
|
if createdTime == "" {
|
||||||
createdTime = util.GetCurrentTime()
|
createdTime = util.GetCurrentTime()
|
||||||
}
|
}
|
||||||
@@ -222,23 +313,84 @@ func (c *ApiController) UploadResource() {
|
|||||||
Url: fileUrl,
|
Url: fileUrl,
|
||||||
Description: description,
|
Description: description,
|
||||||
}
|
}
|
||||||
object.AddOrUpdateResource(resource)
|
_, err = object.AddOrUpdateResource(resource)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case "avatar":
|
case "avatar":
|
||||||
user := object.GetUserNoCheck(util.GetId(owner, username))
|
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
|
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Avatar = fileUrl
|
user.Avatar = fileUrl
|
||||||
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
_, err = object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case "termsOfUse":
|
case "termsOfUse":
|
||||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||||
app := object.GetApplication(applicationId)
|
if err != nil {
|
||||||
app.TermsOfUse = fileUrl
|
c.ResponseError(err.Error())
|
||||||
object.UpdateApplication(applicationId, app)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.IsAdminUser() {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimSuffix(fullFilePath, ".html"))
|
||||||
|
applicationObj, err := object.GetApplication(applicationId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationObj.TermsOfUse = fileUrl
|
||||||
|
_, err = object.UpdateApplication(applicationId, applicationObj)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "idCardFront", "idCardBack", "idCardWithPerson":
|
||||||
|
user, err := object.GetUserNoCheck(util.GetId(owner, username))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Properties == nil {
|
||||||
|
user.Properties = map[string]string{}
|
||||||
|
}
|
||||||
|
user.Properties[tag] = fileUrl
|
||||||
|
user.Properties["isIdCardVerified"] = "false"
|
||||||
|
_, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(fileUrl, objectKey)
|
c.ResponseOk(fileUrl, objectKey)
|
||||||
|
|||||||
@@ -37,13 +37,30 @@ func (c *ApiController) GetRoles() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetRoles(owner)
|
roles, err := object.GetRoles(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(roles)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
|
count, err := object.GetRoleCount(owner, field, value)
|
||||||
roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
roles, err := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(roles, paginator.Nums())
|
c.ResponseOk(roles, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,8 +75,13 @@ func (c *ApiController) GetRoles() {
|
|||||||
func (c *ApiController) GetRole() {
|
func (c *ApiController) GetRole() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetRole(id)
|
role, err := object.GetRole(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(role)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRole
|
// UpdateRole
|
||||||
|
|||||||
54
controllers/role_upload.go
Normal file
54
controllers/role_upload.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) UploadRoles() {
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||||
|
|
||||||
|
file, header, err := c.Ctx.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
|
defer os.Remove(path)
|
||||||
|
err = saveFile(path, &file)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.UploadRoles(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if affected {
|
||||||
|
c.ResponseOk()
|
||||||
|
} else {
|
||||||
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
)
|
)
|
||||||
@@ -23,12 +24,45 @@ import (
|
|||||||
func (c *ApiController) GetSamlMeta() {
|
func (c *ApiController) GetSamlMeta() {
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
paramApp := c.Input().Get("application")
|
paramApp := c.Input().Get("application")
|
||||||
application := object.GetApplication(paramApp)
|
application, err := object.GetApplication(paramApp)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
metadata, _ := object.GetSamlMeta(application, host)
|
|
||||||
|
enablePostBinding, err := c.GetBool("enablePostBinding", false)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := object.GetSamlMeta(application, host, enablePostBinding)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["xml"] = metadata
|
c.Data["xml"] = metadata
|
||||||
c.ServeXML()
|
c.ServeXML()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) HandleSamlRedirect() {
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
owner := c.Ctx.Input.Param(":owner")
|
||||||
|
application := c.Ctx.Input.Param(":application")
|
||||||
|
|
||||||
|
relayState := c.Input().Get("RelayState")
|
||||||
|
samlRequest := c.Input().Get("SAMLRequest")
|
||||||
|
username := c.Input().Get("username")
|
||||||
|
loginHint := c.Input().Get("login_hint")
|
||||||
|
|
||||||
|
targetURL := object.GetSamlRedirectAddress(owner, application, relayState, samlRequest, host, username, loginHint)
|
||||||
|
|
||||||
|
c.Redirect(targetURL, http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,22 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package object
|
package controllers
|
||||||
|
|
||||||
func (syncer *Syncer) getUsers() []*User {
|
import (
|
||||||
users := GetUsers(syncer.Organization)
|
"strings"
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
|
"github.com/casdoor/casdoor/scim"
|
||||||
users := syncer.getUsers()
|
)
|
||||||
|
|
||||||
m1 := map[string]*User{}
|
func (c *RootController) HandleScim() {
|
||||||
m2 := map[string]*User{}
|
_, ok := c.RequireAdmin()
|
||||||
for _, user := range users {
|
if !ok {
|
||||||
m1[user.Id] = user
|
return
|
||||||
m2[user.Name] = user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, m1, m2
|
path := c.Ctx.Request.URL.Path
|
||||||
|
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
||||||
|
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
}
|
}
|
||||||
@@ -20,17 +20,19 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EmailForm struct {
|
type EmailForm struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Sender string `json:"sender"`
|
Sender string `json:"sender"`
|
||||||
Receivers []string `json:"receivers"`
|
Receivers []string `json:"receivers"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
|
ProviderObject object.Provider `json:"providerObject"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SmsForm struct {
|
type SmsForm struct {
|
||||||
@@ -39,6 +41,10 @@ type SmsForm struct {
|
|||||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotificationForm struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
// SendEmail
|
// SendEmail
|
||||||
// @Title SendEmail
|
// @Title SendEmail
|
||||||
// @Tag Service API
|
// @Tag Service API
|
||||||
@@ -46,11 +52,15 @@ type SmsForm struct {
|
|||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param from body controllers.EmailForm true "Details of the email request"
|
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /api/send-email [post]
|
// @router /send-email [post]
|
||||||
func (c *ApiController) SendEmail() {
|
func (c *ApiController) SendEmail() {
|
||||||
var emailForm EmailForm
|
userId, ok := c.RequireSignedIn()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var emailForm EmailForm
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@@ -60,19 +70,30 @@ func (c *ApiController) SendEmail() {
|
|||||||
var provider *object.Provider
|
var provider *object.Provider
|
||||||
if emailForm.Provider != "" {
|
if emailForm.Provider != "" {
|
||||||
// called by frontend's TestEmailWidget, provider name is set by frontend
|
// called by frontend's TestEmailWidget, provider name is set by frontend
|
||||||
provider = object.GetProvider(util.GetId("admin", emailForm.Provider))
|
provider, err = object.GetProvider(util.GetId("admin", emailForm.Provider))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
|
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
|
||||||
var ok bool
|
provider, err = c.GetProviderFromContext("Email")
|
||||||
provider, _, ok = c.GetProviderFromContext("Email")
|
if err != nil {
|
||||||
if !ok {
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if emailForm.ProviderObject.Name != "" {
|
||||||
|
if emailForm.ProviderObject.ClientSecret == "***" {
|
||||||
|
emailForm.ProviderObject.ClientSecret = provider.ClientSecret
|
||||||
|
}
|
||||||
|
provider = &emailForm.ProviderObject
|
||||||
|
}
|
||||||
|
|
||||||
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
||||||
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
||||||
err := object.DailSmtpServer(provider)
|
err = object.TestSmtpServer(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -97,8 +118,33 @@ func (c *ApiController) SendEmail() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content := emailForm.Content
|
||||||
|
if content == "" {
|
||||||
|
content = provider.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
code := "123456"
|
||||||
|
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||||
|
content = strings.Replace(content, "%s", code, 1)
|
||||||
|
userString := "Hi"
|
||||||
|
if !object.IsAppUser(userId) {
|
||||||
|
var user *object.User
|
||||||
|
user, err = object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
userString = user.GetFriendlyName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
|
||||||
|
|
||||||
|
matchContent := object.ResetLinkReg.Find([]byte(content))
|
||||||
|
content = strings.Replace(content, string(matchContent), "", -1)
|
||||||
|
|
||||||
for _, receiver := range emailForm.Receivers {
|
for _, receiver := range emailForm.Receivers {
|
||||||
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
|
err = object.SendEmail(provider, emailForm.Title, content, []string{receiver}, emailForm.Sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -115,34 +161,28 @@ func (c *ApiController) SendEmail() {
|
|||||||
// @Param clientId query string true "The clientId of the application"
|
// @Param clientId query string true "The clientId of the application"
|
||||||
// @Param clientSecret query string true "The clientSecret of the application"
|
// @Param clientSecret query string true "The clientSecret of the application"
|
||||||
// @Param from body controllers.SmsForm true "Details of the sms request"
|
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||||
// @Success 200 {object} Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /api/send-sms [post]
|
// @router /send-sms [post]
|
||||||
func (c *ApiController) SendSms() {
|
func (c *ApiController) SendSms() {
|
||||||
provider, _, ok := c.GetProviderFromContext("SMS")
|
provider, err := c.GetProviderFromContext("SMS")
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var smsForm SmsForm
|
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var invalidReceivers []string
|
var smsForm SmsForm
|
||||||
for idx, receiver := range smsForm.Receivers {
|
err = json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||||
// The receiver phone format: E164 like +8613854673829 +441932567890
|
if err != nil {
|
||||||
if !util.IsPhoneValid(receiver, "") {
|
c.ResponseError(err.Error())
|
||||||
invalidReceivers = append(invalidReceivers, receiver)
|
return
|
||||||
} else {
|
|
||||||
smsForm.Receivers[idx] = receiver
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(invalidReceivers) != 0 {
|
if provider.Type != "Custom HTTP SMS" {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), invalidReceivers))
|
invalidReceivers := getInvalidSmsReceivers(smsForm)
|
||||||
return
|
if len(invalidReceivers) != 0 {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", ")))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
|
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
|
||||||
@@ -153,3 +193,33 @@ func (c *ApiController) SendSms() {
|
|||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendNotification
|
||||||
|
// @Title SendNotification
|
||||||
|
// @Tag Service API
|
||||||
|
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||||
|
// @Param from body controllers.NotificationForm true "Details of the notification request"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /send-notification [post]
|
||||||
|
func (c *ApiController) SendNotification() {
|
||||||
|
provider, err := c.GetProviderFromContext("Notification")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationForm NotificationForm
|
||||||
|
err = json.Unmarshal(c.Ctx.Input.RequestBody, ¬ificationForm)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.SendNotification(provider, notificationForm.Content)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,13 +37,29 @@ func (c *ApiController) GetSessions() {
|
|||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetSessions(owner)
|
sessions, err := object.GetSessions(owner)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(sessions)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSessionCount(owner, field, value)))
|
count, err := object.GetSessionCount(owner, field, value)
|
||||||
sessions := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
sessions, err := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(sessions, paginator.Nums())
|
c.ResponseOk(sessions, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,21 +68,26 @@ func (c *ApiController) GetSessions() {
|
|||||||
// @Title GetSingleSession
|
// @Title GetSingleSession
|
||||||
// @Tag Session API
|
// @Tag Session API
|
||||||
// @Description Get session for one user in one application.
|
// @Description Get session for one user in one application.
|
||||||
// @Param id query string true "The id(organization/application/user) of session"
|
// @Param sessionPkId query string true "The id(organization/user/application) of session"
|
||||||
// @Success 200 {array} string The Response object
|
// @Success 200 {array} string The Response object
|
||||||
// @router /get-session [get]
|
// @router /get-session [get]
|
||||||
func (c *ApiController) GetSingleSession() {
|
func (c *ApiController) GetSingleSession() {
|
||||||
id := c.Input().Get("sessionPkId")
|
id := c.Input().Get("sessionPkId")
|
||||||
|
|
||||||
c.Data["json"] = object.GetSingleSession(id)
|
session, err := object.GetSingleSession(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSession
|
// UpdateSession
|
||||||
// @Title UpdateSession
|
// @Title UpdateSession
|
||||||
// @Tag Session API
|
// @Tag Session API
|
||||||
// @Description Update session for one user in one application.
|
// @Description Update session for one user in one application.
|
||||||
// @Param id query string true "The id(organization/application/user) of session"
|
// @Param id query string true "The id(organization/user/application) of session"
|
||||||
// @Success 200 {array} string The Response object
|
// @Success 200 {array} string The Response object
|
||||||
// @router /update-session [post]
|
// @router /update-session [post]
|
||||||
func (c *ApiController) UpdateSession() {
|
func (c *ApiController) UpdateSession() {
|
||||||
@@ -85,7 +106,7 @@ func (c *ApiController) UpdateSession() {
|
|||||||
// @Title AddSession
|
// @Title AddSession
|
||||||
// @Tag Session API
|
// @Tag Session API
|
||||||
// @Description Add session for one user in one application. If there are other existing sessions, join the session into the list.
|
// @Description Add session for one user in one application. If there are other existing sessions, join the session into the list.
|
||||||
// @Param id query string true "The id(organization/application/user) of session"
|
// @Param id query string true "The id(organization/user/application) of session"
|
||||||
// @Param sessionId query string true "sessionId to be added"
|
// @Param sessionId query string true "sessionId to be added"
|
||||||
// @Success 200 {array} string The Response object
|
// @Success 200 {array} string The Response object
|
||||||
// @router /add-session [post]
|
// @router /add-session [post]
|
||||||
@@ -105,7 +126,7 @@ func (c *ApiController) AddSession() {
|
|||||||
// @Title DeleteSession
|
// @Title DeleteSession
|
||||||
// @Tag Session API
|
// @Tag Session API
|
||||||
// @Description Delete session for one user in one application.
|
// @Description Delete session for one user in one application.
|
||||||
// @Param id query string true "The id(organization/application/user) of session"
|
// @Param id query string true "The id(organization/user/application) of session"
|
||||||
// @Success 200 {array} string The Response object
|
// @Success 200 {array} string The Response object
|
||||||
// @router /delete-session [post]
|
// @router /delete-session [post]
|
||||||
func (c *ApiController) DeleteSession() {
|
func (c *ApiController) DeleteSession() {
|
||||||
@@ -124,7 +145,7 @@ func (c *ApiController) DeleteSession() {
|
|||||||
// @Title IsSessionDuplicated
|
// @Title IsSessionDuplicated
|
||||||
// @Tag Session API
|
// @Tag Session API
|
||||||
// @Description Check if there are other different sessions for one user in one application.
|
// @Description Check if there are other different sessions for one user in one application.
|
||||||
// @Param id query string true "The id(organization/application/user) of session"
|
// @Param sessionPkId query string true "The id(organization/user/application) of session"
|
||||||
// @Param sessionId query string true "sessionId to be checked"
|
// @Param sessionId query string true "sessionId to be checked"
|
||||||
// @Success 200 {array} string The Response object
|
// @Success 200 {array} string The Response object
|
||||||
// @router /is-session-duplicated [get]
|
// @router /is-session-duplicated [get]
|
||||||
@@ -132,8 +153,11 @@ func (c *ApiController) IsSessionDuplicated() {
|
|||||||
id := c.Input().Get("sessionPkId")
|
id := c.Input().Get("sessionPkId")
|
||||||
sessionId := c.Input().Get("sessionId")
|
sessionId := c.Input().Get("sessionId")
|
||||||
|
|
||||||
isUserSessionDuplicated := object.IsSessionDuplicated(id, sessionId)
|
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
|
||||||
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ServeJSON()
|
c.ResponseOk(isUserSessionDuplicated)
|
||||||
}
|
}
|
||||||
|
|||||||
145
controllers/subscription.go
Normal file
145
controllers/subscription.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSubscriptions
|
||||||
|
// @Title GetSubscriptions
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description get subscriptions
|
||||||
|
// @Param owner query string true "The owner of subscriptions"
|
||||||
|
// @Success 200 {array} object.Subscription The Response object
|
||||||
|
// @router /get-subscriptions [get]
|
||||||
|
func (c *ApiController) GetSubscriptions() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
subscriptions, err := object.GetSubscriptions(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(subscriptions)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetSubscriptionCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
subscription, err := object.GetPaginationSubscriptions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(subscription, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubscription
|
||||||
|
// @Title GetSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description get subscription
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||||
|
// @Success 200 {object} object.Subscription The Response object
|
||||||
|
// @router /get-subscription [get]
|
||||||
|
func (c *ApiController) GetSubscription() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
subscription, err := object.GetSubscription(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSubscription
|
||||||
|
// @Title UpdateSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description update subscription
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the subscription"
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-subscription [post]
|
||||||
|
func (c *ApiController) UpdateSubscription() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateSubscription(id, &subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSubscription
|
||||||
|
// @Title AddSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description add subscription
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-subscription [post]
|
||||||
|
func (c *ApiController) AddSubscription() {
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddSubscription(&subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSubscription
|
||||||
|
// @Title DeleteSubscription
|
||||||
|
// @Tag Subscription API
|
||||||
|
// @Description delete subscription
|
||||||
|
// @Param body body object.Subscription true "The details of the subscription"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-subscription [post]
|
||||||
|
func (c *ApiController) DeleteSubscription() {
|
||||||
|
var subscription object.Subscription
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &subscription)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteSubscription(&subscription))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
@@ -37,13 +37,31 @@ func (c *ApiController) GetSyncers() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organization := c.Input().Get("organization")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetSyncers(owner)
|
syncers, err := object.GetMaskedSyncers(object.GetOrganizationSyncers(owner, organization))
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(syncers)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
|
count, err := object.GetSyncerCount(owner, organization, field, value)
|
||||||
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
syncers, err := object.GetMaskedSyncers(object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(syncers, paginator.Nums())
|
c.ResponseOk(syncers, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,8 +76,13 @@ func (c *ApiController) GetSyncers() {
|
|||||||
func (c *ApiController) GetSyncer() {
|
func (c *ApiController) GetSyncer() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetSyncer(id)
|
syncer, err := object.GetMaskedSyncer(object.GetSyncer(id))
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(syncer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSyncer
|
// UpdateSyncer
|
||||||
@@ -131,9 +154,34 @@ func (c *ApiController) DeleteSyncer() {
|
|||||||
// @router /run-syncer [get]
|
// @router /run-syncer [get]
|
||||||
func (c *ApiController) RunSyncer() {
|
func (c *ApiController) RunSyncer() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
syncer := object.GetSyncer(id)
|
syncer, err := object.GetSyncer(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
object.RunSyncer(syncer)
|
err = object.RunSyncer(syncer)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) TestSyncerDb() {
|
||||||
|
var syncer object.Syncer
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.TestSyncerDb(syncer)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,68 +15,64 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/casdoor/casdoor/object"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SystemInfo struct {
|
|
||||||
MemoryUsed uint64 `json:"memory_used"`
|
|
||||||
MemoryTotal uint64 `json:"memory_total"`
|
|
||||||
CpuUsage []float64 `json:"cpu_usage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSystemInfo
|
// GetSystemInfo
|
||||||
// @Title GetSystemInfo
|
// @Title GetSystemInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
// @Description get user's system info
|
// @Description get system info like CPU and memory usage
|
||||||
// @Param id query string true "The id ( owner/name ) of the user"
|
// @Success 200 {object} util.SystemInfo The Response object
|
||||||
// @Success 200 {object} object.SystemInfo The Response object
|
|
||||||
// @router /get-system-info [get]
|
// @router /get-system-info [get]
|
||||||
func (c *ApiController) GetSystemInfo() {
|
func (c *ApiController) GetSystemInfo() {
|
||||||
id := c.GetString("id")
|
_, ok := c.RequireAdmin()
|
||||||
if id == "" {
|
if !ok {
|
||||||
id = c.GetSessionUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
user := object.GetUser(id)
|
|
||||||
if user == nil || !user.IsGlobalAdmin {
|
|
||||||
c.ResponseError(c.T("auth:Unauthorized operation"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cpuUsage, err := util.GetCpuUsage()
|
systemInfo, err := util.GetSystemInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryUsed, memoryTotal, err := util.GetMemoryUsage()
|
c.ResponseOk(systemInfo)
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data["json"] = SystemInfo{
|
|
||||||
CpuUsage: cpuUsage,
|
|
||||||
MemoryUsed: memoryUsed,
|
|
||||||
MemoryTotal: memoryTotal,
|
|
||||||
}
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitRepoVersion
|
// GetVersionInfo
|
||||||
// @Title GitRepoVersion
|
// @Title GetVersionInfo
|
||||||
// @Tag System API
|
// @Tag System API
|
||||||
// @Description get local github repo's latest release version info
|
// @Description get version info like Casdoor release version and commit ID
|
||||||
// @Success 200 {string} local latest version hash of casdoor
|
// @Success 200 {object} util.VersionInfo The Response object
|
||||||
// @router /get-release [get]
|
// @router /get-version-info [get]
|
||||||
func (c *ApiController) GitRepoVersion() {
|
func (c *ApiController) GetVersionInfo() {
|
||||||
version, err := util.GetGitRepoVersion()
|
errInfo := ""
|
||||||
|
versionInfo, err := util.GetVersionInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
errInfo = "Git error: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if versionInfo.Version != "" {
|
||||||
|
c.ResponseOk(versionInfo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = version
|
versionInfo, err = util.GetVersionInfoFromFile()
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
errInfo = errInfo + ", File error: " + err.Error()
|
||||||
|
c.ResponseError(errInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(versionInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health
|
||||||
|
// @Title Health
|
||||||
|
// @Tag System API
|
||||||
|
// @Description check if the system is live
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /health [get]
|
||||||
|
func (c *ApiController) Health() {
|
||||||
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -39,13 +41,30 @@ func (c *ApiController) GetTokens() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organization := c.Input().Get("organization")
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetTokens(owner)
|
token, err := object.GetTokens(owner, organization)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(token)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
|
count, err := object.GetTokenCount(owner, organization, field, value)
|
||||||
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
tokens, err := object.GetPaginationTokens(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(tokens, paginator.Nums())
|
c.ResponseOk(tokens, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,9 +78,13 @@ func (c *ApiController) GetTokens() {
|
|||||||
// @router /get-token [get]
|
// @router /get-token [get]
|
||||||
func (c *ApiController) GetToken() {
|
func (c *ApiController) GetToken() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
token, err := object.GetToken(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.GetToken(id)
|
c.ResponseOk(token)
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateToken
|
// UpdateToken
|
||||||
@@ -124,40 +147,6 @@ func (c *ApiController) DeleteToken() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOAuthCode
|
|
||||||
// @Title GetOAuthCode
|
|
||||||
// @Tag Token API
|
|
||||||
// @Description get OAuth code
|
|
||||||
// @Param id query string true "The id ( owner/name ) of user"
|
|
||||||
// @Param client_id query string true "OAuth client id"
|
|
||||||
// @Param response_type query string true "OAuth response type"
|
|
||||||
// @Param redirect_uri query string true "OAuth redirect URI"
|
|
||||||
// @Param scope query string true "OAuth scope"
|
|
||||||
// @Param state query string true "OAuth state"
|
|
||||||
// @Success 200 {object} object.TokenWrapper The Response object
|
|
||||||
// @router /login/oauth/code [post]
|
|
||||||
func (c *ApiController) GetOAuthCode() {
|
|
||||||
userId := c.Input().Get("user_id")
|
|
||||||
clientId := c.Input().Get("client_id")
|
|
||||||
responseType := c.Input().Get("response_type")
|
|
||||||
redirectUri := c.Input().Get("redirect_uri")
|
|
||||||
scope := c.Input().Get("scope")
|
|
||||||
state := c.Input().Get("state")
|
|
||||||
nonce := c.Input().Get("nonce")
|
|
||||||
|
|
||||||
challengeMethod := c.Input().Get("code_challenge_method")
|
|
||||||
codeChallenge := c.Input().Get("code_challenge")
|
|
||||||
|
|
||||||
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
|
|
||||||
c.ResponseError(c.T("auth:Challenge method should be S256"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host := c.Ctx.Request.Host
|
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host, c.GetAcceptLanguage())
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOAuthToken
|
// GetOAuthToken
|
||||||
// @Title GetOAuthToken
|
// @Title GetOAuthToken
|
||||||
// @Tag Token API
|
// @Tag Token API
|
||||||
@@ -171,41 +160,116 @@ func (c *ApiController) GetOAuthCode() {
|
|||||||
// @Success 401 {object} object.TokenError The Response object
|
// @Success 401 {object} object.TokenError The Response object
|
||||||
// @router /login/oauth/access_token [post]
|
// @router /login/oauth/access_token [post]
|
||||||
func (c *ApiController) GetOAuthToken() {
|
func (c *ApiController) GetOAuthToken() {
|
||||||
grantType := c.Input().Get("grant_type")
|
|
||||||
refreshToken := c.Input().Get("refresh_token")
|
|
||||||
clientId := c.Input().Get("client_id")
|
clientId := c.Input().Get("client_id")
|
||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
|
grantType := c.Input().Get("grant_type")
|
||||||
code := c.Input().Get("code")
|
code := c.Input().Get("code")
|
||||||
verifier := c.Input().Get("code_verifier")
|
verifier := c.Input().Get("code_verifier")
|
||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
|
nonce := c.Input().Get("nonce")
|
||||||
username := c.Input().Get("username")
|
username := c.Input().Get("username")
|
||||||
password := c.Input().Get("password")
|
password := c.Input().Get("password")
|
||||||
tag := c.Input().Get("tag")
|
tag := c.Input().Get("tag")
|
||||||
avatar := c.Input().Get("avatar")
|
avatar := c.Input().Get("avatar")
|
||||||
|
refreshToken := c.Input().Get("refresh_token")
|
||||||
|
deviceCode := c.Input().Get("device_code")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
}
|
}
|
||||||
if clientId == "" {
|
|
||||||
// If clientID is empty, try to read data from RequestBody
|
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
|
||||||
|
// If clientId is empty, try to read data from RequestBody
|
||||||
var tokenRequest TokenRequest
|
var tokenRequest TokenRequest
|
||||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||||
clientId = tokenRequest.ClientId
|
if err == nil {
|
||||||
clientSecret = tokenRequest.ClientSecret
|
if clientId == "" {
|
||||||
grantType = tokenRequest.GrantType
|
clientId = tokenRequest.ClientId
|
||||||
refreshToken = tokenRequest.RefreshToken
|
}
|
||||||
code = tokenRequest.Code
|
if clientSecret == "" {
|
||||||
verifier = tokenRequest.Verifier
|
clientSecret = tokenRequest.ClientSecret
|
||||||
scope = tokenRequest.Scope
|
}
|
||||||
username = tokenRequest.Username
|
if grantType == "" {
|
||||||
password = tokenRequest.Password
|
grantType = tokenRequest.GrantType
|
||||||
tag = tokenRequest.Tag
|
}
|
||||||
avatar = tokenRequest.Avatar
|
if code == "" {
|
||||||
|
code = tokenRequest.Code
|
||||||
|
}
|
||||||
|
if verifier == "" {
|
||||||
|
verifier = tokenRequest.Verifier
|
||||||
|
}
|
||||||
|
if scope == "" {
|
||||||
|
scope = tokenRequest.Scope
|
||||||
|
}
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = tokenRequest.Nonce
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
username = tokenRequest.Username
|
||||||
|
}
|
||||||
|
if password == "" {
|
||||||
|
password = tokenRequest.Password
|
||||||
|
}
|
||||||
|
if tag == "" {
|
||||||
|
tag = tokenRequest.Tag
|
||||||
|
}
|
||||||
|
if avatar == "" {
|
||||||
|
avatar = tokenRequest.Avatar
|
||||||
|
}
|
||||||
|
if refreshToken == "" {
|
||||||
|
refreshToken = tokenRequest.RefreshToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
host := c.Ctx.Request.Host
|
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
if deviceCode != "" {
|
||||||
|
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
|
||||||
|
if !ok {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
|
||||||
|
if !deviceAuthCacheCast.UserSignIn {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "authorization_pending",
|
||||||
|
ErrorDescription: "authorization pending",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: "expired_token",
|
||||||
|
ErrorDescription: "token is expired",
|
||||||
|
}
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
object.DeviceAuthMap.Delete(deviceCode)
|
||||||
|
|
||||||
|
username = deviceAuthCacheCast.UserName
|
||||||
|
}
|
||||||
|
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = token
|
||||||
c.SetTokenErrorHttpStatus()
|
c.SetTokenErrorHttpStatus()
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -243,13 +307,28 @@ func (c *ApiController) RefreshToken() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = refreshToken2
|
||||||
|
c.SetTokenErrorHttpStatus()
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) ResponseTokenError(errorMsg string) {
|
||||||
|
c.Data["json"] = &object.TokenError{
|
||||||
|
Error: errorMsg,
|
||||||
|
}
|
||||||
c.SetTokenErrorHttpStatus()
|
c.SetTokenErrorHttpStatus()
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IntrospectToken
|
// IntrospectToken
|
||||||
// @Title IntrospectToken
|
// @Title IntrospectToken
|
||||||
|
// @Tag Login API
|
||||||
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||||
// parameter representing an OAuth 2.0 token and returns a JSON document
|
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||||
// representing the meta information surrounding the
|
// representing the meta information surrounding the
|
||||||
@@ -269,53 +348,133 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
clientId = c.Input().Get("client_id")
|
clientId = c.Input().Get("client_id")
|
||||||
clientSecret = c.Input().Get("client_secret")
|
clientSecret = c.Input().Get("client_secret")
|
||||||
if clientId == "" || clientSecret == "" {
|
if clientId == "" || clientSecret == "" {
|
||||||
c.ResponseError(c.T("token:Empty clientId or clientSecret"))
|
c.ResponseTokenError(object.InvalidRequest)
|
||||||
c.Data["json"] = &object.TokenError{
|
return
|
||||||
Error: object.InvalidRequest,
|
}
|
||||||
}
|
}
|
||||||
c.SetTokenErrorHttpStatus()
|
|
||||||
|
application, err := object.GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseTokenError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil || application.ClientSecret != clientSecret {
|
||||||
|
c.ResponseTokenError(c.T("token:Invalid application or wrong clientSecret"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respondWithInactiveToken := func() {
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenTypeHint := c.Input().Get("token_type_hint")
|
||||||
|
var token *object.Token
|
||||||
|
if tokenTypeHint != "" {
|
||||||
|
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseTokenError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == nil || token.ExpiresIn <= 0 {
|
||||||
|
respondWithInactiveToken()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.ExpiresIn <= 0 {
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
application := object.GetApplicationByClientId(clientId)
|
|
||||||
if application == nil || application.ClientSecret != clientSecret {
|
var introspectionResponse object.IntrospectionResponse
|
||||||
c.ResponseError(c.T("token:Invalid application or wrong clientSecret"))
|
|
||||||
c.Data["json"] = &object.TokenError{
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
Error: object.InvalidClient,
|
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
|
||||||
|
if err != nil {
|
||||||
|
// and token revoked case. but we not implement
|
||||||
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
|
respondWithInactiveToken()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
introspectionResponse = object.IntrospectionResponse{
|
||||||
|
Active: true,
|
||||||
|
Scope: jwtToken.Scope,
|
||||||
|
ClientId: clientId,
|
||||||
|
Username: jwtToken.Name,
|
||||||
|
TokenType: jwtToken.TokenType,
|
||||||
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
|
Sub: jwtToken.Subject,
|
||||||
|
Aud: jwtToken.Audience,
|
||||||
|
Iss: jwtToken.Issuer,
|
||||||
|
Jti: jwtToken.ID,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||||
|
if err != nil {
|
||||||
|
// and token revoked case. but we not implement
|
||||||
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
|
respondWithInactiveToken()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
introspectionResponse = object.IntrospectionResponse{
|
||||||
|
Active: true,
|
||||||
|
ClientId: clientId,
|
||||||
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
|
Sub: jwtToken.Subject,
|
||||||
|
Aud: jwtToken.Audience,
|
||||||
|
Iss: jwtToken.Issuer,
|
||||||
|
Jti: jwtToken.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwtToken.Scope != "" {
|
||||||
|
introspectionResponse.Scope = jwtToken.Scope
|
||||||
|
}
|
||||||
|
if jwtToken.Name != "" {
|
||||||
|
introspectionResponse.Username = jwtToken.Name
|
||||||
|
}
|
||||||
|
if jwtToken.TokenType != "" {
|
||||||
|
introspectionResponse.TokenType = jwtToken.TokenType
|
||||||
}
|
}
|
||||||
c.SetTokenErrorHttpStatus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
|
||||||
if token == nil {
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
|
||||||
if err != nil || jwtToken.Valid() != nil {
|
|
||||||
// and token revoked case. but we not implement
|
|
||||||
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
|
||||||
// refs: https://tools.ietf.org/html/rfc7009
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
|
||||||
c.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = &object.IntrospectionResponse{
|
if tokenTypeHint == "" {
|
||||||
Active: true,
|
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
|
||||||
Scope: jwtToken.Scope,
|
if err != nil {
|
||||||
ClientId: clientId,
|
c.ResponseTokenError(err.Error())
|
||||||
Username: token.User,
|
return
|
||||||
TokenType: token.TokenType,
|
}
|
||||||
Exp: jwtToken.ExpiresAt.Unix(),
|
if token == nil || token.ExpiresIn <= 0 {
|
||||||
Iat: jwtToken.IssuedAt.Unix(),
|
respondWithInactiveToken()
|
||||||
Nbf: jwtToken.NotBefore.Unix(),
|
return
|
||||||
Sub: jwtToken.Subject,
|
}
|
||||||
Aud: jwtToken.Audience,
|
|
||||||
Iss: jwtToken.Issuer,
|
|
||||||
Jti: jwtToken.Id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if token != nil {
|
||||||
|
application, err = object.GetApplication(fmt.Sprintf("%s/%s", token.Owner, token.Application))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseTokenError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), token.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
introspectionResponse.TokenType = token.TokenType
|
||||||
|
introspectionResponse.ClientId = application.ClientId
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = introspectionResponse
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
167
controllers/transaction.go
Normal file
167
controllers/transaction.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTransactions
|
||||||
|
// @Title GetTransactions
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description get transactions
|
||||||
|
// @Param owner query string true "The owner of transactions"
|
||||||
|
// @Success 200 {array} object.Transaction The Response object
|
||||||
|
// @router /get-transactions [get]
|
||||||
|
func (c *ApiController) GetTransactions() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
transactions, err := object.GetTransactions(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(transactions)
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetTransactionCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
transactions, err := object.GetPaginationTransactions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(transactions, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTransactions
|
||||||
|
// @Title GetUserTransaction
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description get transactions for a user
|
||||||
|
// @Param owner query string true "The owner of transactions"
|
||||||
|
// @Param organization query string true "The organization of the user"
|
||||||
|
// @Param user query string true "The username of the user"
|
||||||
|
// @Success 200 {array} object.Transaction The Response object
|
||||||
|
// @router /get-user-transactions [get]
|
||||||
|
func (c *ApiController) GetUserTransactions() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
user := c.Input().Get("user")
|
||||||
|
|
||||||
|
transactions, err := object.GetUserTransactions(owner, user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(transactions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction
|
||||||
|
// @Title GetTransaction
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description get transaction
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the transaction"
|
||||||
|
// @Success 200 {object} object.Transaction The Response object
|
||||||
|
// @router /get-transaction [get]
|
||||||
|
func (c *ApiController) GetTransaction() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
transaction, err := object.GetTransaction(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTransaction
|
||||||
|
// @Title UpdateTransaction
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description update transaction
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the transaction"
|
||||||
|
// @Param body body object.Transaction true "The details of the transaction"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-transaction [post]
|
||||||
|
func (c *ApiController) UpdateTransaction() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var transaction object.Transaction
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateTransaction(id, &transaction))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTransaction
|
||||||
|
// @Title AddTransaction
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description add transaction
|
||||||
|
// @Param body body object.Transaction true "The details of the transaction"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-transaction [post]
|
||||||
|
func (c *ApiController) AddTransaction() {
|
||||||
|
var transaction object.Transaction
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddTransaction(&transaction))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTransaction
|
||||||
|
// @Title DeleteTransaction
|
||||||
|
// @Tag Transaction API
|
||||||
|
// @Description delete transaction
|
||||||
|
// @Param body body object.Transaction true "The details of the transaction"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-transaction [post]
|
||||||
|
func (c *ApiController) DeleteTransaction() {
|
||||||
|
var transaction object.Transaction
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteTransaction(&transaction))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
@@ -15,12 +15,13 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
type TokenRequest struct {
|
type TokenRequest struct {
|
||||||
GrantType string `json:"grant_type"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
ClientId string `json:"client_id"`
|
ClientId string `json:"client_id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
Verifier string `json:"code_verifier"`
|
Verifier string `json:"code_verifier"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@@ -37,14 +38,36 @@ func (c *ApiController) GetGlobalUsers() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
|
users, err := object.GetMaskedUsers(object.GetGlobalUsers())
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(users)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
count, err := object.GetGlobalUserCount(field, value)
|
||||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
users = object.GetMaskedUsers(users)
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
users, err := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err = object.GetMaskedUsers(users)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(users, paginator.Nums())
|
c.ResponseOk(users, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,20 +81,53 @@ func (c *ApiController) GetGlobalUsers() {
|
|||||||
// @router /get-users [get]
|
// @router /get-users [get]
|
||||||
func (c *ApiController) GetUsers() {
|
func (c *ApiController) GetUsers() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
|
groupName := c.Input().Get("groupName")
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
page := c.Input().Get("p")
|
page := c.Input().Get("p")
|
||||||
field := c.Input().Get("field")
|
field := c.Input().Get("field")
|
||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
|
if groupName != "" {
|
||||||
c.ServeJSON()
|
users, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(users)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err := object.GetMaskedUsers(object.GetUsers(owner))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(users)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
count, err := object.GetUserCount(owner, field, value, groupName)
|
||||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
users = object.GetMaskedUsers(users)
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err = object.GetMaskedUsers(users)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(users, paginator.Nums())
|
c.ResponseOk(users, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,10 +136,11 @@ func (c *ApiController) GetUsers() {
|
|||||||
// @Title GetUser
|
// @Title GetUser
|
||||||
// @Tag User API
|
// @Tag User API
|
||||||
// @Description get user
|
// @Description get user
|
||||||
// @Param id query string true "The id ( owner/name ) of the user"
|
// @Param id query string false "The id ( owner/name ) of the user"
|
||||||
// @Param owner query string false "The owner of the user"
|
// @Param owner query string false "The owner of the user"
|
||||||
// @Param email query string false "The email of the user"
|
// @Param email query string false "The email of the user"
|
||||||
// @Param phone query string false "The phone of the user"
|
// @Param phone query string false "The phone of the user"
|
||||||
|
// @Param userId query string false "The userId of the user"
|
||||||
// @Success 200 {object} object.User The Response object
|
// @Success 200 {object} object.User The Response object
|
||||||
// @router /get-user [get]
|
// @router /get-user [get]
|
||||||
func (c *ApiController) GetUser() {
|
func (c *ApiController) GetUser() {
|
||||||
@@ -91,38 +148,104 @@ func (c *ApiController) GetUser() {
|
|||||||
email := c.Input().Get("email")
|
email := c.Input().Get("email")
|
||||||
phone := c.Input().Get("phone")
|
phone := c.Input().Get("phone")
|
||||||
userId := c.Input().Get("userId")
|
userId := c.Input().Get("userId")
|
||||||
|
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
if owner == "" {
|
var err error
|
||||||
owner, _ = util.GetOwnerAndNameFromId(id)
|
var userFromUserId *object.User
|
||||||
|
if userId != "" && owner != "" {
|
||||||
|
userFromUserId, err = object.GetUserByUserId(owner, userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userFromUserId == nil {
|
||||||
|
c.ResponseOk(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
|
var user *object.User
|
||||||
if !organization.IsProfilePublic {
|
if id == "" && owner == "" {
|
||||||
requestUserId := c.GetSessionUsername()
|
switch {
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false, c.GetAcceptLanguage())
|
case email != "":
|
||||||
if !hasPermission {
|
user, err = object.GetUserByEmailOnly(email)
|
||||||
|
case phone != "":
|
||||||
|
user, err = object.GetUserByPhoneOnly(phone)
|
||||||
|
case userId != "":
|
||||||
|
user, err = object.GetUserByUserIdOnly(userId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if owner == "" {
|
||||||
|
owner = util.GetOwnerFromId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case email != "":
|
||||||
|
user, err = object.GetUserByEmail(owner, email)
|
||||||
|
case phone != "":
|
||||||
|
user, err = object.GetUserByPhone(owner, phone)
|
||||||
|
case userId != "":
|
||||||
|
user = userFromUserId
|
||||||
|
default:
|
||||||
|
user, err = object.GetUser(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var organization *object.Organization
|
||||||
|
if user != nil {
|
||||||
|
organization, err = object.GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !organization.IsProfilePublic {
|
||||||
|
requestUserId := c.GetSessionUsername()
|
||||||
|
var hasPermission bool
|
||||||
|
hasPermission, err = object.CheckUserPermission(requestUserId, user.GetId(), false, c.GetAcceptLanguage())
|
||||||
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.ExtendUserWithRolesAndPermissions(user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdminOrSelf := c.IsAdminOrSelf(user)
|
||||||
|
user, err = object.GetMaskedUser(user, isAdminOrSelf)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization != nil && user != nil {
|
||||||
|
user, err = object.GetFilteredUser(user, c.IsAdmin(), c.IsAdminOrSelf(user), organization.AccountItems)
|
||||||
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
c.ResponseOk(user)
|
||||||
switch {
|
|
||||||
case email != "":
|
|
||||||
user = object.GetUserByEmail(owner, email)
|
|
||||||
case phone != "":
|
|
||||||
user = object.GetUserByPhone(owner, phone)
|
|
||||||
case userId != "":
|
|
||||||
user = object.GetUserByUserId(owner, userId)
|
|
||||||
default:
|
|
||||||
user = object.GetUser(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
object.ExtendUserWithRolesAndPermissions(user)
|
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedUser(user)
|
|
||||||
c.ServeJSON()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser
|
// UpdateUser
|
||||||
@@ -137,10 +260,6 @@ func (c *ApiController) UpdateUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
columnsStr := c.Input().Get("columns")
|
columnsStr := c.Input().Get("columns")
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
id = c.GetSessionUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -148,26 +267,73 @@ func (c *ApiController) UpdateUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg := object.CheckUpdateUser(object.GetUser(id), &user, c.GetAcceptLanguage()); msg != "" {
|
if id == "" {
|
||||||
|
id = c.GetSessionUsername()
|
||||||
|
if id == "" {
|
||||||
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldUser, err := object.GetUser(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.Owner == "built-in" && oldUser.Name == "admin" && (user.Owner != "built-in" || user.Name != "admin") {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.MfaEmailEnabled && user.Email == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.MfaPhoneEnabled && user.Phone == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA phone is enabled but phone number is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
|
||||||
|
if isUsernameLowered {
|
||||||
|
user.Name = strings.ToLower(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin := c.IsAdmin()
|
||||||
|
allowDisplayNameEmpty := c.Input().Get("allowEmpty") != ""
|
||||||
|
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, allowDisplayNameEmpty, c.GetAcceptLanguage()); !pass {
|
||||||
|
c.ResponseError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
columns := []string{}
|
columns := []string{}
|
||||||
if columnsStr != "" {
|
if columnsStr != "" {
|
||||||
columns = strings.Split(columnsStr, ",")
|
columns = strings.Split(columnsStr, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
isGlobalAdmin := c.IsGlobalAdmin()
|
affected, err := object.UpdateUser(id, &user, columns, isAdmin)
|
||||||
|
if err != nil {
|
||||||
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
|
c.ResponseError(err.Error())
|
||||||
c.ResponseError(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
|
||||||
if affected {
|
if affected {
|
||||||
object.UpdateUserToOriginalDatabase(&user)
|
err = object.UpdateUserToOriginalDatabase(&user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(affected)
|
c.Data["json"] = wrapActionResponse(affected)
|
||||||
@@ -189,19 +355,19 @@ func (c *ApiController) AddUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := object.GetUserCount("", "", "")
|
if err := checkQuotaForUser(); err != nil {
|
||||||
if err := checkQuotaForUser(count); err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
|
emptyUser := object.User{}
|
||||||
|
msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
|
c.Data["json"] = wrapActionResponse(object.AddUser(&user, c.GetAcceptLanguage()))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +386,11 @@ func (c *ApiController) DeleteUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Owner == "built-in" && user.Name == "admin" {
|
||||||
|
c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
|
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
@@ -236,7 +407,18 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
organization := c.Ctx.Request.Form.Get("organization")
|
organization := c.Ctx.Request.Form.Get("organization")
|
||||||
username := c.Ctx.Request.Form.Get("username")
|
username := c.Ctx.Request.Form.Get("username")
|
||||||
|
|
||||||
user := object.GetUserByFields(organization, username)
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
c.ResponseError("Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := object.GetUserByFields(organization, username)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(organization, username)))
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(organization, username)))
|
||||||
return
|
return
|
||||||
@@ -275,38 +457,143 @@ func (c *ApiController) SetPassword() {
|
|||||||
userName := c.Ctx.Request.Form.Get("userName")
|
userName := c.Ctx.Request.Form.Get("userName")
|
||||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
code := c.Ctx.Request.Form.Get("code")
|
||||||
|
|
||||||
requestUserId := c.GetSessionUsername()
|
// if userOwner == "built-in" && userName == "admin" {
|
||||||
userId := util.GetId(userOwner, userName)
|
// c.ResponseError(c.T("auth:Unauthorized operation"))
|
||||||
|
// return
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
|
// }
|
||||||
if !hasPermission {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetUser := object.GetUser(userId)
|
|
||||||
|
|
||||||
if oldPassword != "" {
|
|
||||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
|
||||||
if msg != "" {
|
|
||||||
c.ResponseError(msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(newPassword, " ") {
|
if strings.Contains(newPassword, " ") {
|
||||||
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
c.ResponseError(c.T("user:New password cannot contain blank space."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(newPassword) <= 5 {
|
userId := util.GetId(userOwner, userName)
|
||||||
c.ResponseError(c.T("user:New password must have at least 6 characters"))
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestUserId := c.GetSessionUsername()
|
||||||
|
if requestUserId == "" && code == "" {
|
||||||
|
c.ResponseError(c.T("general:Please login first"), "Please login first")
|
||||||
|
return
|
||||||
|
} else if code == "" {
|
||||||
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
|
||||||
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if code != c.GetSession("verifiedCode") {
|
||||||
|
c.ResponseError(c.T("general:Missing parameter"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userId != c.GetSession("verifiedUserId") {
|
||||||
|
c.ResponseError(c.T("general:Wrong userId"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.SetSession("verifiedCode", "")
|
||||||
|
c.SetSession("verifiedUserId", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUser, err := object.GetUser(userId)
|
||||||
|
if targetUser == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin := c.IsAdmin()
|
||||||
|
if isAdmin {
|
||||||
|
if oldPassword != "" {
|
||||||
|
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if code == "" {
|
||||||
|
if user.Ldap == "" {
|
||||||
|
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
|
} else {
|
||||||
|
err = object.CheckLdapUserPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := object.CheckPasswordComplexity(targetUser, newPassword)
|
||||||
|
if msg != "" {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, err := object.GetOrganizationByUser(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:the organization: %s is not found"), targetUser.Owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the new password is the same as the current password
|
||||||
|
if !object.CheckPasswordNotSameAsCurrent(targetUser, newPassword, organization) {
|
||||||
|
c.ResponseError(c.T("user:The new password must be different from your current password"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplicationByUser(targetUser)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:the application for user %s is not found"), userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
|
err = object.CheckEntryIp(clientIp, targetUser, application, organization, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetUser.Password = newPassword
|
targetUser.Password = newPassword
|
||||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
targetUser.UpdateUserPassword(organization)
|
||||||
|
targetUser.NeedUpdatePassword = false
|
||||||
|
targetUser.LastChangePasswordTime = util.GetCurrentTime()
|
||||||
|
|
||||||
|
if user.Ldap == "" {
|
||||||
|
_, err = object.UpdateUser(userId, targetUser, []string{"password", "password_salt", "need_update_password", "password_type", "last_change_password_time"}, false)
|
||||||
|
} else {
|
||||||
|
if isAdmin {
|
||||||
|
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())
|
||||||
|
} else {
|
||||||
|
err = object.ResetLdapPassword(targetUser, oldPassword, newPassword, c.GetAcceptLanguage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +601,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
// @Title CheckUserPassword
|
// @Title CheckUserPassword
|
||||||
// @router /check-user-password [post]
|
// @router /check-user-password [post]
|
||||||
// @Tag User API
|
// @Tag User API
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
func (c *ApiController) CheckUserPassword() {
|
func (c *ApiController) CheckUserPassword() {
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
@@ -322,11 +610,15 @@ func (c *ApiController) CheckUserPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
/*
|
||||||
if msg == "" {
|
* Verified password with user as subject, if field ldap not empty,
|
||||||
c.ResponseOk()
|
* then `isPasswordWithLdapEnabled` is true
|
||||||
|
*/
|
||||||
|
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage(), false, false, user.Ldap != "")
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(msg)
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,8 +636,13 @@ func (c *ApiController) GetSortedUsers() {
|
|||||||
sorter := c.Input().Get("sorter")
|
sorter := c.Input().Get("sorter")
|
||||||
limit := util.ParseInt(c.Input().Get("limit"))
|
limit := util.ParseInt(c.Input().Get("limit"))
|
||||||
|
|
||||||
c.Data["json"] = object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
users, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserCount
|
// GetUserCount
|
||||||
@@ -360,13 +657,65 @@ func (c *ApiController) GetUserCount() {
|
|||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
isOnline := c.Input().Get("isOnline")
|
isOnline := c.Input().Get("isOnline")
|
||||||
|
|
||||||
count := 0
|
var count int64
|
||||||
|
var err error
|
||||||
if isOnline == "" {
|
if isOnline == "" {
|
||||||
count = object.GetUserCount(owner, "", "")
|
count, err = object.GetUserCount(owner, "", "", "")
|
||||||
} else {
|
} else {
|
||||||
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
|
count, err = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = count
|
c.ResponseOk(count)
|
||||||
c.ServeJSON()
|
}
|
||||||
|
|
||||||
|
// AddUserKeys
|
||||||
|
// @Title AddUserKeys
|
||||||
|
// @router /add-user-keys [post]
|
||||||
|
// @Tag User API
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
|
func (c *ApiController) AddUserKeys() {
|
||||||
|
var user object.User
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdmin := c.IsAdmin()
|
||||||
|
affected, err := object.AddUserKeys(&user, isAdmin)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) RemoveUserFromGroup() {
|
||||||
|
owner := c.Ctx.Request.Form.Get("owner")
|
||||||
|
name := c.Ctx.Request.Form.Get("name")
|
||||||
|
groupName := c.Ctx.Request.Form.Get("groupName")
|
||||||
|
|
||||||
|
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item := object.GetAccountItemByName("Groups", organization)
|
||||||
|
res, msg := object.CheckAccountItemModifyRule(item, c.IsAdmin(), c.GetAcceptLanguage())
|
||||||
|
if !res {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := object.DeleteGroupForUser(util.GetId(owner, name), util.GetId(owner, groupName))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(affected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func saveFile(path string, file *multipart.File) (err error) {
|
func saveFile(path string, file *multipart.File) (err error) {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(filepath.Clean(path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -47,20 +48,25 @@ func (c *ApiController) UploadUsers() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
|
||||||
|
|
||||||
|
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||||
path := util.GetUploadXlsxPath(fileId)
|
path := util.GetUploadXlsxPath(fileId)
|
||||||
util.EnsureFileFolderExists(path)
|
defer os.Remove(path)
|
||||||
err = saveFile(path, &file)
|
err = saveFile(path, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.UploadUsers(owner, fileId)
|
affected, err := object.UploadUsers(owner, path)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if affected {
|
if affected {
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(c.T("user_upload:Failed to import users"))
|
c.ResponseError(c.T("general:Failed to import users"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
// Copyright 2023 The Casdoor Authors. 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 controllers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiController) (bool, string) {
|
|
||||||
oldUser := object.GetUser(userId)
|
|
||||||
organization := object.GetOrganizationByUser(oldUser)
|
|
||||||
var itemsChanged []*object.AccountItem
|
|
||||||
|
|
||||||
if oldUser.Owner != newUser.Owner {
|
|
||||||
item := object.GetAccountItemByName("Organization", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Name != newUser.Name {
|
|
||||||
item := object.GetAccountItemByName("Name", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Id != newUser.Id {
|
|
||||||
item := object.GetAccountItemByName("ID", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
|
||||||
item := object.GetAccountItemByName("Display name", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
|
||||||
item := object.GetAccountItemByName("Avatar", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Type != newUser.Type {
|
|
||||||
item := object.GetAccountItemByName("User type", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
// The password is *** when not modified
|
|
||||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
|
||||||
item := object.GetAccountItemByName("Password", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Email != newUser.Email {
|
|
||||||
item := object.GetAccountItemByName("Email", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Phone != newUser.Phone {
|
|
||||||
item := object.GetAccountItemByName("Phone", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
|
||||||
item := object.GetAccountItemByName("Country code", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Region != newUser.Region {
|
|
||||||
item := object.GetAccountItemByName("Country/Region", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Location != newUser.Location {
|
|
||||||
item := object.GetAccountItemByName("Location", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
|
||||||
item := object.GetAccountItemByName("Affiliation", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Title != newUser.Title {
|
|
||||||
item := object.GetAccountItemByName("Title", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
|
||||||
item := object.GetAccountItemByName("Homepage", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Bio != newUser.Bio {
|
|
||||||
item := object.GetAccountItemByName("Bio", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.Tag != newUser.Tag {
|
|
||||||
item := object.GetAccountItemByName("Tag", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
|
||||||
item := object.GetAccountItemByName("Signup application", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
|
||||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
|
||||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
|
||||||
item := object.GetAccountItemByName("Properties", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
|
||||||
item := object.GetAccountItemByName("Is admin", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
|
|
||||||
item := object.GetAccountItemByName("Is global admin", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
|
||||||
item := object.GetAccountItemByName("Is forbidden", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
|
||||||
item := object.GetAccountItemByName("Is deleted", organization)
|
|
||||||
itemsChanged = append(itemsChanged, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUser := c.getCurrentUser()
|
|
||||||
if currentUser == nil && c.IsGlobalAdmin() {
|
|
||||||
currentUser = &object.User{
|
|
||||||
IsGlobalAdmin: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range itemsChanged {
|
|
||||||
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], currentUser, c.GetAcceptLanguage()); !pass {
|
|
||||||
return pass, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, ""
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
@@ -45,6 +45,15 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
|||||||
|
|
||||||
// ResponseError ...
|
// ResponseError ...
|
||||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
error = c.T("subscription:Error")
|
||||||
|
|
||||||
|
resp := &Response{Status: "error", Msg: error}
|
||||||
|
c.ResponseJsonData(resp, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resp := &Response{Status: "error", Msg: error}
|
resp := &Response{Status: "error", Msg: error}
|
||||||
c.ResponseJsonData(resp, data...)
|
c.ResponseJsonData(resp, data...)
|
||||||
}
|
}
|
||||||
@@ -56,6 +65,9 @@ func (c *ApiController) T(error string) string {
|
|||||||
// GetAcceptLanguage ...
|
// GetAcceptLanguage ...
|
||||||
func (c *ApiController) GetAcceptLanguage() string {
|
func (c *ApiController) GetAcceptLanguage() string {
|
||||||
language := c.Ctx.Request.Header.Get("Accept-Language")
|
language := c.Ctx.Request.Header.Get("Accept-Language")
|
||||||
|
if len(language) > 2 {
|
||||||
|
language = language[0:2]
|
||||||
|
}
|
||||||
return conf.GetLanguage(language)
|
return conf.GetLanguage(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,12 +105,24 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
user := object.GetUser(userId)
|
if object.IsAppUser(userId) {
|
||||||
|
tmpUserId := c.Input().Get("userId")
|
||||||
|
if tmpUserId != "" {
|
||||||
|
userId = tmpUserId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ClearUserSession()
|
c.ClearUserSession()
|
||||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, true
|
return user, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,46 +136,122 @@ func (c *ApiController) RequireAdmin() (string, bool) {
|
|||||||
if user.Owner == "built-in" {
|
if user.Owner == "built-in" {
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !user.IsAdmin {
|
||||||
|
c.ResponseError(c.T("general:this operation requires administrator to perform"))
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
return user.Owner, true
|
return user.Owner, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInitScore(organization *object.Organization) (int, error) {
|
func (c *ApiController) IsOrgAdmin() (bool, bool) {
|
||||||
if organization != nil {
|
userId, ok := c.RequireSignedIn()
|
||||||
return organization.InitScore, nil
|
if !ok {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if object.IsAppUser(userId) {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
c.ClearUserSession()
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.IsAdmin, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMaskedEnabled ...
|
||||||
|
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
|
||||||
|
isMaskEnabled := true
|
||||||
|
withSecret := c.Input().Get("withSecret")
|
||||||
|
if withSecret == "1" {
|
||||||
|
isMaskEnabled = false
|
||||||
|
|
||||||
|
if conf.IsDemoMode() {
|
||||||
|
c.ResponseError(c.T("general:this operation is not allowed in demo mode"))
|
||||||
|
return false, isMaskEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return false, isMaskEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, isMaskEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineFullFilePath(fullFilePath string) (string, string) {
|
||||||
|
tokens := strings.Split(fullFilePath, "/")
|
||||||
|
if len(tokens) >= 2 && tokens[0] == "Direct" && tokens[1] != "" {
|
||||||
|
providerName := tokens[1]
|
||||||
|
res := strings.Join(tokens[2:], "/")
|
||||||
|
return providerName, "/" + res
|
||||||
} else {
|
} else {
|
||||||
return strconv.Atoi(conf.GetConfigString("initScore"))
|
return "", fullFilePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
|
||||||
providerName := c.Input().Get("provider")
|
providerName := c.Input().Get("provider")
|
||||||
if providerName != "" {
|
if providerName == "" {
|
||||||
provider := object.GetProvider(util.GetId("admin", providerName))
|
field := c.Input().Get("field")
|
||||||
if provider == nil {
|
value := c.Input().Get("value")
|
||||||
c.ResponseError(c.T("util:The provider: %s is not found"), providerName)
|
if field == "provider" && value != "" {
|
||||||
return nil, nil, false
|
providerName = value
|
||||||
|
} else {
|
||||||
|
fullFilePath := c.Input().Get("fullFilePath")
|
||||||
|
providerName, _ = refineFullFilePath(fullFilePath)
|
||||||
}
|
}
|
||||||
return provider, nil, true
|
}
|
||||||
|
|
||||||
|
if providerName != "" {
|
||||||
|
provider, err := object.GetProvider(util.GetId("admin", providerName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider == nil {
|
||||||
|
err = fmt.Errorf(c.T("util:The provider: %s is not found"), providerName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, ok := c.RequireSignedIn()
|
userId, ok := c.RequireSignedIn()
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, false
|
return nil, fmt.Errorf(c.T("general:Please login first"))
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplicationByUserId(userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
application, user := object.GetApplicationByUserId(userId)
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("util:No application is found for userId: %s"), userId))
|
return nil, fmt.Errorf(c.T("util:No application is found for userId: %s"), userId)
|
||||||
return nil, nil, false
|
}
|
||||||
|
|
||||||
|
provider, err := application.GetProviderByCategory(category)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := application.GetProviderByCategory(category)
|
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name))
|
return nil, fmt.Errorf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name)
|
||||||
return nil, nil, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider, user, true
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkQuotaForApplication(count int) error {
|
func checkQuotaForApplication(count int) error {
|
||||||
@@ -187,13 +287,30 @@ func checkQuotaForProvider(count int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkQuotaForUser(count int) error {
|
func checkQuotaForUser() error {
|
||||||
quota := conf.GetConfigQuota().User
|
quota := conf.GetConfigQuota().User
|
||||||
if quota == -1 {
|
if quota == -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if count >= quota {
|
|
||||||
|
count, err := object.GetUserCount("", "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(count) >= quota {
|
||||||
return fmt.Errorf("user quota is exceeded")
|
return fmt.Errorf("user quota is exceeded")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInvalidSmsReceivers(smsForm SmsForm) []string {
|
||||||
|
var invalidReceivers []string
|
||||||
|
for _, receiver := range smsForm.Receivers {
|
||||||
|
// The receiver phone format: E164 like +8613854673829 +441932567890
|
||||||
|
if !util.IsPhoneValid(receiver, "") {
|
||||||
|
invalidReceivers = append(invalidReceivers, receiver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invalidReceivers
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,147 +15,296 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/captcha"
|
"github.com/casdoor/casdoor/captcha"
|
||||||
|
"github.com/casdoor/casdoor/form"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SignupVerification = "signup"
|
SignupVerification = "signup"
|
||||||
ResetVerification = "reset"
|
ResetVerification = "reset"
|
||||||
LoginVerification = "login"
|
LoginVerification = "login"
|
||||||
ForgetVerification = "forget"
|
ForgetVerification = "forget"
|
||||||
|
MfaSetupVerification = "mfaSetup"
|
||||||
|
MfaAuthVerification = "mfaAuth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *ApiController) getCurrentUser() *object.User {
|
// GetVerifications
|
||||||
var user *object.User
|
// @Title GetVerifications
|
||||||
userId := c.GetSessionUsername()
|
// @Tag Verification API
|
||||||
if userId == "" {
|
// @Description get payments
|
||||||
user = nil
|
// @Param owner query string true "The owner of payments"
|
||||||
|
// @Success 200 {array} object.Verification The Response object
|
||||||
|
// @router /get-payments [get]
|
||||||
|
func (c *ApiController) GetVerifications() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
payments, err := object.GetVerifications(owner)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payments)
|
||||||
} else {
|
} else {
|
||||||
user = object.GetUser(userId)
|
limit := util.ParseInt(limit)
|
||||||
|
count, err := object.GetVerificationCount(owner, field, value)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
payments, err := object.GetPaginationVerifications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payments, paginator.Nums())
|
||||||
}
|
}
|
||||||
return user
|
}
|
||||||
|
|
||||||
|
// GetUserVerifications
|
||||||
|
// @Title GetUserVerifications
|
||||||
|
// @Tag Verification API
|
||||||
|
// @Description get payments for a user
|
||||||
|
// @Param owner query string true "The owner of payments"
|
||||||
|
// @Param organization query string true "The organization of the user"
|
||||||
|
// @Param user query string true "The username of the user"
|
||||||
|
// @Success 200 {array} object.Verification The Response object
|
||||||
|
// @router /get-user-payments [get]
|
||||||
|
func (c *ApiController) GetUserVerifications() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
user := c.Input().Get("user")
|
||||||
|
|
||||||
|
payments, err := object.GetUserVerifications(owner, user)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVerification
|
||||||
|
// @Title GetVerification
|
||||||
|
// @Tag Verification API
|
||||||
|
// @Description get payment
|
||||||
|
// @Param id query string true "The id ( owner/name ) of the payment"
|
||||||
|
// @Success 200 {object} object.Verification The Response object
|
||||||
|
// @router /get-payment [get]
|
||||||
|
func (c *ApiController) GetVerification() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
payment, err := object.GetVerification(id)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendVerificationCode ...
|
// SendVerificationCode ...
|
||||||
// @Title SendVerificationCode
|
// @Title SendVerificationCode
|
||||||
// @Tag Verification API
|
// @Tag Verification API
|
||||||
// @router /send-verification-code [post]
|
// @router /send-verification-code [post]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
func (c *ApiController) SendVerificationCode() {
|
func (c *ApiController) SendVerificationCode() {
|
||||||
destType := c.Ctx.Request.Form.Get("type")
|
var vform form.VerificationForm
|
||||||
dest := c.Ctx.Request.Form.Get("dest")
|
err := c.ParseForm(&vform)
|
||||||
countryCode := c.Ctx.Request.Form.Get("countryCode")
|
if err != nil {
|
||||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
c.ResponseError(err.Error())
|
||||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
|
||||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
|
||||||
applicationId := c.Ctx.Request.Form.Get("applicationId")
|
|
||||||
method := c.Ctx.Request.Form.Get("method")
|
|
||||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
|
||||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
|
||||||
|
|
||||||
if dest == "" {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": dest.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if applicationId == "" {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if checkType == "" {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !strings.Contains(applicationId, "/") {
|
|
||||||
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkType != "none" {
|
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
|
||||||
if checkKey == "" {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if captchaProvider := captcha.GetCaptchaProvider(checkType); captchaProvider == nil {
|
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + checkType)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId); err != nil {
|
}
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||||
} else if !isHuman {
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider != nil {
|
||||||
|
if vform.CaptchaType != provider.Type {
|
||||||
c.ResponseError(c.T("verification:Turing test failed."))
|
c.ResponseError(c.T("verification:Turing test failed."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.Type != "Default" {
|
||||||
|
vform.ClientSecret = provider.ClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
if vform.CaptchaType != "none" {
|
||||||
|
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||||
|
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||||
|
return
|
||||||
|
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, provider.ClientId, vform.ClientSecret, provider.ClientId2); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
} else if !isHuman {
|
||||||
|
c.ResponseError(c.T("verification:Turing test failed."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplication(vform.ApplicationId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
organization, err := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
application := object.GetApplication(applicationId)
|
|
||||||
organization := object.GetOrganization(util.GetId(application.Owner, application.Organization))
|
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
c.ResponseError(c.T("verification:Organization does not exist"))
|
c.ResponseError(c.T("check:Organization does not exist"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
// checkUser != "", means method is ForgetVerification
|
// checkUser != "", means method is ForgetVerification
|
||||||
if checkUser != "" {
|
if vform.CheckUser != "" {
|
||||||
owner := application.Organization
|
owner := application.Organization
|
||||||
user = object.GetUser(util.GetId(owner, checkUser))
|
user, err = object.GetUser(util.GetId(owner, vform.CheckUser))
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user == nil || user.IsDeleted {
|
||||||
|
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsForbidden {
|
||||||
|
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mfaUserSession != "", means method is MfaAuthVerification
|
||||||
|
if mfaUserSession := c.getMfaUserSession(); mfaUserSession != "" {
|
||||||
|
user, err = object.GetUser(mfaUserSession)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResp := errors.New("invalid dest type")
|
sendResp := errors.New("invalid dest type")
|
||||||
|
|
||||||
switch destType {
|
switch vform.Type {
|
||||||
case "email":
|
case object.VerifyTypeEmail:
|
||||||
if !util.IsEmailValid(dest) {
|
if !util.IsEmailValid(vform.Dest) {
|
||||||
c.ResponseError(c.T("verification:Email is invalid"))
|
c.ResponseError(c.T("check:Email is invalid"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == LoginVerification || method == ForgetVerification {
|
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||||
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest {
|
||||||
dest = user.Email
|
vform.Dest = user.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = object.GetUserByEmail(organization.Name, vform.Dest)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user = object.GetUserByEmail(organization.Name, dest)
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if method == ResetVerification {
|
} else if vform.Method == ResetVerification {
|
||||||
user = c.getCurrentUser()
|
user = c.getCurrentUser()
|
||||||
|
} else if vform.Method == MfaAuthVerification {
|
||||||
|
mfaProps := user.GetMfaProps(object.EmailType, false)
|
||||||
|
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
|
||||||
|
vform.Dest = mfaProps.Secret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := application.GetEmailProvider()
|
provider, err = application.GetEmailProvider(vform.Method)
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
if err != nil {
|
||||||
case "phone":
|
c.ResponseError(err.Error())
|
||||||
if method == LoginVerification || method == ForgetVerification {
|
return
|
||||||
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
}
|
||||||
dest = user.Phone
|
if provider == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("verification:please add an Email provider to the \"Providers\" list for the application: %s"), application.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest, vform.Method, c.Ctx.Request.Host, application.Name)
|
||||||
|
case object.VerifyTypePhone:
|
||||||
|
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
|
||||||
|
vform.Dest = user.Phone
|
||||||
}
|
}
|
||||||
|
|
||||||
if user = object.GetUserByPhone(organization.Name, dest); user == nil {
|
if user, err = object.GetUserByPhone(organization.Name, vform.Dest); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
} else if user == nil {
|
||||||
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
countryCode = user.GetCountryCode(countryCode)
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
} else if method == ResetVerification {
|
} else if vform.Method == ResetVerification || vform.Method == MfaSetupVerification {
|
||||||
if user = c.getCurrentUser(); user != nil {
|
if vform.CountryCode == "" {
|
||||||
countryCode = user.GetCountryCode(countryCode)
|
if user = c.getCurrentUser(); user != nil {
|
||||||
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if vform.Method == MfaAuthVerification {
|
||||||
|
mfaProps := user.GetMfaProps(object.SmsType, false)
|
||||||
|
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
|
||||||
|
vform.Dest = mfaProps.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
vform.CountryCode = mfaProps.CountryCode
|
||||||
|
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := application.GetSmsProvider()
|
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
|
||||||
if phone, ok := util.GetE164Number(dest, countryCode); !ok {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("verification:please add a SMS provider to the \"Providers\" list for the application: %s"), application.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, phone)
|
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, clientIp, phone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +315,54 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyCaptcha ...
|
||||||
|
// @Title VerifyCaptcha
|
||||||
|
// @Tag Verification API
|
||||||
|
// @router /verify-captcha [post]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
|
func (c *ApiController) VerifyCaptcha() {
|
||||||
|
var vform form.VerificationForm
|
||||||
|
err := c.ParseForm(&vform)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" {
|
||||||
|
c.ResponseError(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if captchaProvider.Type != "Default" {
|
||||||
|
vform.ClientSecret = captchaProvider.ClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, captchaProvider.ClientId, vform.ClientSecret, captchaProvider.ClientId2)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(isValid)
|
||||||
|
}
|
||||||
|
|
||||||
// ResetEmailOrPhone ...
|
// ResetEmailOrPhone ...
|
||||||
// @Tag Account API
|
// @Tag Account API
|
||||||
// @Title ResetEmailOrPhone
|
// @Title ResetEmailOrPhone
|
||||||
// @router /api/reset-email-or-phone [post]
|
// @router /reset-email-or-phone [post]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
func (c *ApiController) ResetEmailOrPhone() {
|
func (c *ApiController) ResetEmailOrPhone() {
|
||||||
user, ok := c.RequireSignedInUser()
|
user, ok := c.RequireSignedInUser()
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -186,8 +379,13 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkDest := dest
|
checkDest := dest
|
||||||
organization := object.GetOrganizationByUser(user)
|
organization, err := object.GetOrganizationByUser(user)
|
||||||
if destType == "phone" {
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if destType == object.VerifyTypePhone {
|
||||||
if object.HasUserByField(user.Owner, "phone", dest) {
|
if object.HasUserByField(user.Owner, "phone", dest) {
|
||||||
c.ResponseError(c.T("check:Phone already exists"))
|
c.ResponseError(c.T("check:Phone already exists"))
|
||||||
return
|
return
|
||||||
@@ -199,7 +397,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user, c.GetAcceptLanguage()); !pass {
|
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||||
c.ResponseError(errMsg)
|
c.ResponseError(errMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -207,7 +405,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if destType == "email" {
|
} else if destType == object.VerifyTypeEmail {
|
||||||
if object.HasUserByField(user.Owner, "email", dest) {
|
if object.HasUserByField(user.Owner, "email", dest) {
|
||||||
c.ResponseError(c.T("check:Email already exists"))
|
c.ResponseError(c.T("check:Email already exists"))
|
||||||
return
|
return
|
||||||
@@ -219,61 +417,134 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user, c.GetAcceptLanguage()); !pass {
|
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
|
||||||
c.ResponseError(errMsg)
|
c.ResponseError(errMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if msg := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(msg) != 0 {
|
|
||||||
c.ResponseError(msg)
|
result, err := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.Code != object.VerificationSuccess {
|
||||||
|
c.ResponseError(result.Msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch destType {
|
switch destType {
|
||||||
case "email":
|
case object.VerifyTypeEmail:
|
||||||
|
id := user.GetId()
|
||||||
user.Email = dest
|
user.Email = dest
|
||||||
object.SetUserField(user, "email", user.Email)
|
user.EmailVerified = true
|
||||||
case "phone":
|
columns := []string{"email", "email_verified"}
|
||||||
|
if organization.UseEmailAsUsername {
|
||||||
|
user.Name = user.Email
|
||||||
|
columns = append(columns, "name")
|
||||||
|
}
|
||||||
|
_, err = object.UpdateUser(id, user, columns, false)
|
||||||
|
case object.VerifyTypePhone:
|
||||||
user.Phone = dest
|
user.Phone = dest
|
||||||
object.SetUserField(user, "phone", user.Phone)
|
_, err = object.SetUserField(user, "phone", user.Phone)
|
||||||
default:
|
default:
|
||||||
c.ResponseError(c.T("verification:Unknown type"))
|
c.ResponseError(c.T("verification:Unknown type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
object.DisableVerificationCode(checkDest)
|
c.ResponseError(err.Error())
|
||||||
c.ResponseOk()
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyCaptcha ...
|
|
||||||
// @Title VerifyCaptcha
|
|
||||||
// @Tag Verification API
|
|
||||||
// @router /verify-captcha [post]
|
|
||||||
func (c *ApiController) VerifyCaptcha() {
|
|
||||||
captchaType := c.Ctx.Request.Form.Get("captchaType")
|
|
||||||
|
|
||||||
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
|
|
||||||
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
|
|
||||||
if captchaToken == "" {
|
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if clientSecret == "" {
|
if organization.UseEmailAsUsername {
|
||||||
c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.")
|
c.SetSessionUsername(user.GetId())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provider := captcha.GetCaptchaProvider(captchaType)
|
err = object.DisableVerificationCode(checkDest)
|
||||||
if provider == nil {
|
|
||||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(isValid)
|
c.ResponseOk()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyCode
|
||||||
|
// @Tag Verification API
|
||||||
|
// @Title VerifyCode
|
||||||
|
// @router /verify-code [post]
|
||||||
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
|
func (c *ApiController) VerifyCode() {
|
||||||
|
var authForm form.AuthForm
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *object.User
|
||||||
|
if authForm.Name != "" {
|
||||||
|
user, err = object.GetUserByFields(authForm.Organization, authForm.Name)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkDest string
|
||||||
|
if strings.Contains(authForm.Username, "@") {
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username {
|
||||||
|
authForm.Username = user.Email
|
||||||
|
}
|
||||||
|
checkDest = authForm.Username
|
||||||
|
} else {
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username {
|
||||||
|
authForm.Username = user.Phone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
} else if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||||
|
if verificationCodeType == object.VerifyTypePhone {
|
||||||
|
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||||
|
var ok bool
|
||||||
|
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passed, err := c.checkOrgMasterVerificationCode(user, authForm.Code)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(c.T(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !passed {
|
||||||
|
result, err := object.CheckVerificationCode(checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result.Code != object.VerificationSuccess {
|
||||||
|
c.ResponseError(result.Msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = object.DisableVerificationCode(checkDest)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetSession("verifiedCode", authForm.Code)
|
||||||
|
c.SetSession("verifiedUserId", user.GetId())
|
||||||
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|||||||
36
controllers/verification_util.go
Normal file
36
controllers/verification_util.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. 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 controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ApiController) checkOrgMasterVerificationCode(user *object.User, code string) (bool, error) {
|
||||||
|
organization, err := object.GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if organization == nil {
|
||||||
|
return false, fmt.Errorf("The organization: %s does not exist", user.Owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization.MasterVerificationCode != "" && organization.MasterVerificationCode == code {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
@@ -16,13 +16,15 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/form"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/duo-labs/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/duo-labs/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebAuthnSignupBegin
|
// WebAuthnSignupBegin
|
||||||
@@ -32,7 +34,12 @@ import (
|
|||||||
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
|
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
|
||||||
// @router /webauthn/signup/begin [get]
|
// @router /webauthn/signup/begin [get]
|
||||||
func (c *ApiController) WebAuthnSignupBegin() {
|
func (c *ApiController) WebAuthnSignupBegin() {
|
||||||
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
@@ -41,6 +48,13 @@ func (c *ApiController) WebAuthnSignupBegin() {
|
|||||||
|
|
||||||
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
|
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
|
||||||
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
|
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
|
||||||
|
credCreationOpts.AuthenticatorSelection.ResidentKey = "preferred"
|
||||||
|
credCreationOpts.Attestation = "none"
|
||||||
|
|
||||||
|
ext := map[string]interface{}{
|
||||||
|
"credProps": true,
|
||||||
|
}
|
||||||
|
credCreationOpts.Extensions = ext
|
||||||
}
|
}
|
||||||
options, sessionData, err := webauthnObj.BeginRegistration(
|
options, sessionData, err := webauthnObj.BeginRegistration(
|
||||||
user,
|
user,
|
||||||
@@ -60,10 +74,15 @@ func (c *ApiController) WebAuthnSignupBegin() {
|
|||||||
// @Tag User API
|
// @Tag User API
|
||||||
// @Description WebAuthn Registration Flow 2nd stage
|
// @Description WebAuthn Registration Flow 2nd stage
|
||||||
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
|
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
|
||||||
// @Success 200 {object} Response "The Response object"
|
// @Success 200 {object} controllers.Response "The Response object"
|
||||||
// @router /webauthn/signup/finish [post]
|
// @router /webauthn/signup/finish [post]
|
||||||
func (c *ApiController) WebAuthnSignupFinish() {
|
func (c *ApiController) WebAuthnSignupFinish() {
|
||||||
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError(c.T("general:Please login first"))
|
c.ResponseError(c.T("general:Please login first"))
|
||||||
@@ -83,7 +102,12 @@ func (c *ApiController) WebAuthnSignupFinish() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isGlobalAdmin := c.IsGlobalAdmin()
|
isGlobalAdmin := c.IsGlobalAdmin()
|
||||||
user.AddCredentials(*credential, isGlobalAdmin)
|
_, err = user.AddCredentials(*credential, isGlobalAdmin)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,20 +120,40 @@ func (c *ApiController) WebAuthnSignupFinish() {
|
|||||||
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
|
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
|
||||||
// @router /webauthn/signin/begin [get]
|
// @router /webauthn/signin/begin [get]
|
||||||
func (c *ApiController) WebAuthnSigninBegin() {
|
func (c *ApiController) WebAuthnSigninBegin() {
|
||||||
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
userOwner := c.Input().Get("owner")
|
if err != nil {
|
||||||
userName := c.Input().Get("name")
|
c.ResponseError(err.Error())
|
||||||
user := object.GetUserByFields(userOwner, userName)
|
|
||||||
if user == nil {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(user.WebauthnCredentials) == 0 {
|
|
||||||
c.ResponseError(c.T("webauthn:Found no credentials for this user"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options, sessionData, err := webauthnObj.BeginLogin(user)
|
userOwner := c.Input().Get("owner")
|
||||||
|
userName := c.Input().Get("name")
|
||||||
|
|
||||||
|
var options *protocol.CredentialAssertion
|
||||||
|
var sessionData *webauthn.SessionData
|
||||||
|
|
||||||
|
if userName == "" {
|
||||||
|
options, sessionData, err = webauthnObj.BeginDiscoverableLogin()
|
||||||
|
} else {
|
||||||
|
var user *object.User
|
||||||
|
user, err = object.GetUserByFields(userOwner, userName)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(user.WebauthnCredentials) == 0 {
|
||||||
|
c.ResponseError(c.T("webauthn:Found no credentials for this user"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
options, sessionData, err = webauthnObj.BeginLogin(user)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -120,15 +164,21 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnSigninFinish
|
// WebAuthnSigninFinish
|
||||||
// @Title WebAuthnSigninBegin
|
// @Title WebAuthnSigninFinish
|
||||||
// @Tag Login API
|
// @Tag Login API
|
||||||
// @Description WebAuthn Login Flow 2nd stage
|
// @Description WebAuthn Login Flow 2nd stage
|
||||||
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
|
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
|
||||||
// @Success 200 {object} Response "The Response object"
|
// @Success 200 {object} controllers.Response "The Response object"
|
||||||
// @router /webauthn/signin/finish [post]
|
// @router /webauthn/signin/finish [post]
|
||||||
func (c *ApiController) WebAuthnSigninFinish() {
|
func (c *ApiController) WebAuthnSigninFinish() {
|
||||||
responseType := c.Input().Get("responseType")
|
responseType := c.Input().Get("responseType")
|
||||||
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
clientId := c.Input().Get("clientId")
|
||||||
|
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sessionObj := c.GetSession("authentication")
|
sessionObj := c.GetSession("authentication")
|
||||||
sessionData, ok := sessionObj.(webauthn.SessionData)
|
sessionData, ok := sessionObj.(webauthn.SessionData)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -136,20 +186,51 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
|
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
|
||||||
userId := string(sessionData.UserID)
|
|
||||||
user := object.GetUser(userId)
|
var user *object.User
|
||||||
_, err := webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
|
if sessionData.UserID != nil {
|
||||||
|
userId := string(sessionData.UserID)
|
||||||
|
user, err = object.GetUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
|
||||||
|
} else {
|
||||||
|
handler := func(rawID, userHandle []byte) (webauthn.User, error) {
|
||||||
|
user, err = object.GetUserByWebauthID(base64.StdEncoding.EncodeToString(rawID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = webauthnObj.FinishDiscoverableLogin(handler, sessionData, c.Ctx.Request)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(user.GetId())
|
||||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
util.LogInfo(c.Ctx, "API: [%s] signed in", user.GetId())
|
||||||
|
|
||||||
application := object.GetApplicationByUser(user)
|
var application *object.Application
|
||||||
var form RequestForm
|
|
||||||
form.Type = responseType
|
if clientId != "" && (responseType == ResponseTypeCode) {
|
||||||
resp := c.HandleLoggedIn(application, user, &form)
|
application, err = object.GetApplicationByClientId(clientId)
|
||||||
|
} else {
|
||||||
|
application, err = object.GetApplicationByUser(user)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var authForm form.AuthForm
|
||||||
|
authForm.Type = responseType
|
||||||
|
resp := c.HandleLoggedIn(application, user, &authForm)
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ import (
|
|||||||
// @Title GetWebhooks
|
// @Title GetWebhooks
|
||||||
// @Tag Webhook API
|
// @Tag Webhook API
|
||||||
// @Description get webhooks
|
// @Description get webhooks
|
||||||
// @Param owner query string true "The owner of webhooks"
|
// @Param owner query string built-in/admin true "The owner of webhooks"
|
||||||
// @Success 200 {array} object.Webhook The Response object
|
// @Success 200 {array} object.Webhook The Response object
|
||||||
// @router /get-webhooks [get]
|
// @router /get-webhooks [get]
|
||||||
|
// @Security test_apiKey
|
||||||
func (c *ApiController) GetWebhooks() {
|
func (c *ApiController) GetWebhooks() {
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
@@ -37,13 +38,32 @@ func (c *ApiController) GetWebhooks() {
|
|||||||
value := c.Input().Get("value")
|
value := c.Input().Get("value")
|
||||||
sortField := c.Input().Get("sortField")
|
sortField := c.Input().Get("sortField")
|
||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
organization := c.Input().Get("organization")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
c.Data["json"] = object.GetWebhooks(owner)
|
webhooks, err := object.GetWebhooks(owner, organization)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(webhooks)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
|
count, err := object.GetWebhookCount(owner, organization, field, value)
|
||||||
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||||
|
|
||||||
|
webhooks, err := object.GetPaginationWebhooks(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(webhooks, paginator.Nums())
|
c.ResponseOk(webhooks, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,21 +72,26 @@ func (c *ApiController) GetWebhooks() {
|
|||||||
// @Title GetWebhook
|
// @Title GetWebhook
|
||||||
// @Tag Webhook API
|
// @Tag Webhook API
|
||||||
// @Description get webhook
|
// @Description get webhook
|
||||||
// @Param id query string true "The id ( owner/name ) of the webhook"
|
// @Param id query string built-in/admin true "The id ( owner/name ) of the webhook"
|
||||||
// @Success 200 {object} object.Webhook The Response object
|
// @Success 200 {object} object.Webhook The Response object
|
||||||
// @router /get-webhook [get]
|
// @router /get-webhook [get]
|
||||||
func (c *ApiController) GetWebhook() {
|
func (c *ApiController) GetWebhook() {
|
||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
c.Data["json"] = object.GetWebhook(id)
|
webhook, err := object.GetWebhook(id)
|
||||||
c.ServeJSON()
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(webhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWebhook
|
// UpdateWebhook
|
||||||
// @Title UpdateWebhook
|
// @Title UpdateWebhook
|
||||||
// @Tag Webhook API
|
// @Tag Webhook API
|
||||||
// @Description update webhook
|
// @Description update webhook
|
||||||
// @Param id query string true "The id ( owner/name ) of the webhook"
|
// @Param id query string built-in/admin true "The id ( owner/name ) of the webhook"
|
||||||
// @Param body body object.Webhook true "The details of the webhook"
|
// @Param body body object.Webhook true "The details of the webhook"
|
||||||
// @Success 200 {object} controllers.Response The Response object
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
// @router /update-webhook [post]
|
// @router /update-webhook [post]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func NewArgon2idCredManager() *Argon2idCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Argon2idCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -31,7 +31,7 @@ func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt strin
|
|||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
|
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ func NewBcryptCredManager() *BcryptCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *BcryptCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -17,7 +17,7 @@ func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string,
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
package cred
|
package cred
|
||||||
|
|
||||||
type CredManager interface {
|
type CredManager interface {
|
||||||
GetHashedPassword(password string, userSalt string, organizationSalt string) string
|
GetHashedPassword(password string, salt string) string
|
||||||
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
|
IsPasswordCorrect(password string, passwordHash string, salt string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCredManager(passwordType string) CredManager {
|
func GetCredManager(passwordType string) CredManager {
|
||||||
@@ -24,6 +24,8 @@ func GetCredManager(passwordType string) CredManager {
|
|||||||
return NewPlainCredManager()
|
return NewPlainCredManager()
|
||||||
} else if passwordType == "salt" {
|
} else if passwordType == "salt" {
|
||||||
return NewSha256SaltCredManager()
|
return NewSha256SaltCredManager()
|
||||||
|
} else if passwordType == "sha512-salt" {
|
||||||
|
return NewSha512SaltCredManager()
|
||||||
} else if passwordType == "md5-salt" {
|
} else if passwordType == "md5-salt" {
|
||||||
return NewMd5UserSaltCredManager()
|
return NewMd5UserSaltCredManager()
|
||||||
} else if passwordType == "bcrypt" {
|
} else if passwordType == "bcrypt" {
|
||||||
@@ -32,6 +34,8 @@ func GetCredManager(passwordType string) CredManager {
|
|||||||
return NewPbkdf2SaltCredManager()
|
return NewPbkdf2SaltCredManager()
|
||||||
} else if passwordType == "argon2id" {
|
} else if passwordType == "argon2id" {
|
||||||
return NewArgon2idCredManager()
|
return NewArgon2idCredManager()
|
||||||
|
} else if passwordType == "pbkdf2-django" {
|
||||||
|
return NewPbkdf2DjangoCredManager()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,14 +37,21 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
res := getMd5HexDigest(password)
|
if salt == "" {
|
||||||
if userSalt != "" {
|
return getMd5HexDigest(password)
|
||||||
res = getMd5HexDigest(res + userSalt)
|
|
||||||
}
|
}
|
||||||
return res
|
|
||||||
|
return getMd5HexDigest(getMd5HexDigest(password) + salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
// For backward-compatibility
|
||||||
|
if salt == "" {
|
||||||
|
if hashedPwd == cm.GetHashedPassword(getMd5HexDigest(plainPwd), salt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
|
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
|
||||||
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
|
decodedSalt, _ := base64.StdEncoding.DecodeString(salt)
|
||||||
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
|
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
|
||||||
return base64.StdEncoding.EncodeToString(res)
|
return base64.StdEncoding.EncodeToString(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
|||||||
67
cred/pbkdf2_django.go
Normal file
67
cred/pbkdf2_django.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. 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 cred
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// password type: pbkdf2-django
|
||||||
|
|
||||||
|
type Pbkdf2DjangoCredManager struct{}
|
||||||
|
|
||||||
|
func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
|
||||||
|
cm := &Pbkdf2DjangoCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
|
iterations := 260000
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iterations, sha256.Size, sha256.New)
|
||||||
|
hashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, _salt string) bool {
|
||||||
|
parts := strings.Split(passwordHash, "$")
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
algorithm, iterations, salt, hash := parts[0], parts[1], parts[2], parts[3]
|
||||||
|
if algorithm != "pbkdf2_sha256" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
iter, err := strconv.Atoi(iterations)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iter, sha256.Size, sha256.New)
|
||||||
|
computedHashBase64 := base64.StdEncoding.EncodeToString(computedHash)
|
||||||
|
|
||||||
|
return computedHashBase64 == hash
|
||||||
|
}
|
||||||
@@ -21,10 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *PlainCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == plainPwd
|
return hashedPwd == plainPwd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,14 +37,21 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
|||||||
return cm
|
return cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
res := getSha256HexDigest(password)
|
if salt == "" {
|
||||||
if organizationSalt != "" {
|
return getSha256HexDigest(password)
|
||||||
res = getSha256HexDigest(res + organizationSalt)
|
|
||||||
}
|
}
|
||||||
return res
|
|
||||||
|
return getSha256HexDigest(getSha256HexDigest(password) + salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
// For backward-compatibility
|
||||||
|
if salt == "" {
|
||||||
|
if hashedPwd == cm.GetHashedPassword(getSha256HexDigest(plainPwd), salt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ func TestGetSaltedPassword(t *testing.T) {
|
|||||||
password := "123456"
|
password := "123456"
|
||||||
salt := "123"
|
salt := "123"
|
||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, salt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPassword(t *testing.T) {
|
func TestGetPassword(t *testing.T) {
|
||||||
password := "123456"
|
password := "123456"
|
||||||
cm := NewSha256SaltCredManager()
|
cm := NewSha256SaltCredManager()
|
||||||
// https://passwordsgenerator.net/sha256-hash-generator/
|
// https://passwordsgenerator.net/sha256-hash-generator/
|
||||||
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
|
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, ""))
|
||||||
}
|
}
|
||||||
|
|||||||
57
cred/sha512-salt.go
Normal file
57
cred/sha512-salt.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. 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 cred
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sha512SaltCredManager struct{}
|
||||||
|
|
||||||
|
func getSha512(data []byte) []byte {
|
||||||
|
hash := sha512.Sum512(data)
|
||||||
|
return hash[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSha512HexDigest(s string) string {
|
||||||
|
b := getSha512([]byte(s))
|
||||||
|
res := hex.EncodeToString(b)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSha512SaltCredManager() *Sha512SaltCredManager {
|
||||||
|
cm := &Sha512SaltCredManager{}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, salt string) string {
|
||||||
|
if salt == "" {
|
||||||
|
return getSha512HexDigest(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSha512HexDigest(getSha512HexDigest(password) + salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
|
||||||
|
// For backward-compatibility
|
||||||
|
if salt == "" {
|
||||||
|
if hashedPwd == cm.GetHashedPassword(getSha512HexDigest(plainPwd), salt) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ package deployment
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -26,7 +27,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func deployStaticFiles(provider *object.Provider) {
|
func deployStaticFiles(provider *object.Provider) {
|
||||||
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := object.GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint, certificate, provider.Content)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
if storageProvider == nil {
|
if storageProvider == nil {
|
||||||
panic(fmt.Sprintf("the provider type: %s is not supported", provider.Type))
|
panic(fmt.Sprintf("the provider type: %s is not supported", provider.Type))
|
||||||
}
|
}
|
||||||
@@ -45,7 +60,7 @@ func uploadFolder(storageProvider oss.StorageInterface, folder string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(path + filename)
|
file, err := os.Open(filepath.Clean(path + filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDeployStaticFiles(t *testing.T) {
|
func TestDeployStaticFiles(t *testing.T) {
|
||||||
provider := object.GetProvider(util.GetId("admin", "provider_storage_aliyun_oss"))
|
object.InitConfig()
|
||||||
|
|
||||||
|
provider, err := object.GetProvider(util.GetId("admin", "provider_storage_aliyun_oss"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
deployStaticFiles(provider)
|
deployStaticFiles(provider)
|
||||||
}
|
}
|
||||||
|
|||||||
223
email/azure_acs.go
Normal file
223
email/azure_acs.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
importanceNormal = "normal"
|
||||||
|
sendEmailEndpoint = "/emails:send"
|
||||||
|
apiVersion = "2023-03-31"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Email struct {
|
||||||
|
Recipients Recipients `json:"recipients"`
|
||||||
|
SenderAddress string `json:"senderAddress"`
|
||||||
|
Content Content `json:"content"`
|
||||||
|
Headers []CustomHeader `json:"headers"`
|
||||||
|
Tracking bool `json:"disableUserEngagementTracking"`
|
||||||
|
Importance string `json:"importance"`
|
||||||
|
ReplyTo []EmailAddress `json:"replyTo"`
|
||||||
|
Attachments []Attachment `json:"attachments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Recipients struct {
|
||||||
|
To []EmailAddress `json:"to"`
|
||||||
|
CC []EmailAddress `json:"cc"`
|
||||||
|
BCC []EmailAddress `json:"bcc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailAddress struct {
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Content struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
HTML string `json:"html"`
|
||||||
|
PlainText string `json:"plainText"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomHeader struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attachment struct {
|
||||||
|
Content string `json:"contentBytesBase64"`
|
||||||
|
AttachmentType string `json:"attachmentType"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error CommunicationError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunicationError contains the error code and message
|
||||||
|
type CommunicationError struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AzureACSEmailProvider struct {
|
||||||
|
AccessKey string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAzureACSEmailProvider(accessKey string, endpoint string) *AzureACSEmailProvider {
|
||||||
|
return &AzureACSEmailProvider{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmail(fromAddress string, toAddress []string, subject string, content string) *Email {
|
||||||
|
var to []EmailAddress
|
||||||
|
for _, addr := range toAddress {
|
||||||
|
to = append(to, EmailAddress{
|
||||||
|
DisplayName: addr,
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &Email{
|
||||||
|
Recipients: Recipients{
|
||||||
|
To: to,
|
||||||
|
},
|
||||||
|
SenderAddress: fromAddress,
|
||||||
|
Content: Content{
|
||||||
|
Subject: subject,
|
||||||
|
HTML: content,
|
||||||
|
},
|
||||||
|
Importance: importanceNormal,
|
||||||
|
Attachments: []Attachment{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress []string, subject string, content string) error {
|
||||||
|
email := newEmail(fromAddress, toAddress, subject, content)
|
||||||
|
|
||||||
|
postBody, err := json.Marshal(email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint := strings.TrimSuffix(a.Endpoint, "/")
|
||||||
|
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
|
||||||
|
|
||||||
|
bodyBuffer := bytes.NewBuffer(postBody)
|
||||||
|
req, err := http.NewRequest("POST", url, bodyBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = signRequestHMAC(a.AccessKey, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("repeatability-request-id", uuid.New().String())
|
||||||
|
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
|
||||||
|
commError := ErrorResponse{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&commError)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("status code: %d, error message: %s", resp.StatusCode, commError.Error.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
|
return fmt.Errorf("status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signRequestHMAC(secret string, req *http.Request) error {
|
||||||
|
method := req.Method
|
||||||
|
host := req.URL.Host
|
||||||
|
pathAndQuery := req.URL.Path
|
||||||
|
|
||||||
|
if req.URL.RawQuery != "" {
|
||||||
|
pathAndQuery = pathAndQuery + "?" + req.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
var content []byte
|
||||||
|
var err error
|
||||||
|
if req.Body != nil {
|
||||||
|
content, err = io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
// return err
|
||||||
|
content = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = io.NopCloser(bytes.NewBuffer(content))
|
||||||
|
|
||||||
|
key, err := base64.StdEncoding.DecodeString(secret)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error decoding secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := time.Now().UTC().Format(http.TimeFormat)
|
||||||
|
contentHash := GetContentHashBase64(content)
|
||||||
|
stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", strings.ToUpper(method), pathAndQuery, timestamp, host, contentHash)
|
||||||
|
signature := GetHmac(stringToSign, key)
|
||||||
|
|
||||||
|
req.Header.Set("x-ms-content-sha256", contentHash)
|
||||||
|
req.Header.Set("x-ms-date", timestamp)
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature="+signature)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContentHashBase64(content []byte) string {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write(content)
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetHmac(content string, key []byte) string {
|
||||||
|
hmac := hmac.New(sha256.New, key)
|
||||||
|
hmac.Write([]byte(content))
|
||||||
|
|
||||||
|
return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
|
||||||
|
}
|
||||||
146
email/custom_http.go
Normal file
146
email/custom_http.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HttpEmailProvider struct {
|
||||||
|
endpoint string
|
||||||
|
method string
|
||||||
|
httpHeaders map[string]string
|
||||||
|
bodyMapping map[string]string
|
||||||
|
contentType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) *HttpEmailProvider {
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &HttpEmailProvider{
|
||||||
|
endpoint: endpoint,
|
||||||
|
method: method,
|
||||||
|
httpHeaders: httpHeaders,
|
||||||
|
bodyMapping: bodyMapping,
|
||||||
|
contentType: contentType,
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress []string, subject string, content string) error {
|
||||||
|
var req *http.Request
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fromNameField := "fromName"
|
||||||
|
toAddressField := "toAddress"
|
||||||
|
toAddressesField := "toAddresses"
|
||||||
|
subjectField := "subject"
|
||||||
|
contentField := "content"
|
||||||
|
for k, v := range c.bodyMapping {
|
||||||
|
switch k {
|
||||||
|
case "fromName":
|
||||||
|
fromNameField = v
|
||||||
|
case "toAddress":
|
||||||
|
toAddressField = v
|
||||||
|
case "toAddresses":
|
||||||
|
toAddressesField = v
|
||||||
|
case "subject":
|
||||||
|
subjectField = v
|
||||||
|
case "content":
|
||||||
|
contentField = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.method == "POST" || c.method == "PUT" || c.method == "DELETE" {
|
||||||
|
bodyMap := make(map[string]string)
|
||||||
|
bodyMap[fromNameField] = fromName
|
||||||
|
bodyMap[subjectField] = subject
|
||||||
|
bodyMap[contentField] = content
|
||||||
|
|
||||||
|
var fromValueBytes []byte
|
||||||
|
if c.contentType == "application/json" {
|
||||||
|
fromValueBytes, err = json.Marshal(bodyMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest(c.method, c.endpoint, bytes.NewBuffer(fromValueBytes))
|
||||||
|
} else {
|
||||||
|
formValues := url.Values{}
|
||||||
|
for k, v := range bodyMap {
|
||||||
|
formValues.Add(k, v)
|
||||||
|
}
|
||||||
|
if len(toAddress) == 1 {
|
||||||
|
formValues.Add(toAddressField, toAddress[0])
|
||||||
|
} else {
|
||||||
|
for _, addr := range toAddress {
|
||||||
|
formValues.Add(toAddressesField, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", c.contentType)
|
||||||
|
} else if c.method == "GET" {
|
||||||
|
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := req.URL.Query()
|
||||||
|
q.Add(fromNameField, fromName)
|
||||||
|
if len(toAddress) == 1 {
|
||||||
|
q.Add(toAddressField, toAddress[0])
|
||||||
|
} else {
|
||||||
|
for _, addr := range toAddress {
|
||||||
|
q.Add(toAddressesField, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q.Add(subjectField, subject)
|
||||||
|
q.Add(contentField, content)
|
||||||
|
req.URL.RawQuery = q.Encode()
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range c.httpHeaders {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := proxy.DefaultHttpClient
|
||||||
|
resp, err := httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("HttpEmailProvider's Send() error, custom HTTP Email request failed with status: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
31
email/provider.go
Normal file
31
email/provider.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 email
|
||||||
|
|
||||||
|
type EmailProvider interface {
|
||||||
|
Send(fromAddress string, fromName string, toAddress []string, subject string, content string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) EmailProvider {
|
||||||
|
if typ == "Azure ACS" {
|
||||||
|
return NewAzureACSEmailProvider(clientSecret, host)
|
||||||
|
} else if typ == "Custom HTTP Email" {
|
||||||
|
return NewHttpEmailProvider(endpoint, method, httpHeaders, bodyMapping, contentType)
|
||||||
|
} else if typ == "SendGrid" {
|
||||||
|
return NewSendgridEmailProvider(clientSecret, host, endpoint)
|
||||||
|
} else {
|
||||||
|
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||||
|
}
|
||||||
|
}
|
||||||
96
email/sendgrid.go
Normal file
96
email/sendgrid.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sendgrid/sendgrid-go"
|
||||||
|
"github.com/sendgrid/sendgrid-go/helpers/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SendgridEmailProvider struct {
|
||||||
|
ApiKey string
|
||||||
|
Host string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendgridResponseBody struct {
|
||||||
|
Errors []struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Field interface{} `json:"field"`
|
||||||
|
Help interface{} `json:"help"`
|
||||||
|
} `json:"errors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSendgridEmailProvider(apiKey string, host string, endpoint string) *SendgridEmailProvider {
|
||||||
|
return &SendgridEmailProvider{ApiKey: apiKey, Host: host, Endpoint: endpoint}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendgridEmailProvider) Send(fromAddress string, fromName string, toAddresses []string, subject string, content string) error {
|
||||||
|
from := mail.NewEmail(fromName, fromAddress)
|
||||||
|
message := mail.NewV3Mail()
|
||||||
|
message.SetFrom(from)
|
||||||
|
message.AddContent(mail.NewContent("text/html", content))
|
||||||
|
|
||||||
|
personalization := mail.NewPersonalization()
|
||||||
|
|
||||||
|
for _, toAddress := range toAddresses {
|
||||||
|
to := mail.NewEmail(toAddress, toAddress)
|
||||||
|
personalization.AddTos(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
message.AddPersonalizations(personalization)
|
||||||
|
|
||||||
|
client := s.initSendgridClient()
|
||||||
|
resp, err := client.Send(message)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 300 {
|
||||||
|
var responseBody SendgridResponseBody
|
||||||
|
err = json.Unmarshal([]byte(resp.Body), &responseBody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
for _, sendgridError := range responseBody.Errors {
|
||||||
|
messages = append(messages, sendgridError.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("status code: %d, error message: %s", resp.StatusCode, messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
|
return fmt.Errorf("status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SendgridEmailProvider) initSendgridClient() *sendgrid.Client {
|
||||||
|
if s.Host == "" || s.Endpoint == "" {
|
||||||
|
return sendgrid.NewSendClient(s.ApiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
request := sendgrid.GetRequest(s.ApiKey, s.Endpoint, s.Host)
|
||||||
|
request.Method = "POST"
|
||||||
|
|
||||||
|
return &sendgrid.Client{Request: request}
|
||||||
|
}
|
||||||
61
email/smtp.go
Normal file
61
email/smtp.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/gomail/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SmtpEmailProvider struct {
|
||||||
|
Dialer *gomail.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSmtpEmailProvider(userName string, password string, host string, port int, typ string, disableSsl bool) *SmtpEmailProvider {
|
||||||
|
dialer := gomail.NewDialer(host, port, userName, password)
|
||||||
|
if typ == "SUBMAIL" {
|
||||||
|
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer.SSL = !disableSsl
|
||||||
|
|
||||||
|
if strings.HasSuffix(host, ".amazonaws.com") {
|
||||||
|
socks5Proxy := conf.GetConfigString("socks5Proxy")
|
||||||
|
if socks5Proxy != "" {
|
||||||
|
dialer.SetSocks5Proxy(socks5Proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SmtpEmailProvider{Dialer: dialer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SmtpEmailProvider) Send(fromAddress string, fromName string, toAddresses []string, subject string, content string) error {
|
||||||
|
message := gomail.NewMessage()
|
||||||
|
|
||||||
|
message.SetAddressHeader("From", fromAddress, fromName)
|
||||||
|
var addresses []string
|
||||||
|
for _, address := range toAddresses {
|
||||||
|
addresses = append(addresses, message.FormatAddress(address, ""))
|
||||||
|
}
|
||||||
|
message.SetHeader("To", addresses...)
|
||||||
|
message.SetHeader("Subject", subject)
|
||||||
|
message.SetBody("text/html", content)
|
||||||
|
|
||||||
|
message.SkipUsernameCheck = true
|
||||||
|
return s.Dialer.DialAndSend(message)
|
||||||
|
}
|
||||||
81
faceId/aliyun.go
Normal file
81
faceId/aliyun.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. 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 faceId
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
facebody20191230 "github.com/alibabacloud-go/facebody-20191230/v5/client"
|
||||||
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunFaceIdProvider struct {
|
||||||
|
AccessKey string
|
||||||
|
AccessSecret string
|
||||||
|
|
||||||
|
Endpoint string
|
||||||
|
QualityScoreThreshold float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAliyunFaceIdProvider(accessKey string, accessSecret string, endPoint string) *AliyunFaceIdProvider {
|
||||||
|
return &AliyunFaceIdProvider{
|
||||||
|
AccessKey: accessKey,
|
||||||
|
AccessSecret: accessSecret,
|
||||||
|
Endpoint: endPoint,
|
||||||
|
QualityScoreThreshold: 0.65,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *AliyunFaceIdProvider) Check(base64ImageA string, base64ImageB string) (bool, error) {
|
||||||
|
config := openapi.Config{
|
||||||
|
AccessKeyId: tea.String(provider.AccessKey),
|
||||||
|
AccessKeySecret: tea.String(provider.AccessSecret),
|
||||||
|
}
|
||||||
|
config.Endpoint = tea.String(provider.Endpoint)
|
||||||
|
client, err := facebody20191230.NewClient(&config)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
compareFaceRequest := &facebody20191230.CompareFaceRequest{
|
||||||
|
QualityScoreThreshold: tea.Float32(provider.QualityScoreThreshold),
|
||||||
|
ImageDataA: tea.String(strings.Replace(base64ImageA, "data:image/png;base64,", "", -1)),
|
||||||
|
ImageDataB: tea.String(strings.Replace(base64ImageB, "data:image/png;base64,", "", -1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime := &util.RuntimeOptions{}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := tea.Recover(recover()); r != nil {
|
||||||
|
err = r
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
result, err := client.CompareFaceWithOptions(compareFaceRequest, runtime)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if *result.Body.Data.Thresholds[0] < *result.Body.Data.Confidence {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
23
faceId/provider.go
Normal file
23
faceId/provider.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2025 The Casdoor Authors. 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 faceId
|
||||||
|
|
||||||
|
type FaceIdProvider interface {
|
||||||
|
Check(base64ImageA string, base64ImageB string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFaceIdProvider(typ string, clientId string, clientSecret string, endPoint string) FaceIdProvider {
|
||||||
|
return NewAliyunFaceIdProvider(clientId, clientSecret, endPoint)
|
||||||
|
}
|
||||||
85
form/auth.go
Normal file
85
form/auth.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 form
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
type AuthForm struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
SigninMethod string `json:"signinMethod"`
|
||||||
|
|
||||||
|
Organization string `json:"organization"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Education string `json:"education"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Affiliation string `json:"affiliation"`
|
||||||
|
IdCard string `json:"idCard"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
InvitationCode string `json:"invitationCode"`
|
||||||
|
|
||||||
|
Application string `json:"application"`
|
||||||
|
ClientId string `json:"clientId"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
ProviderBack string `json:"providerBack"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
State string `json:"state"`
|
||||||
|
RedirectUri string `json:"redirectUri"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
|
||||||
|
EmailCode string `json:"emailCode"`
|
||||||
|
PhoneCode string `json:"phoneCode"`
|
||||||
|
CountryCode string `json:"countryCode"`
|
||||||
|
|
||||||
|
AutoSignin bool `json:"autoSignin"`
|
||||||
|
|
||||||
|
RelayState string `json:"relayState"`
|
||||||
|
SamlRequest string `json:"samlRequest"`
|
||||||
|
SamlResponse string `json:"samlResponse"`
|
||||||
|
|
||||||
|
CaptchaType string `json:"captchaType"`
|
||||||
|
CaptchaToken string `json:"captchaToken"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
|
|
||||||
|
MfaType string `json:"mfaType"`
|
||||||
|
Passcode string `json:"passcode"`
|
||||||
|
RecoveryCode string `json:"recoveryCode"`
|
||||||
|
EnableMfaRemember bool `json:"enableMfaRemember"`
|
||||||
|
|
||||||
|
Plan string `json:"plan"`
|
||||||
|
Pricing string `json:"pricing"`
|
||||||
|
|
||||||
|
FaceId []float64 `json:"faceId"`
|
||||||
|
FaceIdImage []string `json:"faceIdImage"`
|
||||||
|
UserCode string `json:"userCode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
|
||||||
|
val := reflect.ValueOf(*form)
|
||||||
|
fieldValue := val.FieldByName(fieldName)
|
||||||
|
|
||||||
|
if fieldValue.IsValid() && fieldValue.Kind() == reflect.String {
|
||||||
|
return true, fieldValue.String()
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
67
form/verification.go
Normal file
67
form/verification.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. 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 form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerificationForm struct {
|
||||||
|
Dest string `form:"dest"`
|
||||||
|
Type string `form:"type"`
|
||||||
|
CountryCode string `form:"countryCode"`
|
||||||
|
ApplicationId string `form:"applicationId"`
|
||||||
|
Method string `form:"method"`
|
||||||
|
CheckUser string `form:"checkUser"`
|
||||||
|
|
||||||
|
CaptchaType string `form:"captchaType"`
|
||||||
|
ClientSecret string `form:"clientSecret"`
|
||||||
|
CaptchaToken string `form:"captchaToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SendVerifyCode = 0
|
||||||
|
VerifyCaptcha = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (form *VerificationForm) CheckParameter(checkType int, lang string) string {
|
||||||
|
if checkType == SendVerifyCode {
|
||||||
|
if form.Type == "" {
|
||||||
|
return i18n.Translate(lang, "general:Missing parameter") + ": type."
|
||||||
|
}
|
||||||
|
if form.Dest == "" {
|
||||||
|
return i18n.Translate(lang, "general:Missing parameter") + ": dest."
|
||||||
|
}
|
||||||
|
if form.CaptchaType == "" {
|
||||||
|
return i18n.Translate(lang, "general:Missing parameter") + ": captchaType."
|
||||||
|
}
|
||||||
|
if !strings.Contains(form.ApplicationId, "/") {
|
||||||
|
return i18n.Translate(lang, "verification:Wrong parameter") + ": applicationId."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.CaptchaType != "none" {
|
||||||
|
if form.CaptchaToken == "" {
|
||||||
|
return i18n.Translate(lang, "general:Missing parameter") + ": captchaToken."
|
||||||
|
}
|
||||||
|
if form.ClientSecret == "" {
|
||||||
|
return i18n.Translate(lang, "general:Missing parameter") + ": clientSecret."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
260
go.mod
260
go.mod
@@ -1,60 +1,248 @@
|
|||||||
module github.com/casdoor/casdoor
|
module github.com/casdoor/casdoor
|
||||||
|
|
||||||
go 1.16
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.3
|
github.com/Masterminds/squirrel v1.5.3
|
||||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
|
||||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.4
|
||||||
github.com/aws/aws-sdk-go v1.44.4
|
github.com/alibabacloud-go/facebody-20191230/v5 v5.1.2
|
||||||
github.com/beego/beego v1.12.11
|
github.com/alibabacloud-go/openapi-util v0.1.0
|
||||||
|
github.com/alibabacloud-go/tea v1.3.2
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||||
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
|
github.com/beego/beego v1.12.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.77.2
|
||||||
github.com/casdoor/go-sms-sender v0.5.2
|
github.com/casdoor/go-sms-sender v0.25.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.1.0
|
||||||
github.com/casdoor/oss v1.2.0
|
github.com/casdoor/ldapserver v1.2.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
github.com/casdoor/notify v1.0.1
|
||||||
|
github.com/casdoor/oss v1.8.0
|
||||||
|
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||||
|
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||||
|
github.com/go-git/go-git/v5 v5.13.0
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
github.com/go-mysql-org/go-mysql v1.7.0
|
github.com/go-mysql-org/go-mysql v1.7.0
|
||||||
github.com/go-pay/gopay v1.5.72
|
github.com/go-pay/gopay v1.5.72
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/go-webauthn/webauthn v0.10.2
|
||||||
github.com/google/go-cmp v0.5.8 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/lestrrat-go/jwx v1.2.21
|
github.com/lestrrat-go/jwx v1.2.29
|
||||||
github.com/lib/pq v1.8.0
|
github.com/lib/pq v1.10.9
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||||
github.com/markbates/goth v1.75.2
|
github.com/markbates/goth v1.79.0
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
|
github.com/pquerna/otp v1.4.0
|
||||||
|
github.com/prometheus/client_golang v1.11.1
|
||||||
|
github.com/prometheus/client_model v0.4.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.9.0
|
||||||
github.com/russellhaering/goxmldsig v1.1.1
|
github.com/russellhaering/goxmldsig v1.2.0
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/sendgrid/sendgrid-go v3.14.0+incompatible
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stripe/stripe-go/v74 v74.29.0
|
||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/xorm-io/builder v0.3.13
|
||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
golang.org/x/crypto v0.32.0
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
|
golang.org/x/net v0.34.0
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
golang.org/x/oauth2 v0.17.0
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/text v0.21.0
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
google.golang.org/api v0.150.0
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||||
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
|
maunium.net/go/mautrix v0.16.0
|
||||||
|
modernc.org/sqlite v1.18.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.110.8 // indirect
|
||||||
|
cloud.google.com/go/compute v1.23.1 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
|
cloud.google.com/go/iam v1.1.3 // indirect
|
||||||
|
cloud.google.com/go/storage v1.35.1 // indirect
|
||||||
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
|
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||||
|
github.com/Azure/azure-storage-blob-go v0.15.0 // indirect
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||||
|
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20221121042443-a3fd332d56d9 // indirect
|
||||||
|
github.com/SherClockHolmes/webpush-go v1.2.0 // indirect
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
|
github.com/alibabacloud-go/darabonba-number v1.0.4 // indirect
|
||||||
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.3.6 // indirect
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||||
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.545 // indirect
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible // indirect
|
||||||
|
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||||
|
github.com/apistd/uni-go-sdk v0.0.2 // indirect
|
||||||
|
github.com/atc0005/go-teams-notify/v2 v2.13.0 // indirect
|
||||||
|
github.com/baidubce/bce-sdk-go v0.9.156 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/blinkbean/dingtalk v0.0.0-20210905093040-7d935c0f7e19 // indirect
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
|
github.com/bwmarrin/discordgo v0.27.1 // indirect
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0 // indirect
|
||||||
|
github.com/casdoor/go-reddit/v2 v2.1.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
|
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb // indirect
|
||||||
|
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
|
github.com/dghubble/oauth1 v0.7.2 // indirect
|
||||||
|
github.com/dghubble/sling v1.4.0 // indirect
|
||||||
|
github.com/di-wu/parser v0.2.2 // indirect
|
||||||
|
github.com/di-wu/xsd-datetime v1.0.0 // indirect
|
||||||
|
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 // indirect
|
||||||
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
||||||
|
github.com/go-lark/lark v1.9.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/go-webauthn/x v0.1.9 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/go-tpm v0.9.0 // indirect
|
||||||
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/gregdel/pushover v1.2.1 // indirect
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
|
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||||
|
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||||
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
|
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||||
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
|
github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
|
||||||
|
github.com/markbates/going v1.0.0 // indirect
|
||||||
|
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
github.com/mileusna/viber v1.0.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
|
||||||
|
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 // indirect
|
||||||
|
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7 // indirect
|
||||||
|
github.com/pingcap/tidb/parser v0.0.0-20221126021158-6b02a5d8ba7d // indirect
|
||||||
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/common v0.30.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
|
github.com/qiniu/go-sdk/v7 v7.12.1 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
|
github.com/rs/zerolog v1.30.0 // indirect
|
||||||
|
github.com/scim2/filter-parser/v2 v2.2.0 // indirect
|
||||||
|
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||||
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
|
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
|
||||||
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
|
||||||
|
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
|
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||||
|
github.com/slack-go/slack v0.12.3 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.744 // indirect
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.744 // indirect
|
||||||
|
github.com/tidwall/gjson v1.16.0 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.4.0 // indirect
|
||||||
|
github.com/twilio/twilio-go v1.13.0 // indirect
|
||||||
|
github.com/ucloud/ucloud-sdk-go v0.22.5 // indirect
|
||||||
|
github.com/utahta/go-linenotify v0.5.0 // indirect
|
||||||
|
github.com/volcengine/volc-sdk-golang v1.0.117 // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
|
go.mau.fi/util v0.0.0-20230805171708-199bf3eec776 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.7.0 // indirect
|
||||||
|
go.uber.org/zap v1.19.1 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
||||||
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||||
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
lukechampine.com/uint128 v1.1.1 // indirect
|
||||||
|
maunium.net/go/maulogger/v2 v2.4.1 // indirect
|
||||||
|
modernc.org/cc/v3 v3.37.0 // indirect
|
||||||
|
modernc.org/ccgo/v3 v3.16.9 // indirect
|
||||||
|
modernc.org/libc v1.18.0 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.3.0 // indirect
|
||||||
|
modernc.org/opt v0.1.1 // indirect
|
||||||
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
|
modernc.org/token v1.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ func getAllFilePathsInFolder(folder string, fileSuffix string) []string {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(path, "node_modules") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(info.Name(), fileSuffix) {
|
if !strings.HasSuffix(info.Name(), fileSuffix) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -99,7 +103,7 @@ func getAllFilePathsInFolder(folder string, fileSuffix string) []string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseEnData(category string) *I18nData {
|
func parseAllWords(category string) *I18nData {
|
||||||
var paths []string
|
var paths []string
|
||||||
if category == "backend" {
|
if category == "backend" {
|
||||||
paths = getAllFilePathsInFolder("../", ".go")
|
paths = getAllFilePathsInFolder("../", ".go")
|
||||||
@@ -136,3 +140,11 @@ func parseEnData(category string) *I18nData {
|
|||||||
|
|
||||||
return &data
|
return &data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyToOtherLanguage(category string, language string, newData *I18nData) {
|
||||||
|
oldData := readI18nFile(category, language)
|
||||||
|
println(oldData)
|
||||||
|
|
||||||
|
applyData(newData, oldData)
|
||||||
|
writeI18nFile(category, language, newData)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,44 +12,71 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
package i18n
|
package i18n
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func applyToOtherLanguage(category string, language string, i18nData *I18nData) {
|
|
||||||
newData := readI18nFile(category, language)
|
|
||||||
println(newData)
|
|
||||||
|
|
||||||
applyData(i18nData, newData)
|
|
||||||
writeI18nFile(category, language, i18nData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateI18nFrontend(t *testing.T) {
|
func TestGenerateI18nFrontend(t *testing.T) {
|
||||||
enData := parseEnData("frontend")
|
data := parseAllWords("frontend")
|
||||||
writeI18nFile("frontend", "en", enData)
|
|
||||||
|
|
||||||
applyToOtherLanguage("frontend", "zh", enData)
|
applyToOtherLanguage("frontend", "en", data)
|
||||||
applyToOtherLanguage("frontend", "es", enData)
|
applyToOtherLanguage("frontend", "zh", data)
|
||||||
applyToOtherLanguage("frontend", "fr", enData)
|
applyToOtherLanguage("frontend", "es", data)
|
||||||
applyToOtherLanguage("frontend", "de", enData)
|
applyToOtherLanguage("frontend", "fr", data)
|
||||||
applyToOtherLanguage("frontend", "ja", enData)
|
applyToOtherLanguage("frontend", "de", data)
|
||||||
applyToOtherLanguage("frontend", "ko", enData)
|
applyToOtherLanguage("frontend", "id", data)
|
||||||
applyToOtherLanguage("frontend", "ru", enData)
|
applyToOtherLanguage("frontend", "ja", data)
|
||||||
applyToOtherLanguage("frontend", "vi", enData)
|
applyToOtherLanguage("frontend", "ko", data)
|
||||||
|
applyToOtherLanguage("frontend", "ru", data)
|
||||||
|
applyToOtherLanguage("frontend", "vi", data)
|
||||||
|
applyToOtherLanguage("frontend", "pt", data)
|
||||||
|
applyToOtherLanguage("frontend", "it", data)
|
||||||
|
applyToOtherLanguage("frontend", "ms", data)
|
||||||
|
applyToOtherLanguage("frontend", "tr", data)
|
||||||
|
applyToOtherLanguage("frontend", "ar", data)
|
||||||
|
applyToOtherLanguage("frontend", "he", data)
|
||||||
|
applyToOtherLanguage("frontend", "nl", data)
|
||||||
|
applyToOtherLanguage("frontend", "pl", data)
|
||||||
|
applyToOtherLanguage("frontend", "fi", data)
|
||||||
|
applyToOtherLanguage("frontend", "sv", data)
|
||||||
|
applyToOtherLanguage("frontend", "uk", data)
|
||||||
|
applyToOtherLanguage("frontend", "kk", data)
|
||||||
|
applyToOtherLanguage("frontend", "fa", data)
|
||||||
|
applyToOtherLanguage("frontend", "cs", data)
|
||||||
|
applyToOtherLanguage("frontend", "sk", data)
|
||||||
|
applyToOtherLanguage("frontend", "az", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateI18nBackend(t *testing.T) {
|
func TestGenerateI18nBackend(t *testing.T) {
|
||||||
enData := parseEnData("backend")
|
data := parseAllWords("backend")
|
||||||
writeI18nFile("backend", "en", enData)
|
|
||||||
|
|
||||||
applyToOtherLanguage("backend", "zh", enData)
|
applyToOtherLanguage("backend", "en", data)
|
||||||
applyToOtherLanguage("backend", "es", enData)
|
applyToOtherLanguage("backend", "zh", data)
|
||||||
applyToOtherLanguage("backend", "fr", enData)
|
applyToOtherLanguage("backend", "es", data)
|
||||||
applyToOtherLanguage("backend", "de", enData)
|
applyToOtherLanguage("backend", "fr", data)
|
||||||
applyToOtherLanguage("backend", "ja", enData)
|
applyToOtherLanguage("backend", "de", data)
|
||||||
applyToOtherLanguage("backend", "ko", enData)
|
applyToOtherLanguage("backend", "id", data)
|
||||||
applyToOtherLanguage("backend", "ru", enData)
|
applyToOtherLanguage("backend", "ja", data)
|
||||||
applyToOtherLanguage("backend", "vi", enData)
|
applyToOtherLanguage("backend", "ko", data)
|
||||||
|
applyToOtherLanguage("backend", "ru", data)
|
||||||
|
applyToOtherLanguage("backend", "vi", data)
|
||||||
|
applyToOtherLanguage("backend", "pt", data)
|
||||||
|
applyToOtherLanguage("backend", "it", data)
|
||||||
|
applyToOtherLanguage("backend", "ms", data)
|
||||||
|
applyToOtherLanguage("backend", "tr", data)
|
||||||
|
applyToOtherLanguage("backend", "ar", data)
|
||||||
|
applyToOtherLanguage("backend", "he", data)
|
||||||
|
applyToOtherLanguage("backend", "nl", data)
|
||||||
|
applyToOtherLanguage("backend", "pl", data)
|
||||||
|
applyToOtherLanguage("backend", "fi", data)
|
||||||
|
applyToOtherLanguage("backend", "sv", data)
|
||||||
|
applyToOtherLanguage("backend", "uk", data)
|
||||||
|
applyToOtherLanguage("backend", "kk", data)
|
||||||
|
applyToOtherLanguage("backend", "fa", data)
|
||||||
|
applyToOtherLanguage("backend", "cs", data)
|
||||||
|
applyToOtherLanguage("backend", "sk", data)
|
||||||
|
applyToOtherLanguage("backend", "az", data)
|
||||||
}
|
}
|
||||||
|
|||||||
197
i18n/locales/ar/data.json
Normal file
197
i18n/locales/ar/data.json
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"Failed to add user": "فشل إضافة المستخدم",
|
||||||
|
"Get init score failed, error: %w": "فشل الحصول على النتيجة الأولية، الخطأ: %w",
|
||||||
|
"Please sign out first": "يرجى تسجيل الخروج أولاً",
|
||||||
|
"The application does not allow to sign up new account": "التطبيق لا يسمح بالتسجيل بحساب جديد"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"Challenge method should be S256": "يجب أن تكون طريقة التحدي S256",
|
||||||
|
"DeviceCode Invalid": "رمز الجهاز غير صالح",
|
||||||
|
"Failed to create user, user information is invalid: %s": "فشل إنشاء المستخدم، معلومات المستخدم غير صالحة: %s",
|
||||||
|
"Failed to login in: %s": "فشل تسجيل الدخول: %s",
|
||||||
|
"Invalid token": "الرمز غير صالح",
|
||||||
|
"State expected: %s, but got: %s": "كان من المتوقع الحالة: %s، لكن حصلنا على: %s",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %s, please use another way to sign up": "الحساب الخاص بالمزود: %s واسم المستخدم: %s (%s) غير موجود ولا يُسمح بالتسجيل كحساب جديد عبر %s، يرجى استخدام طريقة أخرى للتسجيل",
|
||||||
|
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "الحساب الخاص بالمزود: %s واسم المستخدم: %s (%s) غير موجود ولا يُسمح بالتسجيل كحساب جديد، يرجى الاتصال بدعم تكنولوجيا المعلومات",
|
||||||
|
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "الحساب الخاص بالمزود: %s واسم المستخدم: %s (%s) مرتبط بالفعل بحساب آخر: %s (%s)",
|
||||||
|
"The application: %s does not exist": "التطبيق: %s غير موجود",
|
||||||
|
"The application: %s has disabled users to signin": "The application: %s has disabled users to signin",
|
||||||
|
"The login method: login with LDAP is not enabled for the application": "طريقة تسجيل الدخول: تسجيل الدخول باستخدام LDAP غير مفعّلة لهذا التطبيق",
|
||||||
|
"The login method: login with SMS is not enabled for the application": "طريقة تسجيل الدخول: تسجيل الدخول باستخدام الرسائل النصية غير مفعّلة لهذا التطبيق",
|
||||||
|
"The login method: login with email is not enabled for the application": "طريقة تسجيل الدخول: تسجيل الدخول باستخدام البريد الإلكتروني غير مفعّلة لهذا التطبيق",
|
||||||
|
"The login method: login with face is not enabled for the application": "طريقة تسجيل الدخول: تسجيل الدخول باستخدام الوجه غير مفعّلة لهذا التطبيق",
|
||||||
|
"The login method: login with password is not enabled for the application": "طريقة تسجيل الدخول: تسجيل الدخول باستخدام كلمة المرور غير مفعّلة لهذا التطبيق",
|
||||||
|
"The organization: %s does not exist": "المنظمة: %s غير موجودة",
|
||||||
|
"The organization: %s has disabled users to signin": "The organization: %s has disabled users to signin",
|
||||||
|
"The provider: %s does not exist": "المزود: %s غير موجود",
|
||||||
|
"The provider: %s is not enabled for the application": "المزود: %s غير مفعّل لهذا التطبيق",
|
||||||
|
"Unauthorized operation": "عملية غير مصرح بها",
|
||||||
|
"Unknown authentication type (not password or provider), form = %s": "نوع مصادقة غير معروف (ليس كلمة مرور أو مزود)، النموذج = %s",
|
||||||
|
"User's tag: %s is not listed in the application's tags": "وسم المستخدم: %s غير مدرج في وسوم التطبيق",
|
||||||
|
"UserCode Expired": "رمز المستخدم منتهي الصلاحية",
|
||||||
|
"UserCode Invalid": "رمز المستخدم غير صالح",
|
||||||
|
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "المستخدم المدفوع %s ليس لديه اشتراك نشط أو معلق والتطبيق: %s ليس لديه تسعير افتراضي",
|
||||||
|
"the application for user %s is not found": "لم يتم العثور على التطبيق الخاص بالمستخدم %s",
|
||||||
|
"the organization: %s is not found": "لم يتم العثور على المنظمة: %s"
|
||||||
|
},
|
||||||
|
"cas": {
|
||||||
|
"Service %s and %s do not match": "الخدمة %s و %s غير متطابقتين"
|
||||||
|
},
|
||||||
|
"check": {
|
||||||
|
"%s does not meet the CIDR format requirements: %s": "%s لا تلبي متطلبات تنسيق CIDR: %s",
|
||||||
|
"Affiliation cannot be blank": "الانتماء لا يمكن أن يكون فارغاً",
|
||||||
|
"CIDR for IP: %s should not be empty": "CIDR لعنوان IP: %s لا يجب أن يكون فارغاً",
|
||||||
|
"Default code does not match the code's matching rules": "الرمز الافتراضي لا يتطابق مع قواعد المطابقة",
|
||||||
|
"DisplayName cannot be blank": "اسم العرض لا يمكن أن يكون فارغاً",
|
||||||
|
"DisplayName is not valid real name": "اسم العرض ليس اسمًا حقيقيًا صالحًا",
|
||||||
|
"Email already exists": "البريد الإلكتروني موجود بالفعل",
|
||||||
|
"Email cannot be empty": "البريد الإلكتروني لا يمكن أن يكون فارغاً",
|
||||||
|
"Email is invalid": "البريد الإلكتروني غير صالح",
|
||||||
|
"Empty username.": "اسم المستخدم فارغ.",
|
||||||
|
"Face data does not exist, cannot log in": "بيانات الوجه غير موجودة، لا يمكن تسجيل الدخول",
|
||||||
|
"Face data mismatch": "عدم تطابق بيانات الوجه",
|
||||||
|
"Failed to parse client IP: %s": "فشل تحليل IP العميل: %s",
|
||||||
|
"FirstName cannot be blank": "الاسم الأول لا يمكن أن يكون فارغاً",
|
||||||
|
"Invitation code cannot be blank": "رمز الدعوة لا يمكن أن يكون فارغاً",
|
||||||
|
"Invitation code exhausted": "رمز الدعوة استُنفِد",
|
||||||
|
"Invitation code is invalid": "رمز الدعوة غير صالح",
|
||||||
|
"Invitation code suspended": "رمز الدعوة موقوف",
|
||||||
|
"LDAP user name or password incorrect": "اسم مستخدم LDAP أو كلمة المرور غير صحيحة",
|
||||||
|
"LastName cannot be blank": "الاسم الأخير لا يمكن أن يكون فارغاً",
|
||||||
|
"Multiple accounts with same uid, please check your ldap server": "حسابات متعددة بنفس uid، يرجى التحقق من خادم ldap الخاص بك",
|
||||||
|
"Organization does not exist": "المنظمة غير موجودة",
|
||||||
|
"Password cannot be empty": "كلمة المرور لا يمكن أن تكون فارغة",
|
||||||
|
"Phone already exists": "الهاتف موجود بالفعل",
|
||||||
|
"Phone cannot be empty": "الهاتف لا يمكن أن يكون فارغاً",
|
||||||
|
"Phone number is invalid": "رقم الهاتف غير صالح",
|
||||||
|
"Please register using the email corresponding to the invitation code": "يرجى التسجيل باستخدام البريد الإلكتروني المطابق لرمز الدعوة",
|
||||||
|
"Please register using the phone corresponding to the invitation code": "يرجى التسجيل باستخدام الهاتف المطابق لرمز الدعوة",
|
||||||
|
"Please register using the username corresponding to the invitation code": "يرجى التسجيل باستخدام اسم المستخدم المطابق لرمز الدعوة",
|
||||||
|
"Session outdated, please login again": "الجلسة منتهية الصلاحية، يرجى تسجيل الدخول مرة أخرى",
|
||||||
|
"The invitation code has already been used": "رمز الدعوة تم استخدامه بالفعل",
|
||||||
|
"The user is forbidden to sign in, please contact the administrator": "المستخدم ممنوع من تسجيل الدخول، يرجى الاتصال بالمسؤول",
|
||||||
|
"The user: %s doesn't exist in LDAP server": "المستخدم: %s غير موجود في خادم LDAP",
|
||||||
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "اسم المستخدم يمكن أن يحتوي فقط على أحرف وأرقام، شرطات سفلية أو علوية، لا يمكن أن تحتوي على شرطات متتالية، ولا يمكن أن يبدأ أو ينتهي بشرطة.",
|
||||||
|
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "Hesap alanı \\\"%s\\\" için \\\"%s\\\" değeri, hesap öğesi regex'iyle eşleşmiyor",
|
||||||
|
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "Kayıt alanı \\\"%s\\\" için \\\"%s\\\" değeri, \\\"%s\\\" uygulamasının kayıt öğesi regex'iyle eşleşmiyor",
|
||||||
|
"Username already exists": "اسم المستخدم موجود بالفعل",
|
||||||
|
"Username cannot be an email address": "اسم المستخدم لا يمكن أن يكون عنوان بريد إلكتروني",
|
||||||
|
"Username cannot contain white spaces": "اسم المستخدم لا يمكن أن يحتوي على مسافات",
|
||||||
|
"Username cannot start with a digit": "اسم المستخدم لا يمكن أن يبدأ برقم",
|
||||||
|
"Username is too long (maximum is 255 characters).": "اسم المستخدم طويل جداً (الحد الأقصى 255 حرفاً).",
|
||||||
|
"Username must have at least 2 characters": "اسم المستخدم يجب أن يحتوي على حرفين على الأقل",
|
||||||
|
"Username supports email format. Also The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline. Also pay attention to the email format.": "اسم المستخدم يدعم تنسيق البريد الإلكتروني. كما أن اسم المستخدم يمكن أن يحتوي فقط على أحرف وأرقام، شرطات سفلية أو علوية، لا يمكن أن تحتوي على شرطات متتالية، ولا يمكن أن يبدأ أو ينتهي بشرطة. انتبه أيضًا لتنسيق البريد الإلكتروني.",
|
||||||
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "لقد قمت بإدخال كلمة المرور أو الرمز الخطأ عدة مرات، يرجى الانتظار %d دقائق ثم المحاولة مرة أخرى",
|
||||||
|
"Your IP address: %s has been banned according to the configuration of: ": "عنوان IP الخاص بك: %s تم حظره وفقًا لتكوين: ",
|
||||||
|
"Your password has expired. Please reset your password by clicking \\\"Forgot password\\\"": "Şifrenizin süresi doldu. Lütfen \\\"Şifremi unuttum\\\"a tıklayarak şifrenizi sıfırlayın",
|
||||||
|
"Your region is not allow to signup by phone": "منطقتك لا تسمح بالتسجيل عبر الهاتف",
|
||||||
|
"password or code is incorrect": "كلمة المرور أو الرمز غير صحيح",
|
||||||
|
"password or code is incorrect, you have %s remaining chances": "كلمة المرور أو الرمز غير صحيح، لديك %s فرصة متبقية",
|
||||||
|
"unsupported password type: %s": "نوع كلمة المرور غير مدعوم: %s"
|
||||||
|
},
|
||||||
|
"enforcer": {
|
||||||
|
"the adapter: %s is not found": "المحول: %s غير موجود"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"Failed to import groups": "فشل استيراد المجموعات",
|
||||||
|
"Failed to import users": "فشل استيراد المستخدمين",
|
||||||
|
"Missing parameter": "المعلمة مفقودة",
|
||||||
|
"Only admin user can specify user": "فقط المسؤول يمكنه تحديد المستخدم",
|
||||||
|
"Please login first": "يرجى تسجيل الدخول أولاً",
|
||||||
|
"The organization: %s should have one application at least": "المنظمة: %s يجب أن تحتوي على تطبيق واحد على الأقل",
|
||||||
|
"The user: %s doesn't exist": "المستخدم: %s غير موجود",
|
||||||
|
"Wrong userId": "معرف المستخدم غير صحيح",
|
||||||
|
"don't support captchaProvider: ": "لا يدعم captchaProvider: ",
|
||||||
|
"this operation is not allowed in demo mode": "هذه العملية غير مسموح بها في وضع العرض التوضيحي",
|
||||||
|
"this operation requires administrator to perform": "هذه العملية تتطلب مسؤولاً لتنفيذها"
|
||||||
|
},
|
||||||
|
"invitation": {
|
||||||
|
"Invitation %s does not exist": "Invitation %s does not exist"
|
||||||
|
},
|
||||||
|
"ldap": {
|
||||||
|
"Ldap server exist": "خادم LDAP موجود"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"Please link first": "يرجى الربط أولاً",
|
||||||
|
"This application has no providers": "هذا التطبيق لا يحتوي على مزودين",
|
||||||
|
"This application has no providers of type": "هذا التطبيق لا يحتوي على مزودين من النوع",
|
||||||
|
"This provider can't be unlinked": "لا يمكن فصل هذا المزود",
|
||||||
|
"You are not the global admin, you can't unlink other users": "أنت لست المسؤول العام، لا يمكنك فصل مستخدمين آخرين",
|
||||||
|
"You can't unlink yourself, you are not a member of any application": "لا يمكنك فصل نفسك، أنت لست عضواً في أي تطبيق"
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"Only admin can modify the %s.": "فقط المسؤول يمكنه تعديل %s.",
|
||||||
|
"The %s is immutable.": "%s غير قابل للتعديل.",
|
||||||
|
"Unknown modify rule %s.": "قاعدة تعديل غير معروفة %s.",
|
||||||
|
"adding a new user to the 'built-in' organization is currently disabled. Please note: all users in the 'built-in' organization are global administrators in Casdoor. Refer to the docs: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. If you still wish to create a user for the 'built-in' organization, go to the organization's settings page and enable the 'Has privilege consent' option.": "إضافة مستخدم جديد إلى المنظمة \"المدمجة\" غير متوفر حاليًا. يرجى ملاحظة: جميع المستخدمين في المنظمة \"المدمجة\" هم مسؤولون عالميون في Casdoor. يرجى الرجوع إلى الوثائق: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. إذا كنت لا تزال ترغب في إنشاء مستخدم للمنظمة \"المدمجة\"، اไป إلى صفحة إعدادات المنظمة وقم بتمكين خيار \"لديه موافقة صلاحية\"."
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"The permission: \\\"%s\\\" doesn't exist": "İzin: \\\"%s\\\" mevcut değil"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"Invalid application id": "معرف التطبيق غير صالح",
|
||||||
|
"the provider: %s does not exist": "المزود: %s غير موجود"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"User is nil for tag: avatar": "المستخدم nil للوسم: avatar",
|
||||||
|
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "اسم المستخدم أو fullFilePath فارغ: username = %s، fullFilePath = %s"
|
||||||
|
},
|
||||||
|
"saml": {
|
||||||
|
"Application %s not found": "التطبيق %s غير موجود"
|
||||||
|
},
|
||||||
|
"saml_sp": {
|
||||||
|
"provider %s's category is not SAML": "فئة المزود %s ليست SAML"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"Empty parameters for emailForm: %v": "معلمات فارغة لـ emailForm: %v",
|
||||||
|
"Invalid Email receivers: %s": "مستقبلو البريد الإلكتروني غير صالحين: %s",
|
||||||
|
"Invalid phone receivers: %s": "مستقلو الهاتف غير صالحين: %s"
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"The objectKey: %s is not allowed": "مفتاح الكائن: %s غير مسموح به",
|
||||||
|
"The provider type: %s is not supported": "نوع المزود: %s غير مدعوم"
|
||||||
|
},
|
||||||
|
"subscription": {
|
||||||
|
"Error": "خطأ"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"Grant_type: %s is not supported in this application": "Grant_type: %s غير مدعوم في هذا التطبيق",
|
||||||
|
"Invalid application or wrong clientSecret": "تطبيق غير صالح أو clientSecret خاطئ",
|
||||||
|
"Invalid client_id": "client_id غير صالح",
|
||||||
|
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s غير موجود في قائمة Redirect URI المسموح بها",
|
||||||
|
"Token not found, invalid accessToken": "الرمز غير موجود، accessToken غير صالح"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"Display name cannot be empty": "اسم العرض لا يمكن أن يكون فارغاً",
|
||||||
|
"MFA email is enabled but email is empty": "تم تمكين MFA للبريد الإلكتروني لكن البريد الإلكتروني فارغ",
|
||||||
|
"MFA phone is enabled but phone number is empty": "تم تمكين MFA للهاتف لكن رقم الهاتف فارغ",
|
||||||
|
"New password cannot contain blank space.": "كلمة المرور الجديدة لا يمكن أن تحتوي على مسافات.",
|
||||||
|
"The new password must be different from your current password": "The new password must be different from your current password",
|
||||||
|
"the user's owner and name should not be empty": "مالك المستخدم واسمه لا يجب أن يكونا فارغين"
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"No application is found for userId: %s": "لم يتم العثور على تطبيق لـ userId: %s",
|
||||||
|
"No provider for category: %s is found for application: %s": "لم يتم العثور على مزود للفئة: %s للتطبيق: %s",
|
||||||
|
"The provider: %s is not found": "المزود: %s غير موجود"
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"Invalid captcha provider.": "مزود captcha غير صالح.",
|
||||||
|
"Phone number is invalid in your region %s": "رقم الهاتف غير صالح في منطقتك %s",
|
||||||
|
"The verification code has already been used!": "رمز التحقق تم استخدامه بالفعل!",
|
||||||
|
"The verification code has not been sent yet!": "رمز التحقق لم يُرسل بعد!",
|
||||||
|
"Turing test failed.": "فشل اختبار تورينغ.",
|
||||||
|
"Unable to get the email modify rule.": "غير قادر على الحصول على قاعدة تعديل البريد الإلكتروني.",
|
||||||
|
"Unable to get the phone modify rule.": "غير قادر على الحصول على قاعدة تعديل الهاتف.",
|
||||||
|
"Unknown type": "نوع غير معروف",
|
||||||
|
"Wrong verification code!": "رمز التحقق خاطئ!",
|
||||||
|
"You should verify your code in %d min!": "يجب عليك التحقق من الرمز خلال %d دقيقة!",
|
||||||
|
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "lütfen uygulama için \\\"Sağlayıcılar\\\" listesine bir SMS sağlayıcı ekleyin: %s",
|
||||||
|
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "lütfen uygulama için \\\"Sağlayıcılar\\\" listesine bir E-posta sağlayıcı ekleyin: %s",
|
||||||
|
"the user does not exist, please sign up first": "المستخدم غير موجود، يرجى التسجيل أولاً"
|
||||||
|
},
|
||||||
|
"webauthn": {
|
||||||
|
"Found no credentials for this user": "Found no credentials for this user",
|
||||||
|
"Please call WebAuthnSigninBegin first": "يرجى استدعاء WebAuthnSigninBegin أولاً"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user