mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-20 01:50:32 +08:00
Compare commits
711 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d943d5cc61 | ||
|
|
19ed35f964 | ||
|
|
5757021e87 | ||
|
|
259a4e1307 | ||
|
|
034d822dd5 | ||
|
|
a8502d1173 | ||
|
|
3c2f7b7fc8 | ||
|
|
fbc73de3bb | ||
|
|
479daf4fa4 | ||
|
|
d129202b95 | ||
|
|
c1f553440e | ||
|
|
7dcae2d183 | ||
|
|
5ec0c7a890 | ||
|
|
051752340d | ||
|
|
c87c001da3 | ||
|
|
12bc419659 | ||
|
|
d5f18f2d64 | ||
|
|
02c06bc93c | ||
|
|
40aa9a4693 | ||
|
|
630b84f534 | ||
|
|
339a85e4b0 | ||
|
|
c22ab44894 | ||
|
|
c3fb48f473 | ||
|
|
a111fd672c | ||
|
|
9fd175eefd | ||
|
|
d9bcce9485 | ||
|
|
d183b9eca9 | ||
|
|
f24d9ae251 | ||
|
|
030c1caa50 | ||
|
|
cee2c608a2 | ||
|
|
82d0e895e0 | ||
|
|
dee9bac110 | ||
|
|
e7a6986b62 | ||
|
|
b91b4aec91 | ||
|
|
fe48c38bc6 | ||
|
|
1be777c08f | ||
|
|
8d54bfad8a | ||
|
|
728fe11a3c | ||
|
|
69e0f4e40d | ||
|
|
ba32a45693 | ||
|
|
a4d83af768 | ||
|
|
5b8f6415d9 | ||
|
|
5389cb435c | ||
|
|
9b6131890c | ||
|
|
ffc0a0e0d5 | ||
|
|
ff22bf507f | ||
|
|
2d4103d751 | ||
|
|
4611b59b08 | ||
|
|
445d3c9d0e | ||
|
|
dbebd1846f | ||
|
|
2fcc8f5bfe | ||
|
|
4b65320a96 | ||
|
|
5e8897e41b | ||
|
|
ba1646a0c3 | ||
|
|
c1cd187558 | ||
|
|
519fd655cf | ||
|
|
377ac05928 | ||
|
|
4f124ff140 | ||
|
|
d5f802ec7d | ||
|
|
64d3b7e87f | ||
|
|
dfce1bd74c | ||
|
|
067ae5448f | ||
|
|
9943e3c316 | ||
|
|
0c665edcbc | ||
|
|
5015bf1c7d | ||
|
|
2ec947d488 | ||
|
|
10a85f2386 | ||
|
|
0d13512eb1 | ||
|
|
b60856be5e | ||
|
|
4b4c9be71b | ||
|
|
e79e3c36d0 | ||
|
|
cc8c9b32ef | ||
|
|
efdcb3279d | ||
|
|
3818492065 | ||
|
|
f4890a6a22 | ||
|
|
fa45d3083a | ||
|
|
caa1ffe3c8 | ||
|
|
c88edc4d3e | ||
|
|
f5bc76016d | ||
|
|
8345295d0c | ||
|
|
3bdc0e0d1b | ||
|
|
8dcb56ea71 | ||
|
|
b95f107a60 | ||
|
|
d6c2d0f3e8 | ||
|
|
7cfece3019 | ||
|
|
3efbcc739d | ||
|
|
99ef329325 | ||
|
|
8f0995ca34 | ||
|
|
e64f181e28 | ||
|
|
db56f54b8c | ||
|
|
bf642b35d4 | ||
|
|
5ee5299a68 | ||
|
|
e7f395cfd4 | ||
|
|
29d512d316 | ||
|
|
5814ae6baf | ||
|
|
52145abdc8 | ||
|
|
157e515310 | ||
|
|
47ed2e903c | ||
|
|
822ad14ea9 | ||
|
|
3355f8644e | ||
|
|
00f06930ba | ||
|
|
755d912f61 | ||
|
|
95f2a3b311 | ||
|
|
cb625b3fa2 | ||
|
|
16cc64f08d | ||
|
|
318cf52b33 | ||
|
|
07f9a9ee96 | ||
|
|
c2110ef59d | ||
|
|
2f70e77e53 | ||
|
|
98f6cc0085 | ||
|
|
370e835499 | ||
|
|
f43d01c5c2 | ||
|
|
4ca5f4b196 | ||
|
|
cf9e628a3e | ||
|
|
726e4e3dc1 | ||
|
|
0adb9b0047 | ||
|
|
00ab156453 | ||
|
|
589c0404d2 | ||
|
|
b2f2674d3e | ||
|
|
a555d27dd2 | ||
|
|
8d8c662e58 | ||
|
|
94c78593fc | ||
|
|
f4265d015a | ||
|
|
96e2f286ee | ||
|
|
29807b82e1 | ||
|
|
e0b7286882 | ||
|
|
1762d19787 | ||
|
|
2f71d9743b | ||
|
|
6ba658ac60 | ||
|
|
cc47f3b65d | ||
|
|
eca1d23e35 | ||
|
|
6947ebd152 | ||
|
|
967113689d | ||
|
|
b73b9a65b6 | ||
|
|
70a550d8bc | ||
|
|
113398c36b | ||
|
|
667158f585 | ||
|
|
dc9d2389a5 | ||
|
|
c8b8488797 | ||
|
|
07fa438348 | ||
|
|
d2565e03c8 | ||
|
|
cc2797ed27 | ||
|
|
746967e18a | ||
|
|
14c4f60a40 | ||
|
|
79fd6ff5d3 | ||
|
|
04bc8628a8 | ||
|
|
d6c9ee508c | ||
|
|
d224e728a3 | ||
|
|
36b7993994 | ||
|
|
c10ccd8106 | ||
|
|
a04702a8d0 | ||
|
|
e888ff8475 | ||
|
|
7923bffa6d | ||
|
|
bfd5d0172a | ||
|
|
4e92a8273c | ||
|
|
c46925dbe8 | ||
|
|
7f39aee9c4 | ||
|
|
b86087b2af | ||
|
|
bddd57cda8 | ||
|
|
44b59d866a | ||
|
|
95600414d9 | ||
|
|
9eb09b7db0 | ||
|
|
91c0282040 | ||
|
|
2b6f397bb9 | ||
|
|
db7f4a4af9 | ||
|
|
825de2bdaa | ||
|
|
0b3742b0b1 | ||
|
|
1394dce306 | ||
|
|
64fc810359 | ||
|
|
abba56a6f3 | ||
|
|
cc933cf5f3 | ||
|
|
37829062ad | ||
|
|
f5fdf0af6a | ||
|
|
21f433d278 | ||
|
|
102e22f2c7 | ||
|
|
8d6756fe9a | ||
|
|
63f33d0ad9 | ||
|
|
1c2e9064fe | ||
|
|
54ef2ec09f | ||
|
|
d84ddda607 | ||
|
|
30a2fdef37 | ||
|
|
87e6fb63e1 | ||
|
|
e9e0721b34 | ||
|
|
5ec678fa28 | ||
|
|
77fffcacac | ||
|
|
0e71e603ac | ||
|
|
cbf973882d | ||
|
|
467d709b8e | ||
|
|
4d71725bf5 | ||
|
|
9e920181d2 | ||
|
|
db892333fe | ||
|
|
acaee2e892 | ||
|
|
5fd681e971 | ||
|
|
b1db47bad1 | ||
|
|
7520b71198 | ||
|
|
7792f4589d | ||
|
|
609e9785e4 | ||
|
|
b7f2f9056f | ||
|
|
ebc5fe454c | ||
|
|
2e1b51910f | ||
|
|
5526286ad8 | ||
|
|
795240687d | ||
|
|
6c30ccfb14 | ||
|
|
802df55009 | ||
|
|
353bf46daf | ||
|
|
122970bb54 | ||
|
|
fa3681ed75 | ||
|
|
08e59796c8 | ||
|
|
568372d077 | ||
|
|
4f2668cd90 | ||
|
|
8cb1291f6f | ||
|
|
15786070bb | ||
|
|
ba0d089589 | ||
|
|
9a71bb02e3 | ||
|
|
a1b5282da9 | ||
|
|
80d2738863 | ||
|
|
964b60da29 | ||
|
|
ea8017dd4b | ||
|
|
391bc3ebc9 | ||
|
|
2326d55f73 | ||
|
|
890030b3ef | ||
|
|
725ee3393e | ||
|
|
e61e46f1e5 | ||
|
|
63c720985b | ||
|
|
7582ba0b6f | ||
|
|
a04f669580 | ||
|
|
8cb96142db | ||
|
|
d62a5fd7de | ||
|
|
c7a3ad901b | ||
|
|
0ef87632f4 | ||
|
|
331862a94c | ||
|
|
08eaf298c1 | ||
|
|
69794fe29d | ||
|
|
09f430266b | ||
|
|
52d9017611 | ||
|
|
c70c62f52e | ||
|
|
355b0b35d0 | ||
|
|
e4846807cd | ||
|
|
f4a59de3a5 | ||
|
|
a1b16f88d1 | ||
|
|
90ec8ec787 | ||
|
|
ea8971ff29 | ||
|
|
bd41425039 | ||
|
|
9d9a1da07f | ||
|
|
465d25a272 | ||
|
|
ef1195960e | ||
|
|
089f4ff480 | ||
|
|
88aa444ad1 | ||
|
|
1c5ce46bd5 | ||
|
|
14d09cad2c | ||
|
|
06006c87b8 | ||
|
|
a4edf47dc4 | ||
|
|
e68b0198f1 | ||
|
|
015961bc3c | ||
|
|
5d98cc6ac5 | ||
|
|
b3eec024b8 | ||
|
|
eefcfd8440 | ||
|
|
c6b2106c94 | ||
|
|
edf621f4d5 | ||
|
|
e50c6cd4b5 | ||
|
|
9c3117beb0 | ||
|
|
4ca307564c | ||
|
|
15a6f64fdc | ||
|
|
75e917a070 | ||
|
|
e1182bb635 | ||
|
|
2b70698c2a | ||
|
|
2def51ad99 | ||
|
|
398ba19fa5 | ||
|
|
8674b4853a | ||
|
|
518c3f9f69 | ||
|
|
495b64995f | ||
|
|
f3c10c59cb | ||
|
|
c05b27c38f | ||
|
|
004282f970 | ||
|
|
b7e0a4fe4e | ||
|
|
d3c4f14bd4 | ||
|
|
8d9451124c | ||
|
|
cfd876226a | ||
|
|
8c84ba53b4 | ||
|
|
d103a2bd8c | ||
|
|
b58d5ebb2c | ||
|
|
8c6f0a31b6 | ||
|
|
718fc4df74 | ||
|
|
3f7e83261d | ||
|
|
5c2f96bda0 | ||
|
|
40fbca7db4 | ||
|
|
1b74a58d06 | ||
|
|
9feefc31f9 | ||
|
|
a5783598ff | ||
|
|
c55fa4f452 | ||
|
|
44150a6781 | ||
|
|
d16569d461 | ||
|
|
6aeadfa3bd | ||
|
|
05222c7a29 | ||
|
|
8c66ef6860 | ||
|
|
a271ef0719 | ||
|
|
d79544f34a | ||
|
|
ea692c4e73 | ||
|
|
0a461bf19e | ||
|
|
9fb4b4d394 | ||
|
|
af3def97bf | ||
|
|
8ea906a132 | ||
|
|
5afe3ed650 | ||
|
|
ca9fcf7edc | ||
|
|
c0ab24205d | ||
|
|
0c657f0487 | ||
|
|
acca9eacdc | ||
|
|
9f5c6b1e05 | ||
|
|
4001e63f16 | ||
|
|
5a6f3cdea5 | ||
|
|
bf77992457 | ||
|
|
1a2d85102c | ||
|
|
629ae5a54b | ||
|
|
3be7d3b273 | ||
|
|
e39c3b7613 | ||
|
|
bc5e748e5c | ||
|
|
eabeef094a | ||
|
|
7a2230f63e | ||
|
|
512a451800 | ||
|
|
c9ae582802 | ||
|
|
3d493b8d8f | ||
|
|
1c01a34814 | ||
|
|
21b4e0e51d | ||
|
|
c632c3c307 | ||
|
|
3bbb0fb58e | ||
|
|
170ae241dc | ||
|
|
8d7fdb93ac | ||
|
|
66f36d780a | ||
|
|
aa5b0f168a | ||
|
|
521cda7b4e | ||
|
|
07178a7125 | ||
|
|
ded859aebc | ||
|
|
bb09b24b0f | ||
|
|
4a930121c4 | ||
|
|
d83b86df37 | ||
|
|
59602a1325 | ||
|
|
705d2ede6b | ||
|
|
a22dfd8954 | ||
|
|
e986d3ab8e | ||
|
|
0fc388d662 | ||
|
|
896f8b808f | ||
|
|
08a2b5a69d | ||
|
|
2e5602d4c1 | ||
|
|
dc0712c8a5 | ||
|
|
2be7d28a6c | ||
|
|
21b36bbb47 | ||
|
|
6ae8e537b9 | ||
|
|
49ede30fb8 | ||
|
|
89b9f73b32 | ||
|
|
59ac7d6317 | ||
|
|
4a0f22355e | ||
|
|
1bfddc5d53 | ||
|
|
3923574d57 | ||
|
|
c963ba6098 | ||
|
|
6cfe3c1586 | ||
|
|
857c9e540f | ||
|
|
64950e900f | ||
|
|
00b00a20de | ||
|
|
1bca883f4f | ||
|
|
66bb34f051 | ||
|
|
122792d247 | ||
|
|
8e6e427ce6 | ||
|
|
264bf5c447 | ||
|
|
d59ba750f4 | ||
|
|
dcf85591e2 | ||
|
|
98e0c1aa85 | ||
|
|
0124bb27a4 | ||
|
|
7eb4da6e54 | ||
|
|
f26b6c2d11 | ||
|
|
8343007afa | ||
|
|
fbfae75780 | ||
|
|
a42a76400e | ||
|
|
2ba44748c2 | ||
|
|
62397ce3b1 | ||
|
|
234a9b9060 | ||
|
|
8b8eeea73d | ||
|
|
6ac364074a | ||
|
|
1e9cca02fc | ||
|
|
79258e1dd0 | ||
|
|
c9f93b0785 | ||
|
|
8f21ad0928 | ||
|
|
81a2f91d83 | ||
|
|
9c233e42c0 | ||
|
|
64f85fdc6c | ||
|
|
54e97d57bf | ||
|
|
68018c6d12 | ||
|
|
b189993547 | ||
|
|
a43db3e55a | ||
|
|
d3a8ab8347 | ||
|
|
e0b6270f50 | ||
|
|
0f69f4f07c | ||
|
|
3a806c14ff | ||
|
|
165e6a9876 | ||
|
|
504ab7a9ed | ||
|
|
50a9ca2b4c | ||
|
|
910ea04384 | ||
|
|
6dc3fd0f45 | ||
|
|
01d5f3b776 | ||
|
|
00134dda4f | ||
|
|
92610f9971 | ||
|
|
32afafd56c | ||
|
|
ce60a76920 | ||
|
|
586da5caac | ||
|
|
5b9a91db85 | ||
|
|
9dd43238f0 | ||
|
|
b11b3b6021 | ||
|
|
3274bd0c7c | ||
|
|
02b1feb2e5 | ||
|
|
7ea469e876 | ||
|
|
8bc9163def | ||
|
|
7871541cd5 | ||
|
|
985205a978 | ||
|
|
134b2db3d7 | ||
|
|
c122e89fc1 | ||
|
|
a7912eecc1 | ||
|
|
cc31c1d666 | ||
|
|
5a852bfd1d | ||
|
|
9fe310f2b7 | ||
|
|
2e68f8c6d5 | ||
|
|
7f22f05073 | ||
|
|
d14ba476b9 | ||
|
|
1025a7c118 | ||
|
|
00ba783fc4 | ||
|
|
56b2c91217 | ||
|
|
e1673c1541 | ||
|
|
c4de5449af | ||
|
|
8ddc0cd626 | ||
|
|
8132ea9621 | ||
|
|
35443f82c6 | ||
|
|
222b00f0a8 | ||
|
|
3d3a331784 | ||
|
|
30bed3cb47 | ||
|
|
8e5f3c18e1 | ||
|
|
f672045b45 | ||
|
|
440aad2369 | ||
|
|
ec91ded5aa | ||
|
|
04a246355e | ||
|
|
4162ab4984 | ||
|
|
cd89f7a559 | ||
|
|
c983ee7ca6 | ||
|
|
89fb4e555e | ||
|
|
98ec187019 | ||
|
|
a9e68385ef | ||
|
|
b65ae4eec1 | ||
|
|
741d1feec8 | ||
|
|
56be5f9a51 | ||
|
|
ec5a574ce6 | ||
|
|
6142ca52ca | ||
|
|
eb658ad8ee | ||
|
|
860fe91725 | ||
|
|
081da18403 | ||
|
|
7434d0f3d3 | ||
|
|
1cb5ae54c5 | ||
|
|
29049297d8 | ||
|
|
58c7a60220 | ||
|
|
2e7ef69f07 | ||
|
|
90e617b2dc | ||
|
|
95cda41732 | ||
|
|
0960578c35 | ||
|
|
645f8e5418 | ||
|
|
11f1f8f440 | ||
|
|
57a73edc4a | ||
|
|
dc89f2b0f6 | ||
|
|
badd75b894 | ||
|
|
215b96c881 | ||
|
|
0f8a46fe0b | ||
|
|
089ff6c6eb | ||
|
|
b23e8cf90c | ||
|
|
2fff5f79ce | ||
|
|
705c0159d4 | ||
|
|
290c5aac9e | ||
|
|
dbf11d61a7 | ||
|
|
bd888fad38 | ||
|
|
31d981baf1 | ||
|
|
02edb89602 | ||
|
|
f39378562f | ||
|
|
665e77b797 | ||
|
|
b2a75a527b | ||
|
|
3ccf54581a | ||
|
|
66d953a6c1 | ||
|
|
9bc29e25ef | ||
|
|
947d132362 | ||
|
|
3e41ce0104 | ||
|
|
85c76e2ed1 | ||
|
|
b103683fea | ||
|
|
6508d96162 | ||
|
|
e6862713bb | ||
|
|
798386655f | ||
|
|
45bd3b316b | ||
|
|
4b9ce5f401 | ||
|
|
18806f07a8 | ||
|
|
338c589e51 | ||
|
|
f48e04ef15 | ||
|
|
df7ac93feb | ||
|
|
55e0fe14ca | ||
|
|
e206ec5057 | ||
|
|
2ea5e7ec78 | ||
|
|
43db2de8d8 | ||
|
|
08779b607d | ||
|
|
18ea101db8 | ||
|
|
23955c4450 | ||
|
|
d8e5194e99 | ||
|
|
22aeebe8d3 | ||
|
|
162c7261a3 | ||
|
|
fb9f18af2c | ||
|
|
b7cbc0e015 | ||
|
|
d361a39ffc | ||
|
|
db5ecddd3f | ||
|
|
9346587ca6 | ||
|
|
fce4efcb84 | ||
|
|
f4db41acb3 | ||
|
|
e4b6f758a7 | ||
|
|
1589da0a62 | ||
|
|
892cb39e3e | ||
|
|
827930a020 | ||
|
|
400e335e68 | ||
|
|
7bea33dabd | ||
|
|
33e61cc5cf | ||
|
|
19c1ef1b68 | ||
|
|
6e5aa2bc40 | ||
|
|
e2f6efd1ab | ||
|
|
d2d4c411ab | ||
|
|
efab90ef3f | ||
|
|
5a3abdbc95 | ||
|
|
aa706019f2 | ||
|
|
5d1133dac9 | ||
|
|
885592d774 | ||
|
|
a1851c2b78 | ||
|
|
de67ee9014 | ||
|
|
c76a432003 | ||
|
|
e3b3a76088 | ||
|
|
75f23478d1 | ||
|
|
a24665714a | ||
|
|
8a4311c85c | ||
|
|
f442f11568 | ||
|
|
fffada894c | ||
|
|
6095af0512 | ||
|
|
6e47cd5bd5 | ||
|
|
813204194f | ||
|
|
76e4490aa7 | ||
|
|
d8aa6a1c95 | ||
|
|
1407ba5af7 | ||
|
|
82f4f542ed | ||
|
|
79f1c62ff7 | ||
|
|
be0cfa132e | ||
|
|
d509c61816 | ||
|
|
0f7cd56441 | ||
|
|
5b1b8662ac | ||
|
|
7b32207443 | ||
|
|
904faae6eb | ||
|
|
a093f3af5a | ||
|
|
9971b368d0 | ||
|
|
6ad1f5d33b | ||
|
|
fa2653c836 | ||
|
|
6dcece3990 | ||
|
|
785f306c4b | ||
|
|
3065bacad2 | ||
|
|
5779d57960 | ||
|
|
cfe1eb9d50 | ||
|
|
93564e8389 | ||
|
|
92c35948e7 | ||
|
|
ac6c82f9d8 | ||
|
|
c26c9ba0e8 | ||
|
|
35e482f24e | ||
|
|
ea9bdbf45e | ||
|
|
ee8f2a6046 | ||
|
|
ef1995de4f | ||
|
|
6fbdc556ac | ||
|
|
7a0ed4ebaf | ||
|
|
3793029491 | ||
|
|
8ce8169d65 | ||
|
|
0faeaae78f | ||
|
|
43113a0ee4 | ||
|
|
c317d601a5 | ||
|
|
6002395d12 | ||
|
|
3437885723 | ||
|
|
f0ffacb6a5 | ||
|
|
5326cafbed | ||
|
|
987bb14a6b | ||
|
|
bb5e110049 | ||
|
|
0fc7015c61 | ||
|
|
4dd0c1a457 | ||
|
|
085f0ca239 | ||
|
|
d586efc242 | ||
|
|
36895801f0 | ||
|
|
6774b0379c | ||
|
|
2934d6bdeb | ||
|
|
39f2f609c3 | ||
|
|
3a13b8e73c | ||
|
|
5c49693bc0 | ||
|
|
45071bb8c7 | ||
|
|
4ea6529969 | ||
|
|
bc368a4361 | ||
|
|
cc8485a7db | ||
|
|
d6c8bb87c2 | ||
|
|
73ffb22c32 | ||
|
|
658dd79d81 | ||
|
|
f5ceae901b | ||
|
|
2029a28d7b | ||
|
|
0127f8fb63 | ||
|
|
d6715c7601 | ||
|
|
6c2c5be33d | ||
|
|
2063f1d27d | ||
|
|
b1ac60fa02 | ||
|
|
3bcc292da2 | ||
|
|
437289438f | ||
|
|
d11bb49eac | ||
|
|
fa358654e6 | ||
|
|
6880c28194 | ||
|
|
9af2ac49be | ||
|
|
3d4c987d32 | ||
|
|
7fe9705303 | ||
|
|
b6b433f26a | ||
|
|
da38f7a6ba | ||
|
|
540adfed20 | ||
|
|
39d208ccd0 | ||
|
|
c24de7d180 | ||
|
|
fa141a0edb | ||
|
|
1c97501016 | ||
|
|
6368298715 | ||
|
|
26502350d3 | ||
|
|
55b62e9786 | ||
|
|
7f367ae346 | ||
|
|
8b921b2c1e | ||
|
|
808e6c6283 | ||
|
|
f89f454e0e | ||
|
|
63a4066a8d | ||
|
|
7049e09570 | ||
|
|
80c47dd8c6 | ||
|
|
9980ef1975 | ||
|
|
c8cd37058e | ||
|
|
18c021b009 | ||
|
|
d0e243fca3 | ||
|
|
1908f528c8 | ||
|
|
0ce7420907 | ||
|
|
4a170d1d56 | ||
|
|
2a481d6a2e | ||
|
|
4e22e0523d | ||
|
|
df948e9e34 | ||
|
|
f014554415 | ||
|
|
1fd6ee388c | ||
|
|
202a94a8e5 | ||
|
|
85523fa9d4 | ||
|
|
64c9548019 | ||
|
|
d38cfa28b8 | ||
|
|
65f01c1a09 | ||
|
|
5e04b8c726 | ||
|
|
45ec95b859 | ||
|
|
9e8bde509a | ||
|
|
e792e814d4 | ||
|
|
1dfcfb307f | ||
|
|
f3f902af45 | ||
|
|
ba4185c9b1 | ||
|
|
dfa77ab25d | ||
|
|
e91fdca5e7 | ||
|
|
40fb336e95 | ||
|
|
62c69a89c1 | ||
|
|
717d655fdb | ||
|
|
e301121c21 | ||
|
|
a09a8b2af0 | ||
|
|
df5514ef31 | ||
|
|
60ad52f7ae | ||
|
|
d44a5ee2c1 | ||
|
|
bf4bbeeb15 | ||
|
|
475f79ffc8 | ||
|
|
abe1e5eccb | ||
|
|
e4778b967f | ||
|
|
e71311b1af | ||
|
|
906fe9758e | ||
|
|
65eee22099 | ||
|
|
7203dcbe22 | ||
|
|
46fb2b4bff | ||
|
|
15f9d71127 | ||
|
|
b5b86262d6 | ||
|
|
9b45e5fe43 | ||
|
|
277dec0af3 | ||
|
|
20b70c323d | ||
|
|
faeb93494c | ||
|
|
001496b90f | ||
|
|
40587f35e3 | ||
|
|
4b66c24bb6 | ||
|
|
8543d09d05 | ||
|
|
2dd4afbe7b | ||
|
|
101d418257 | ||
|
|
c81118feff | ||
|
|
ea09beffe2 | ||
|
|
bac289d387 | ||
|
|
cf2152846c | ||
|
|
7bffb03b29 | ||
|
|
6e015a959f | ||
|
|
34f87c78d8 | ||
|
|
e57590ab70 | ||
|
|
6824d558ca | ||
|
|
9677282017 | ||
|
|
4daa4416c1 | ||
|
|
35df55cf70 | ||
|
|
09b39d69f6 | ||
|
|
7304e62f95 | ||
|
|
cdc1445883 | ||
|
|
d30c96038d | ||
|
|
ee1e08ae20 | ||
|
|
8790e95147 | ||
|
|
3ea58a0cdc | ||
|
|
09ffe69382 | ||
|
|
1483d08df7 | ||
|
|
cd39695443 | ||
|
|
6459926a0f | ||
|
|
b13ec27981 | ||
|
|
84c1f2634e | ||
|
|
b9adda2277 | ||
|
|
f0692985f1 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
web/node_modules/
|
||||
@@ -1,2 +0,0 @@
|
||||
HTTP_PORT=:7000
|
||||
DB_DATABASE_SOURCE=root:123@tcp(localhost:3306)/casdoor
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.go linguist-detectable=true
|
||||
*.js linguist-detectable=false
|
||||
12
.github/semantic.yml
vendored
Normal file
12
.github/semantic.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# Always validate the PR title AND all the commits
|
||||
titleAndCommits: true
|
||||
# Require at least one commit to be valid
|
||||
# this is only relevant when using commitsOnly: true or titleAndCommits: true,
|
||||
# which validate all commits by default
|
||||
anyCommit: true
|
||||
# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
|
||||
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
|
||||
allowMergeCommits: false
|
||||
# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
|
||||
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
|
||||
allowRevertCommits: false
|
||||
102
.github/workflows/build.yml
vendored
Normal file
102
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
frontend:
|
||||
name: Front-end
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.17.0'
|
||||
- run: yarn install && CI=false yarn run build
|
||||
working-directory: ./web
|
||||
|
||||
|
||||
backend:
|
||||
name: Back-end
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16.5'
|
||||
- run: go version
|
||||
- name: Build
|
||||
run: |
|
||||
go build -race -ldflags "-extldflags '-static'"
|
||||
working-directory: ./
|
||||
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Fetch Previous version
|
||||
id: get-previous-tag
|
||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
||||
|
||||
- name: Release
|
||||
run: yarn global add semantic-release@17.4.4 && semantic-release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
|
||||
|
||||
- name: Fetch Current version
|
||||
id: get-current-tag
|
||||
uses: actions-ecosystem/action-get-latest-tag@v1
|
||||
|
||||
- name: Decide Should_Push Or Not
|
||||
id: should_push
|
||||
run: |
|
||||
old_version=${{steps.get-previous-tag.outputs.tag}}
|
||||
new_version=${{steps.get-current-tag.outputs.tag }}
|
||||
|
||||
old_array=(${old_version//\./ })
|
||||
new_array=(${new_version//\./ })
|
||||
|
||||
if [ ${old_array[0]} != ${new_array[0]} ]
|
||||
then
|
||||
echo ::set-output name=push::'true'
|
||||
elif [ ${old_array[1]} != ${new_array[1]} ]
|
||||
then
|
||||
echo ::set-output name=push::'true'
|
||||
|
||||
else
|
||||
echo ::set-output name=push::'false'
|
||||
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
push: true
|
||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v2
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
target: ALLINONE
|
||||
push: true
|
||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||
35
.github/workflows/sync.yml
vendored
Normal file
35
.github/workflows/sync.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Crowdin Action
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@1.2.0
|
||||
with:
|
||||
upload_translations: true
|
||||
|
||||
download_translations: true
|
||||
push_translations: true
|
||||
commit_message: 'refactor: New Crowdin translations by Github Action'
|
||||
|
||||
localization_branch_name: l10n_crowdin_action
|
||||
create_pull_request: true
|
||||
pull_request_title: 'refactor: New Crowdin translations'
|
||||
|
||||
crowdin_branch_name: l10n_branch
|
||||
config: './web/crowdin.yml'
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: '463556'
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.swp
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
@@ -16,12 +17,12 @@
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
.vscode/
|
||||
|
||||
tmp/
|
||||
tmpFiles/
|
||||
*.tmp
|
||||
logs/
|
||||
files/
|
||||
lastupdate.tmp
|
||||
commentsRouter*.go
|
||||
|
||||
.env
|
||||
|
||||
23
.releaserc.json
Normal file
23
.releaserc.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"debug": true,
|
||||
"branches": [
|
||||
"+([0-9])?(.{+([0-9]),x}).x",
|
||||
"master",
|
||||
{
|
||||
"name": "rc"
|
||||
},
|
||||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM golang:1.17.5 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
|
||||
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
|
||||
|
||||
FROM node:16.13.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn config set registry https://registry.npm.taobao.org
|
||||
RUN yarn install && yarn run build
|
||||
|
||||
|
||||
FROM debian:latest AS ALLINONE
|
||||
RUN apt update && apt install -y mariadb-server mariadb-client&&mkdir -p web/build && chmod 777 /tmp
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD chmod 777 /tmp && service mariadb start&&\
|
||||
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
|
||||
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
|
||||
./wait-for-it localhost:3306 -- ./server --createDatabase=true
|
||||
|
||||
|
||||
FROM alpine:latest
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add curl
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
|
||||
COPY --from=BACK /go/src/casdoor/ ./
|
||||
COPY --from=BACK /usr/bin/wait-for-it ./
|
||||
RUN mkdir -p web/build && apk add --no-cache bash coreutils
|
||||
COPY --from=FRONT /web/build /web/build
|
||||
CMD ./server
|
||||
|
||||
222
README.md
222
README.md
@@ -1,36 +1,186 @@
|
||||
# Casdoor
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Go (1.15 or later)
|
||||
MySQL (5.8 or higher)
|
||||
Node.js (version 8 or higher)
|
||||
Yarn
|
||||
|
||||
### Initialize project
|
||||
|
||||
Create a database named casdoor.
|
||||
|
||||
```sql
|
||||
CREATE DATABASE IF NOT EXISTS casdoor default charset utf8 COLLATE utf8_general_ci
|
||||
```
|
||||
|
||||
Configure database source in the `.env` file. If you have not copied the `.env.template` file to a new file named `.env`, which should now be performed.
|
||||
|
||||
### Run project
|
||||
|
||||
#### backend
|
||||
|
||||
```shell
|
||||
go run cmd/main.go
|
||||
```
|
||||
|
||||
### frontend
|
||||
|
||||
```shell
|
||||
cd web
|
||||
yarn
|
||||
yarn start
|
||||
```
|
||||
<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>
|
||||
<p align="center">
|
||||
<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">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
|
||||
</a>
|
||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
|
||||
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
|
||||
</a>
|
||||
<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">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/issues">
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/casdoor/casdoor/network">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
|
||||
</a>
|
||||
<a href="https://crowdin.com/project/casdoor-site">
|
||||
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
|
||||
</a>
|
||||
<a href="https://gitter.im/casbin/casdoor">
|
||||
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
|
||||
Deployed site: https://door.casbin.com/
|
||||
|
||||
## Quick Start
|
||||
Run your own casdoor program in a few minutes.
|
||||
|
||||
### Download
|
||||
|
||||
There are two methods, get code via go subcommand `get`:
|
||||
|
||||
```shell
|
||||
go get github.com/casdoor/casdoor
|
||||
```
|
||||
|
||||
or `git`:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/casdoor/casdoor
|
||||
```
|
||||
|
||||
Finally, change directory:
|
||||
|
||||
```bash
|
||||
cd casdoor/
|
||||
```
|
||||
|
||||
We provide two start up methods for all kinds of users.
|
||||
|
||||
### Manual
|
||||
|
||||
#### Simple configuration
|
||||
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
|
||||
|
||||
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
|
||||
|
||||
```bash
|
||||
username:password@tcp(database_ip:database_port)/
|
||||
```
|
||||
|
||||
#### Run
|
||||
|
||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
||||
|
||||
##### Dev Mode
|
||||
|
||||
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
||||
|
||||
```bash
|
||||
cd web/ && yarn && yarn run start
|
||||
```
|
||||
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
|
||||
|
||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
||||
|
||||
##### Production Mode
|
||||
|
||||
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
|
||||
|
||||
```bash
|
||||
cd web/ && yarn && yarn run build
|
||||
```
|
||||
|
||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
|
||||
```bash
|
||||
go build main.go && sudo ./main
|
||||
```
|
||||
|
||||
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
|
||||
|
||||
### Docker
|
||||
|
||||
Casdoor provide 2 kinds of image:
|
||||
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
|
||||
|
||||
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
|
||||
|
||||
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
|
||||
|
||||
### Start casdoor with casbin/casdoor-all-in-one
|
||||
if the image is not pulled, pull it from dockerhub
|
||||
```shell
|
||||
docker pull casbin/casdoor-all-in-one
|
||||
```
|
||||
Start it with
|
||||
```shell
|
||||
docker run -p 8000:8000 casbin/casdoor-all-in-one
|
||||
```
|
||||
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
|
||||
|
||||
### Start casdoor with casbin/casdoor
|
||||
#### modify the configurations
|
||||
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
|
||||
|
||||
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
|
||||
|
||||
```bash
|
||||
dataSourceName = root:123456@tcp(db:3306)/
|
||||
```
|
||||
|
||||
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
|
||||
|
||||
#### Run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
That's it! Try to visit http://localhost:8000/. :small_airplane:
|
||||
|
||||
## Detailed documentation
|
||||
|
||||
We also provide a complete [document](https://casdoor.org/) as a reference.
|
||||
|
||||
## Other examples
|
||||
|
||||
These all use casdoor as a centralized authentication platform.
|
||||
|
||||
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
|
||||
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
|
||||
- ......
|
||||
|
||||
## Contribute
|
||||
|
||||
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
|
||||
|
||||
### I18n notice
|
||||
|
||||
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
|
||||
|
||||
## License
|
||||
|
||||
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
|
||||
|
||||
|
||||
130
authz/authz.go
Normal file
130
authz/authz.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2021 The casbin 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 authz
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||
)
|
||||
|
||||
var Enforcer *casbin.Enforcer
|
||||
|
||||
func InitAuthz() {
|
||||
var err error
|
||||
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "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.urlPath == "/api/update-user" && 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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Enforcer.ClearPolicy()
|
||||
|
||||
//if len(Enforcer.GetPolicy()) == 0 {
|
||||
if true {
|
||||
ruleText := `
|
||||
p, built-in, *, *, *, *, *
|
||||
p, app, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, POST, /api/upload-avatar, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
p, *, *, GET, /api/get-human-check, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
p, *, *, *, /api/certs, *, *
|
||||
p, *, *, GET, /api/get-saml-login, *, *
|
||||
p, *, *, POST, /api/acs, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
// load all rules from string adapter to enforcer's memory
|
||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// save all rules from enforcer's memory to Xorm adapter (DB)
|
||||
// same as:
|
||||
// a.SavePolicy(Enforcer.GetModel())
|
||||
err = Enforcer.SavePolicy()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
68
cmd/main.go
68
cmd/main.go
@@ -1,68 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/config"
|
||||
"github.com/casdoor/casdoor/internal/handler"
|
||||
"github.com/casdoor/casdoor/internal/store"
|
||||
"github.com/casdoor/casdoor/internal/store/shared"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := config.NewConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
db, err := shared.NewDB(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
userStore := store.NewUserStore(db)
|
||||
applicationStore := store.NewApplicationStore(db)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.HTTPPort,
|
||||
Handler: handler.New(userStore, applicationStore),
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("listen and serve on %s", srv.Addr)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
<-quit
|
||||
log.Println("Shutdown Server ...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Server Shutdown:", err)
|
||||
}
|
||||
log.Println("Server exiting")
|
||||
}
|
||||
19
conf/app.conf
Normal file
19
conf/app.conf
Normal file
@@ -0,0 +1,19 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
SessionOn = true
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
logPostOnly = true
|
||||
origin =
|
||||
33
conf/conf.go
Normal file
33
conf/conf.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
)
|
||||
|
||||
func GetBeegoConfDataSourceName() string {
|
||||
dataSourceName := beego.AppConfig.String("dataSourceName")
|
||||
|
||||
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
|
||||
if runningInDocker == "true" {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
|
||||
}
|
||||
|
||||
return dataSourceName
|
||||
}
|
||||
305
controllers/account.go
Normal file
305
controllers/account.go
Normal file
@@ -0,0 +1,305 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
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"`
|
||||
PhonePrefix string `json:"phonePrefix"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
|
||||
RelayState string `json:"relayState"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Data interface{} `json:"data"`
|
||||
Data2 interface{} `json:"data2"`
|
||||
}
|
||||
|
||||
type Userinfo struct {
|
||||
Sub string `json:"sub"`
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Name string `json:"name,omitempty"`
|
||||
DisplayName string `json:"preferred_username,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Avatar string `json:"picture,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Phone string `json:"phone,omitempty"`
|
||||
}
|
||||
|
||||
type HumanCheck struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage interface{} `json:"captchaImage"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
// @Tag Login API
|
||||
// @Title Signup
|
||||
// @Description sign up a new user
|
||||
// @Param username formData string true "The username to sign up"
|
||||
// @Param password formData string true "The password"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /signup [post]
|
||||
func (c *ApiController) Signup() {
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseError("Please sign out first before signing up", c.GetSessionUsername())
|
||||
return
|
||||
}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError("The application does not allow to sign up new account")
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.Email, form.Phone, form.Affiliation)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") && form.Email != "" {
|
||||
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var checkPhone string
|
||||
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
|
||||
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
|
||||
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Phone: %s", checkResult))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
lastIdInt := util.ParseInt(lastUser.Id)
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
}
|
||||
|
||||
username := form.Username
|
||||
if !application.IsSignupItemVisible("Username") {
|
||||
username = id
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: form.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: "normal-user",
|
||||
Password: form.Password,
|
||||
DisplayName: form.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IdCard: form.IdCard,
|
||||
Region: form.Region,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
affected := object.AddUser(user)
|
||||
if !affected {
|
||||
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
|
||||
return
|
||||
}
|
||||
|
||||
object.AddUserToOriginalDatabase(user)
|
||||
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||
|
||||
c.ResponseOk(userId)
|
||||
}
|
||||
|
||||
// Logout
|
||||
// @Title Logout
|
||||
// @Tag Login API
|
||||
// @Description logout the current user
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /logout [post]
|
||||
func (c *ApiController) Logout() {
|
||||
user := c.GetSessionUsername()
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
|
||||
c.ResponseOk(user)
|
||||
}
|
||||
|
||||
// GetAccount
|
||||
// @Title GetAccount
|
||||
// @Tag Account API
|
||||
// @Description get the details of the current account
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-account [get]
|
||||
func (c *ApiController) GetAccount() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
|
||||
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
|
||||
resp := Response{
|
||||
Status: "ok",
|
||||
Sub: user.Id,
|
||||
Name: user.Name,
|
||||
Data: user,
|
||||
Data2: organization,
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UserInfo
|
||||
// @Title UserInfo
|
||||
// @Tag Account API
|
||||
// @Description return user information according to OIDC standards
|
||||
// @Success 200 {object} controllers.Userinfo The Response object
|
||||
// @router /userinfo [get]
|
||||
func (c *ApiController) GetUserinfo() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
scope, aud := c.GetSessionOidc()
|
||||
iss := beego.AppConfig.String("origin")
|
||||
resp := Userinfo{
|
||||
Sub: user.Id,
|
||||
Iss: iss,
|
||||
Aud: aud,
|
||||
}
|
||||
if strings.Contains(scope, "profile") {
|
||||
resp.Name = user.Name
|
||||
resp.DisplayName = user.DisplayName
|
||||
resp.Avatar = user.Avatar
|
||||
}
|
||||
if strings.Contains(scope, "email") {
|
||||
resp.Email = user.Email
|
||||
}
|
||||
if strings.Contains(scope, "address") {
|
||||
resp.Address = user.Location
|
||||
}
|
||||
if strings.Contains(scope, "phone") {
|
||||
resp.Phone = user.Phone
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetHumanCheck ...
|
||||
// @Tag Login API
|
||||
// @Title GetHumancheck
|
||||
// @router /api/get-human-check [get]
|
||||
func (c *ApiController) GetHumanCheck() {
|
||||
c.Data["json"] = HumanCheck{Type: "none"}
|
||||
|
||||
provider := object.GetDefaultHumanCheckProvider()
|
||||
if provider == nil {
|
||||
id, img := object.GetCaptcha()
|
||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.ServeJSON()
|
||||
}
|
||||
151
controllers/application.go
Normal file
151
controllers/application.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetApplications
|
||||
// @Title GetApplications
|
||||
// @Tag Application API
|
||||
// @Description get all applications
|
||||
// @Param owner query string true "The owner of applications."
|
||||
// @Success 200 {array} object.Application The Response object
|
||||
// @router /get-applications [get]
|
||||
func (c *ApiController) GetApplications() {
|
||||
userId := c.GetSessionUsername()
|
||||
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")
|
||||
organization := c.Input().Get("organization")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
var applications []*object.Application
|
||||
if organization == "" {
|
||||
applications = object.GetApplications(owner)
|
||||
} else {
|
||||
applications = object.GetApplicationsByOrganizationName(owner, organization)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplications(applications, userId)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
|
||||
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
|
||||
c.ResponseOk(applications, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetApplication
|
||||
// @Title GetApplication
|
||||
// @Tag Application API
|
||||
// @Description get the detail of an application
|
||||
// @Param id query string true "The id of the application."
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-application [get]
|
||||
func (c *ApiController) GetApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserApplication
|
||||
// @Title GetUserApplication
|
||||
// @Tag Application API
|
||||
// @Description get the detail of the user's application
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Success 200 {object} object.Application The Response object
|
||||
// @router /get-user-application [get]
|
||||
func (c *ApiController) GetUserApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateApplication
|
||||
// @Title UpdateApplication
|
||||
// @Tag Application API
|
||||
// @Description update an application
|
||||
// @Param id query string true "The id of the application"
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-application [post]
|
||||
func (c *ApiController) UpdateApplication() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddApplication
|
||||
// @Title AddApplication
|
||||
// @Tag Application API
|
||||
// @Description add an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-application [post]
|
||||
func (c *ApiController) AddApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteApplication
|
||||
// @Title DeleteApplication
|
||||
// @Tag Application API
|
||||
// @Description delete an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-application [post]
|
||||
func (c *ApiController) DeleteApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
403
controllers/auth.go
Normal file
403
controllers/auth.go
Normal file
@@ -0,0 +1,403 @@
|
||||
// Copyright 2021 The casbin 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/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func codeToResponse(code *object.Code) *Response {
|
||||
if code.Code == "" {
|
||||
return &Response{Status: "error", Msg: code.Message, Data: code.Code}
|
||||
}
|
||||
|
||||
return &Response{Status: "ok", Msg: "", Data: code.Code}
|
||||
}
|
||||
|
||||
// HandleLoggedIn ...
|
||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
||||
userId := user.GetId()
|
||||
if form.Type == ResponseTypeLogin {
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
resp = &Response{Status: "ok", Msg: "", Data: userId}
|
||||
} else if form.Type == ResponseTypeCode {
|
||||
clientId := c.Input().Get("clientId")
|
||||
responseType := c.Input().Get("responseType")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
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("Challenge method should be S256")
|
||||
return
|
||||
}
|
||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
||||
resp = codeToResponse(code)
|
||||
|
||||
if application.EnableSigninSession || application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
} else {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
||||
}
|
||||
|
||||
// if user did not check auto signin
|
||||
if resp.Status == "ok" && !form.AutoSignin {
|
||||
timestamp := time.Now().Unix()
|
||||
timestamp += 3600 * 24
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// GetApplicationLogin ...
|
||||
// @Title GetApplicationLogin
|
||||
// @Tag Login API
|
||||
// @Description get application login
|
||||
// @Param clientId query string true "client id"
|
||||
// @Param responseType query string true "response type"
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @router /update-application [get]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
responseType := c.Input().Get("responseType")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
|
||||
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg, application)
|
||||
} else {
|
||||
c.ResponseOk(application)
|
||||
}
|
||||
}
|
||||
|
||||
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
|
||||
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
||||
} else {
|
||||
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
||||
}
|
||||
}
|
||||
|
||||
// Login ...
|
||||
// @Title Login
|
||||
// @Tag Login API
|
||||
// @Description login
|
||||
// @Param oAuthParams query string true "oAuth parameters"
|
||||
// @Param body body RequestForm true "Login information"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @router /login [post]
|
||||
func (c *ApiController) Login() {
|
||||
resp := &Response{}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if form.Username != "" {
|
||||
if form.Type == ResponseTypeLogin {
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseError("Please sign out first before signing in", c.GetSessionUsername())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
var verificationCodeType string
|
||||
var checkResult string
|
||||
|
||||
// check result through Email or Phone
|
||||
if strings.Contains(form.Username, "@") {
|
||||
verificationCodeType = "email"
|
||||
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
||||
} else {
|
||||
verificationCodeType = "phone"
|
||||
if len(form.PhonePrefix) == 0 {
|
||||
responseText := fmt.Sprintf("%s%s", verificationCodeType, "No phone prefix")
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
||||
}
|
||||
if len(checkResult) != 0 {
|
||||
responseText := fmt.Sprintf("%s%s", verificationCodeType, checkResult)
|
||||
c.ResponseError(responseText)
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
object.DisableVerificationCode(form.Username)
|
||||
|
||||
user = object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
password := form.Password
|
||||
user, msg = object.CheckUserPassword(form.Organization, form.Username, password)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
}
|
||||
} else if form.Provider != "" {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
||||
providerItem := application.GetProviderItem(provider.Name)
|
||||
if !providerItem.IsProviderVisible() {
|
||||
c.ResponseError(fmt.Sprintf("The provider: %s is not enabled for the application", provider.Name))
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "SAML" {
|
||||
// SAML
|
||||
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if provider.Category == "OAuth" {
|
||||
// OAuth
|
||||
|
||||
clientId := provider.ClientId
|
||||
clientSecret := provider.ClientSecret
|
||||
if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") {
|
||||
clientId = provider.ClientId2
|
||||
clientSecret = provider.ClientSecret2
|
||||
}
|
||||
|
||||
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, form.RedirectUri)
|
||||
if idProvider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||
return
|
||||
}
|
||||
|
||||
setHttpClient(idProvider, provider.Type)
|
||||
|
||||
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
|
||||
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
|
||||
return
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(form.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !token.Valid() {
|
||||
c.ResponseError("Invalid token")
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err = idProvider.GetUserInfo(token)
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("Failed to login in: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if form.Method == "signup" {
|
||||
user := &object.User{}
|
||||
if provider.Category == "SAML" {
|
||||
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
|
||||
} else if provider.Category == "OAuth" {
|
||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if user == nil {
|
||||
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
||||
}
|
||||
if user == nil {
|
||||
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
|
||||
}
|
||||
}
|
||||
|
||||
if user != nil && user.IsDeleted == false {
|
||||
// Sign in via OAuth (want to sign up but already have account)
|
||||
|
||||
if user.IsForbidden {
|
||||
c.ResponseError("the user is forbidden to sign in, please contact the administrator")
|
||||
}
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
} else if provider.Category == "OAuth" {
|
||||
// Sign up via OAuth
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError(fmt.Sprintf("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", provider.Type, userInfo.Username, userInfo.DisplayName))
|
||||
return
|
||||
}
|
||||
|
||||
if !providerItem.CanSignUp {
|
||||
c.ResponseError(fmt.Sprintf("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", provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
|
||||
return
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||
user = &object.User{
|
||||
Owner: application.Organization,
|
||||
Name: userInfo.Username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
DisplayName: userInfo.DisplayName,
|
||||
Avatar: userInfo.AvatarUrl,
|
||||
Address: []string{},
|
||||
Email: userInfo.Email,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: properties,
|
||||
}
|
||||
// sync info from 3rd-party if possible
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
|
||||
affected := object.AddUser(user)
|
||||
if !affected {
|
||||
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
|
||||
return
|
||||
}
|
||||
|
||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.User = user.Name
|
||||
go object.AddRecord(record)
|
||||
} else if provider.Category == "SAML" {
|
||||
resp = &Response{Status: "error", Msg: "The account does not exist"}
|
||||
}
|
||||
//resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
} else { // form.Method != "signup"
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError("The account does not exist", userInfo)
|
||||
return
|
||||
}
|
||||
|
||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if oldUser == nil {
|
||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
||||
}
|
||||
if oldUser != nil {
|
||||
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
|
||||
return
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
|
||||
isLinked := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
if isLinked {
|
||||
resp = &Response{Status: "ok", Msg: "", Data: isLinked}
|
||||
} else {
|
||||
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
user := c.getCurrentUser()
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSamlLogin() {
|
||||
providerId := c.Input().Get("id")
|
||||
relayState := c.Input().Get("relayState")
|
||||
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
c.ResponseOk(authURL, method)
|
||||
}
|
||||
|
||||
func (c *ApiController) HandleSamlLogin() {
|
||||
relayState := c.Input().Get("RelayState")
|
||||
samlResponse := c.Input().Get("SAMLResponse")
|
||||
decode, err := base64.StdEncoding.DecodeString(relayState)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
slice := strings.Split(string(decode), "&")
|
||||
relayState = url.QueryEscape(relayState)
|
||||
samlResponse = url.QueryEscape(samlResponse)
|
||||
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
|
||||
slice[4], relayState, samlResponse)
|
||||
c.Redirect(targetUrl, 303)
|
||||
}
|
||||
134
controllers/base.go
Normal file
134
controllers/base.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// controller for handlers under /api uri
|
||||
type ApiController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// controller for handlers directly under / (root)
|
||||
type RootController struct {
|
||||
ApiController
|
||||
}
|
||||
|
||||
type SessionData struct {
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
func (c *ApiController) IsGlobalAdmin() bool {
|
||||
username := c.GetSessionUsername()
|
||||
if strings.HasPrefix(username, "app/") {
|
||||
// e.g., "app/app-casnode"
|
||||
return true
|
||||
}
|
||||
|
||||
user := object.GetUser(username)
|
||||
if user == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return user.Owner == "built-in" || user.IsGlobalAdmin
|
||||
}
|
||||
|
||||
// GetSessionUsername ...
|
||||
func (c *ApiController) GetSessionUsername() string {
|
||||
// check if user session expired
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
return ""
|
||||
}
|
||||
|
||||
user := c.GetSession("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
return "", ""
|
||||
}
|
||||
scopeValue := c.GetSession("scope")
|
||||
audValue := c.GetSession("aud")
|
||||
var scope, aud string
|
||||
var ok bool
|
||||
if scope, ok = scopeValue.(string); !ok {
|
||||
scope = ""
|
||||
}
|
||||
if aud, ok = audValue.(string); !ok {
|
||||
aud = ""
|
||||
}
|
||||
return scope, aud
|
||||
}
|
||||
|
||||
// SetSessionUsername ...
|
||||
func (c *ApiController) SetSessionUsername(user string) {
|
||||
c.SetSession("username", user)
|
||||
}
|
||||
|
||||
// GetSessionData ...
|
||||
func (c *ApiController) GetSessionData() *SessionData {
|
||||
session := c.GetSession("SessionData")
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionData := &SessionData{}
|
||||
err := util.JsonToStruct(session.(string), sessionData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionData
|
||||
}
|
||||
|
||||
// SetSessionData ...
|
||||
func (c *ApiController) SetSessionData(s *SessionData) {
|
||||
if s == nil {
|
||||
c.DelSession("SessionData")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSession("SessionData", util.StructToJson(s))
|
||||
}
|
||||
|
||||
func wrapActionResponse(affected bool) *Response {
|
||||
if affected {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||
} else {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||
}
|
||||
}
|
||||
116
controllers/cert.go
Normal file
116
controllers/cert.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetCerts
|
||||
// @Title GetCerts
|
||||
// @Tag Cert API
|
||||
// @Description get certs
|
||||
// @Param owner query string true "The owner of certs"
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-certs [get]
|
||||
func (c *ApiController) GetCerts() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetMaskedCerts(object.GetCerts(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
|
||||
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(certs, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetCert
|
||||
// @Tag Cert API
|
||||
// @Description get cert
|
||||
// @Param id query string true "The id of the cert"
|
||||
// @Success 200 {object} object.Cert The Response object
|
||||
// @router /get-cert [get]
|
||||
func (c *ApiController) GetCert() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateCert
|
||||
// @Tag Cert API
|
||||
// @Description update cert
|
||||
// @Param id query string true "The id of the cert"
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-cert [post]
|
||||
func (c *ApiController) UpdateCert() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddCert
|
||||
// @Tag Cert API
|
||||
// @Description add cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-cert [post]
|
||||
func (c *ApiController) AddCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteCert
|
||||
// @Tag Cert API
|
||||
// @Description delete cert
|
||||
// @Param body body object.Cert true "The details of the cert"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-cert [post]
|
||||
func (c *ApiController) DeleteCert() {
|
||||
var cert object.Cert
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))
|
||||
c.ServeJSON()
|
||||
}
|
||||
237
controllers/ldap.go
Normal file
237
controllers/ldap.go
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"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 {
|
||||
//Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapRespUser `json:"users"`
|
||||
}
|
||||
|
||||
//type LdapRespGroup struct {
|
||||
// GroupId string
|
||||
// GroupName string
|
||||
//}
|
||||
|
||||
type LdapSyncResp struct {
|
||||
Exist []object.LdapRespUser `json:"exist"`
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title GetLdapser
|
||||
// @router /get-ldap-user [post]
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
var resp LdapResp
|
||||
|
||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
// c.ResponseError(err.Error())
|
||||
// return
|
||||
//}
|
||||
|
||||
//for _, group := range groupsMap {
|
||||
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
||||
// GroupId: group.GidNumber,
|
||||
// GroupName: group.Cn,
|
||||
// })
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
//GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
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}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title GetLdaps
|
||||
// @router /get-ldaps [post]
|
||||
func (c *ApiController) GetLdaps() {
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title GetLdap
|
||||
// @router /get-ldap [post]
|
||||
func (c *ApiController) GetLdap() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if util.IsStrsEmpty(id) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title AddLdap
|
||||
// @router /add-ldap [post]
|
||||
func (c *ApiController) AddLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckLdapExist(&ldap) {
|
||||
c.ResponseError("Ldap server exist")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.AddLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
if ldap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title UpdateLdap
|
||||
// @router /update-ldap [post]
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
if ldap.AutoSync != 0 {
|
||||
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title DeleteLdap
|
||||
// @router /delete-ldap [post]
|
||||
func (c *ApiController) DeleteLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Account API
|
||||
// @Title SyncLdapUsers
|
||||
// @router /sync-ldap-users [post]
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
ldapId := c.Input().Get("ldapId")
|
||||
var users []object.LdapRespUser
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed := object.SyncLdapUsers(owner, users)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||
Exist: *exist,
|
||||
Failed: *failed,
|
||||
}}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: exist}
|
||||
c.ServeJSON()
|
||||
}
|
||||
55
controllers/link.go
Normal file
55
controllers/link.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
)
|
||||
|
||||
type LinkForm struct {
|
||||
ProviderType string `json:"providerType"`
|
||||
}
|
||||
|
||||
// Unlink ...
|
||||
// @router /unlink [post]
|
||||
// @Tag Login API
|
||||
func (c *ApiController) Unlink() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var form LinkForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
providerType := form.ProviderType
|
||||
|
||||
user := object.GetUser(userId)
|
||||
value := object.GetUserField(user, providerType)
|
||||
|
||||
if value == "" {
|
||||
c.ResponseError("Please link first", value)
|
||||
return
|
||||
}
|
||||
|
||||
object.ClearUserOAuthProperties(user, providerType)
|
||||
|
||||
object.LinkUserAccount(user, providerType, "")
|
||||
c.ResponseOk()
|
||||
}
|
||||
39
controllers/oidc_discovery.go
Normal file
39
controllers/oidc_discovery.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
|
||||
// @Title GetOidcDiscovery
|
||||
// @Tag OIDC API
|
||||
// @router /.well-known/openid-configuration [get]
|
||||
func (c *RootController) GetOidcDiscovery() {
|
||||
host := c.Ctx.Request.Host
|
||||
c.Data["json"] = object.GetOidcDiscovery(host)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetOidcCert
|
||||
// @Tag OIDC API
|
||||
// @router /api/certs [get]
|
||||
func (c *RootController) GetOidcCert() {
|
||||
jwks, err := object.GetJsonWebKeySet()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = jwks
|
||||
c.ServeJSON()
|
||||
}
|
||||
120
controllers/organization.go
Normal file
120
controllers/organization.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetOrganizations ...
|
||||
// @Title GetOrganizations
|
||||
// @Tag Organization API
|
||||
// @Description get organizations
|
||||
// @Param owner query string true "owner"
|
||||
// @Success 200 {array} object.Organization The Response object
|
||||
// @router /get-organizations [get]
|
||||
func (c *ApiController) GetOrganizations() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value)))
|
||||
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(organizations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrganization ...
|
||||
// @Title GetOrganization
|
||||
// @Tag Organization API
|
||||
// @Description get organization
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} object.Organization The Response object
|
||||
// @router /get-organization [get]
|
||||
func (c *ApiController) GetOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedOrganization(object.GetOrganization(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateOrganization ...
|
||||
// @Title UpdateOrganization
|
||||
// @Tag Organization API
|
||||
// @Description update organization
|
||||
// @Param id query string true "The id of the organization"
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-organization [post]
|
||||
func (c *ApiController) UpdateOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddOrganization ...
|
||||
// @Title AddOrganization
|
||||
// @Tag Organization API
|
||||
// @Description add organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-organization [post]
|
||||
func (c *ApiController) AddOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteOrganization ...
|
||||
// @Title DeleteOrganization
|
||||
// @Tag Organization API
|
||||
// @Description delete organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-organization [post]
|
||||
func (c *ApiController) DeleteOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
116
controllers/permission.go
Normal file
116
controllers/permission.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetPermissions
|
||||
// @Title GetPermissions
|
||||
// @Tag Permission API
|
||||
// @Description get permissions
|
||||
// @Param owner query string true "The owner of permissions"
|
||||
// @Success 200 {array} object.Permission The Response object
|
||||
// @router /get-permissions [get]
|
||||
func (c *ApiController) GetPermissions() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetPermissions(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPermissionCount(owner, field, value)))
|
||||
permissions := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(permissions, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetPermission
|
||||
// @Tag Permission API
|
||||
// @Description get permission
|
||||
// @Param id query string true "The id of the permission"
|
||||
// @Success 200 {object} object.Permission The Response object
|
||||
// @router /get-permission [get]
|
||||
func (c *ApiController) GetPermission() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetPermission(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdatePermission
|
||||
// @Tag Permission API
|
||||
// @Description update permission
|
||||
// @Param id query string true "The id of the permission"
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-permission [post]
|
||||
func (c *ApiController) UpdatePermission() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdatePermission(id, &permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddPermission
|
||||
// @Tag Permission API
|
||||
// @Description add permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-permission [post]
|
||||
func (c *ApiController) AddPermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddPermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeletePermission
|
||||
// @Tag Permission API
|
||||
// @Description delete permission
|
||||
// @Param body body object.Permission true "The details of the permission"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-permission [post]
|
||||
func (c *ApiController) DeletePermission() {
|
||||
var permission object.Permission
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePermission(&permission))
|
||||
c.ServeJSON()
|
||||
}
|
||||
115
controllers/provider.go
Normal file
115
controllers/provider.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetProviders
|
||||
// @Title GetProviders
|
||||
// @Tag Provider API
|
||||
// @Description get providers
|
||||
// @Param owner query string true "The owner of providers"
|
||||
// @Success 200 {array} object.Provider The Response object
|
||||
// @router /get-providers [get]
|
||||
func (c *ApiController) GetProviders() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetProvider
|
||||
// @Tag Provider API
|
||||
// @Description get provider
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Success 200 {object} object.Provider The Response object
|
||||
// @router /get-provider [get]
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateProvider
|
||||
// @Tag Provider API
|
||||
// @Description update provider
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-provider [post]
|
||||
func (c *ApiController) UpdateProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddProvider
|
||||
// @Tag Provider API
|
||||
// @Description add provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-provider [post]
|
||||
func (c *ApiController) AddProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteProvider
|
||||
// @Tag Provider API
|
||||
// @Description delete provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-provider [post]
|
||||
func (c *ApiController) DeleteProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
67
controllers/record.go
Normal file
67
controllers/record.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetRecords
|
||||
// @Title GetRecords
|
||||
// @Tag Record API
|
||||
// @Description get all records
|
||||
// @Param pageSize query string true "The size of each page"
|
||||
// @Param p query string true "The number of the page"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records [get]
|
||||
func (c *ApiController) GetRecords() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetRecords()
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value)))
|
||||
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(records, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecordsByFilter
|
||||
// @Tag Record API
|
||||
// @Title GetRecordsByFilter
|
||||
// @Description get records by filter
|
||||
// @Param body body object.Records true "filter Record message"
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records-filter [post]
|
||||
func (c *ApiController) GetRecordsByFilter() {
|
||||
body := string(c.Ctx.Input.RequestBody)
|
||||
|
||||
record := &object.Record{}
|
||||
err := util.JsonToStruct(body, record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetRecordsByField(record)
|
||||
c.ServeJSON()
|
||||
}
|
||||
214
controllers/resource.go
Normal file
214
controllers/resource.go
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// @router /get-resources [get]
|
||||
// @Tag Resource API
|
||||
// @Title GetResources
|
||||
func (c *ApiController) GetResources() {
|
||||
owner := c.Input().Get("owner")
|
||||
user := c.Input().Get("user")
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetResources(owner, user)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value)))
|
||||
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(resources, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Tag Resource API
|
||||
// @Title GetResource
|
||||
// @router /get-resource [get]
|
||||
func (c *ApiController) GetResource() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetResource(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Resource API
|
||||
// @Title UpdateResource
|
||||
// @router /update-resource [post]
|
||||
func (c *ApiController) UpdateResource() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Resource API
|
||||
// @Title AddResource
|
||||
// @router /add-resource [post]
|
||||
func (c *ApiController) AddResource() {
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Resource API
|
||||
// @Title DeleteResource
|
||||
// @router /delete-resource [post]
|
||||
func (c *ApiController) DeleteResource() {
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = object.DeleteFile(provider, resource.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteResource(&resource))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Tag Resource API
|
||||
// @Title UploadResource
|
||||
// @router /upload-resource [post]
|
||||
func (c *ApiController) UploadResource() {
|
||||
owner := c.Input().Get("owner")
|
||||
username := c.Input().Get("user")
|
||||
application := c.Input().Get("application")
|
||||
tag := c.Input().Get("tag")
|
||||
parent := c.Input().Get("parent")
|
||||
fullFilePath := c.Input().Get("fullFilePath")
|
||||
createdTime := c.Input().Get("createdTime")
|
||||
description := c.Input().Get("description")
|
||||
|
||||
file, header, err := c.GetFile("file")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if username == "" || fullFilePath == "" {
|
||||
c.ResponseError(fmt.Sprintf("username or fullFilePath is empty: username = %s, fullFilePath = %s", username, fullFilePath))
|
||||
return
|
||||
}
|
||||
|
||||
filename := filepath.Base(fullFilePath)
|
||||
fileBuffer := bytes.NewBuffer(nil)
|
||||
if _, err = io.Copy(fileBuffer, file); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
provider, user, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fileType := "unknown"
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
fileType, _ = util.GetOwnerAndNameFromId(contentType)
|
||||
|
||||
if fileType != "image" && fileType != "video" {
|
||||
ext := filepath.Ext(filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
|
||||
}
|
||||
|
||||
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if createdTime == "" {
|
||||
createdTime = util.GetCurrentTime()
|
||||
}
|
||||
fileFormat := filepath.Ext(fullFilePath)
|
||||
fileSize := int(header.Size)
|
||||
resource := &object.Resource{
|
||||
Owner: owner,
|
||||
Name: objectKey,
|
||||
CreatedTime: createdTime,
|
||||
User: username,
|
||||
Provider: provider.Name,
|
||||
Application: application,
|
||||
Tag: tag,
|
||||
Parent: parent,
|
||||
FileName: filename,
|
||||
FileType: fileType,
|
||||
FileFormat: fileFormat,
|
||||
FileSize: fileSize,
|
||||
Url: fileUrl,
|
||||
Description: description,
|
||||
}
|
||||
object.AddOrUpdateResource(resource)
|
||||
|
||||
switch tag {
|
||||
case "avatar":
|
||||
if user == nil {
|
||||
user = object.GetUserNoCheck(username)
|
||||
if user == nil {
|
||||
c.ResponseError("user is nil for tag: \"avatar\"")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user.Avatar = fileUrl
|
||||
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
|
||||
case "termsOfUse":
|
||||
applicationId := fmt.Sprintf("admin/%s", parent)
|
||||
app := object.GetApplication(applicationId)
|
||||
app.TermsOfUse = fileUrl
|
||||
object.UpdateApplication(applicationId, app)
|
||||
}
|
||||
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
}
|
||||
116
controllers/role.go
Normal file
116
controllers/role.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetRoles
|
||||
// @Title GetRoles
|
||||
// @Tag Role API
|
||||
// @Description get roles
|
||||
// @Param owner query string true "The owner of roles"
|
||||
// @Success 200 {array} object.Role The Response object
|
||||
// @router /get-roles [get]
|
||||
func (c *ApiController) GetRoles() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetRoles(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
|
||||
roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(roles, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetRole
|
||||
// @Tag Role API
|
||||
// @Description get role
|
||||
// @Param id query string true "The id of the role"
|
||||
// @Success 200 {object} object.Role The Response object
|
||||
// @router /get-role [get]
|
||||
func (c *ApiController) GetRole() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetRole(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateRole
|
||||
// @Tag Role API
|
||||
// @Description update role
|
||||
// @Param id query string true "The id of the role"
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-role [post]
|
||||
func (c *ApiController) UpdateRole() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddRole
|
||||
// @Tag Role API
|
||||
// @Description add role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-role [post]
|
||||
func (c *ApiController) AddRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteRole
|
||||
// @Tag Role API
|
||||
// @Description delete role
|
||||
// @Param body body object.Role true "The details of the role"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-role [post]
|
||||
func (c *ApiController) DeleteRole() {
|
||||
var role object.Role
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))
|
||||
c.ServeJSON()
|
||||
}
|
||||
131
controllers/service.go
Normal file
131
controllers/service.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// SendEmail
|
||||
// @Title SendEmail
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body emailForm true "Details of the email request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
provider, _, ok := c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var emailForm struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Sender string `json:"sender"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||
return
|
||||
}
|
||||
|
||||
invalidReceivers := []string{}
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
if !util.IsEmailValid(receiver) {
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// SendSms
|
||||
// @Title SendSms
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body smsForm true "Details of the sms request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, _, ok := c.GetProviderFromContext("SMS")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm struct {
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
|
||||
}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
org := object.GetOrganization(smsForm.OrgId)
|
||||
var invalidReceivers []string
|
||||
for idx, receiver := range smsForm.Receivers {
|
||||
if !util.IsPhoneCnValid(receiver) {
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
} else {
|
||||
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Invalid phone receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
116
controllers/syncer.go
Normal file
116
controllers/syncer.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetSyncers
|
||||
// @Title GetSyncers
|
||||
// @Tag Syncer API
|
||||
// @Description get syncers
|
||||
// @Param owner query string true "The owner of syncers"
|
||||
// @Success 200 {array} object.Syncer The Response object
|
||||
// @router /get-syncers [get]
|
||||
func (c *ApiController) GetSyncers() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetSyncers(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
|
||||
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(syncers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description get syncer
|
||||
// @Param id query string true "The id of the syncer"
|
||||
// @Success 200 {object} object.Syncer The Response object
|
||||
// @router /get-syncer [get]
|
||||
func (c *ApiController) GetSyncer() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetSyncer(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description update syncer
|
||||
// @Param id query string true "The id of the syncer"
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-syncer [post]
|
||||
func (c *ApiController) UpdateSyncer() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateSyncer(id, &syncer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description add syncer
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-syncer [post]
|
||||
func (c *ApiController) AddSyncer() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddSyncer(&syncer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteSyncer
|
||||
// @Tag Syncer API
|
||||
// @Description delete syncer
|
||||
// @Param body body object.Syncer true "The details of the syncer"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-syncer [post]
|
||||
func (c *ApiController) DeleteSyncer() {
|
||||
var syncer object.Syncer
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
|
||||
c.ServeJSON()
|
||||
}
|
||||
201
controllers/token.go
Normal file
201
controllers/token.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetTokens
|
||||
// @Title GetTokens
|
||||
// @Tag Token API
|
||||
// @Description get tokens
|
||||
// @Param owner query string true "The owner of tokens"
|
||||
// @Param pageSize query string true "The size of each page"
|
||||
// @Param p query string true "The number of the page"
|
||||
// @Success 200 {array} object.Token The Response object
|
||||
// @router /get-tokens [get]
|
||||
func (c *ApiController) GetTokens() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetTokens(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
|
||||
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(tokens, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetToken
|
||||
// @Title GetToken
|
||||
// @Tag Token API
|
||||
// @Description get token
|
||||
// @Param id query string true "The id of token"
|
||||
// @Success 200 {object} object.Token The Response object
|
||||
// @router /get-token [get]
|
||||
func (c *ApiController) GetToken() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetToken(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateToken
|
||||
// @Title UpdateToken
|
||||
// @Tag Token API
|
||||
// @Description update token
|
||||
// @Param id query string true "The id of token"
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-token [post]
|
||||
func (c *ApiController) UpdateToken() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddToken
|
||||
// @Title AddToken
|
||||
// @Tag Token API
|
||||
// @Description add token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-token [post]
|
||||
func (c *ApiController) AddToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddToken(&token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteToken
|
||||
// @Tag Token API
|
||||
// @Title DeleteToken
|
||||
// @Description delete token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-token [post]
|
||||
func (c *ApiController) DeleteToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteToken(&token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthCode
|
||||
// @Title GetOAuthCode
|
||||
// @Tag Token API
|
||||
// @Description get OAuth code
|
||||
// @Param user_id query string true "The id 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("Challenge method should be S256")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetOAuthToken
|
||||
// @Title GetOAuthToken
|
||||
// @Tag Token API
|
||||
// @Description get OAuth access token
|
||||
// @Param grant_type query string true "OAuth grant type"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string true "OAuth client secret"
|
||||
// @Param code query string true "OAuth code"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @router /login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
code := c.Input().Get("code")
|
||||
verifier := c.Input().Get("code_verifier")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RefreshToken
|
||||
// @Title RefreshToken
|
||||
// @Description refresh OAuth access token
|
||||
// @Param grant_type query string true "OAuth grant type"
|
||||
// @Param refresh_token query string true "OAuth refresh token"
|
||||
// @Param scope query string true "OAuth scope"
|
||||
// @Param client_id query string true "OAuth client id"
|
||||
// @Param client_secret query string true "OAuth client secret"
|
||||
// @Success 200 {object} object.TokenWrapper The Response object
|
||||
// @router /login/oauth/refresh_token [post]
|
||||
func (c *ApiController) RefreshToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
scope := c.Input().Get("scope")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
|
||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret)
|
||||
c.ServeJSON()
|
||||
}
|
||||
341
controllers/user.go
Normal file
341
controllers/user.go
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetGlobalUsers
|
||||
// @Title GetGlobalUsers
|
||||
// @Tag User API
|
||||
// @Description get global users
|
||||
// @Success 200 {array} object.User The Response object
|
||||
// @router /get-global-users [get]
|
||||
func (c *ApiController) GetGlobalUsers() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetUsers
|
||||
// @Title GetUsers
|
||||
// @Tag User API
|
||||
// @Description
|
||||
// @Param owner query string true "The owner of users"
|
||||
// @Success 200 {array} object.User The Response object
|
||||
// @router /get-users [get]
|
||||
func (c *ApiController) GetUsers() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(users, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetUser
|
||||
// @Title GetUser
|
||||
// @Tag User API
|
||||
// @Description get user
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Success 200 {object} object.User The Response object
|
||||
// @router /get-user [get]
|
||||
func (c *ApiController) GetUser() {
|
||||
id := c.Input().Get("id")
|
||||
owner := c.Input().Get("owner")
|
||||
email := c.Input().Get("email")
|
||||
|
||||
var user *object.User
|
||||
if email == "" {
|
||||
user = object.GetUser(id)
|
||||
} else {
|
||||
user = object.GetUserByEmail(owner, email)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetMaskedUser(user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateUser
|
||||
// @Title UpdateUser
|
||||
// @Tag User API
|
||||
// @Description update user
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-user [post]
|
||||
func (c *ApiController) UpdateUser() {
|
||||
id := c.Input().Get("id")
|
||||
columnsStr := c.Input().Get("columns")
|
||||
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if user.DisplayName == "" {
|
||||
c.ResponseError("Display name cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
columns := []string{}
|
||||
if columnsStr != "" {
|
||||
columns = strings.Split(columnsStr, ",")
|
||||
}
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddUser
|
||||
// @Title AddUser
|
||||
// @Tag User API
|
||||
// @Description add user
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-user [post]
|
||||
func (c *ApiController) AddUser() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteUser
|
||||
// @Title DeleteUser
|
||||
// @Tag User API
|
||||
// @Description delete user
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-user [post]
|
||||
func (c *ApiController) DeleteUser() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetEmailAndPhone
|
||||
// @Title GetEmailAndPhone
|
||||
// @Tag User API
|
||||
// @Description get email and phone by username
|
||||
// @Param username formData string true "The username of the user"
|
||||
// @Param organization formData string true "The organization of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-email-and-phone [post]
|
||||
func (c *ApiController) GetEmailAndPhone() {
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
user := object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
||||
var contentType string
|
||||
switch form.Username {
|
||||
case user.Email:
|
||||
contentType = "email"
|
||||
case user.Phone:
|
||||
contentType = "phone"
|
||||
case user.Name:
|
||||
contentType = "username"
|
||||
}
|
||||
|
||||
c.ResponseOk(respUser, contentType)
|
||||
}
|
||||
|
||||
// SetPassword
|
||||
// @Title SetPassword
|
||||
// @Tag Account API
|
||||
// @Description set password
|
||||
// @Param userOwner formData string true "The owner of the user"
|
||||
// @Param userName formData string true "The name of the user"
|
||||
// @Param oldPassword formData string true "The old password of the user"
|
||||
// @Param newPassword formData string true "The new password of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /set-password [post]
|
||||
func (c *ApiController) SetPassword() {
|
||||
userOwner := c.Ctx.Request.Form.Get("userOwner")
|
||||
userName := c.Ctx.Request.Form.Get("userName")
|
||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" {
|
||||
c.ResponseError("Please login first.")
|
||||
return
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||
targetUser := object.GetUser(userId)
|
||||
if targetUser == nil {
|
||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||
return
|
||||
}
|
||||
|
||||
hasPermission := false
|
||||
if strings.HasPrefix(requestUserId, "app/") {
|
||||
hasPermission = true
|
||||
} else {
|
||||
requestUser := object.GetUser(requestUserId)
|
||||
if requestUser == nil {
|
||||
c.ResponseError("Session outdated. Please login again.")
|
||||
return
|
||||
}
|
||||
if requestUser.IsGlobalAdmin {
|
||||
hasPermission = true
|
||||
} else if requestUserId == userId {
|
||||
hasPermission = true
|
||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||
hasPermission = true
|
||||
}
|
||||
}
|
||||
if !hasPermission {
|
||||
c.ResponseError("You don't have the permission to do this.")
|
||||
return
|
||||
}
|
||||
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(newPassword, " ") {
|
||||
c.ResponseError("New password cannot contain blank space.")
|
||||
return
|
||||
}
|
||||
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError("New password must have at least 6 characters")
|
||||
return
|
||||
}
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title CheckUserPassword
|
||||
// @router /check-user-password [post]
|
||||
// @Tag User API
|
||||
func (c *ApiController) CheckUserPassword() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password)
|
||||
if msg == "" {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// GetSortedUsers
|
||||
// @Title GetSortedUsers
|
||||
// @Tag User API
|
||||
// @Description
|
||||
// @Param owner query string true "The owner of users"
|
||||
// @Param sorter query string true "The DB column name to sort by, e.g., created_time"
|
||||
// @Param limit query string true "The count of users to return, e.g., 25"
|
||||
// @Success 200 {array} object.User The Response object
|
||||
// @router /get-sorted-users [get]
|
||||
func (c *ApiController) GetSortedUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
sorter := c.Input().Get("sorter")
|
||||
limit := util.ParseInt(c.Input().Get("limit"))
|
||||
|
||||
c.Data["json"] = object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetUserCount
|
||||
// @Title GetUserCount
|
||||
// @Tag User API
|
||||
// @Description
|
||||
// @Param owner query string true "The owner of users"
|
||||
// @Param isOnline query string true "The filter for query, 1 for online, 0 for offline, empty string for all users"
|
||||
// @Success 200 {int} int The count of filtered users for an organization
|
||||
// @router /get-user-count [get]
|
||||
func (c *ApiController) GetUserCount() {
|
||||
owner := c.Input().Get("owner")
|
||||
isOnline := c.Input().Get("isOnline")
|
||||
|
||||
count := 0
|
||||
if isOnline == "" {
|
||||
count = object.GetUserCount(owner, "", "")
|
||||
} else {
|
||||
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
|
||||
}
|
||||
|
||||
c.Data["json"] = count
|
||||
c.ServeJSON()
|
||||
}
|
||||
60
controllers/user_upload.go
Normal file
60
controllers/user_upload.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func saveFile(path string, file *multipart.File) {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, *file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) UploadUsers() {
|
||||
userId := c.GetSessionUsername()
|
||||
owner, user := util.GetOwnerAndNameFromId(userId)
|
||||
|
||||
file, header, err := c.Ctx.Request.FormFile("file")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
|
||||
|
||||
path := util.GetUploadXlsxPath(fileId)
|
||||
util.EnsureFileFolderExists(path)
|
||||
saveFile(path, &file)
|
||||
|
||||
affected := object.UploadUsers(owner, fileId)
|
||||
if affected {
|
||||
c.ResponseOk()
|
||||
} else {
|
||||
c.ResponseError("Failed to import users")
|
||||
}
|
||||
}
|
||||
102
controllers/util.go
Normal file
102
controllers/util.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// ResponseOk ...
|
||||
func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||
resp := Response{Status: "ok"}
|
||||
switch len(data) {
|
||||
case 2:
|
||||
resp.Data2 = data[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
resp.Data = data[0]
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResponseError ...
|
||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||
resp := Response{Status: "error", Msg: error}
|
||||
switch len(data) {
|
||||
case 2:
|
||||
resp.Data2 = data[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
resp.Data = data[0]
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// RequireSignedIn ...
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError("Please sign in first")
|
||||
return "", false
|
||||
}
|
||||
return userId, true
|
||||
}
|
||||
|
||||
func getInitScore() int {
|
||||
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
providerName := c.Input().Get("provider")
|
||||
if providerName != "" {
|
||||
provider := object.GetProvider(util.GetId(providerName))
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
|
||||
return nil, nil, false
|
||||
}
|
||||
return provider, nil, true
|
||||
}
|
||||
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
application, user := object.GetApplicationByUserId(userId)
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("No application is found for userId: \"%s\"", userId))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
provider := application.GetProviderByCategory(category)
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("No provider for category: \"%s\" is found for application: %s", category, application.Name))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
return provider, user, true
|
||||
}
|
||||
165
controllers/verification.go
Normal file
165
controllers/verification.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// SendVerificationCode ...
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
orgId := c.Ctx.Request.Form.Get("organizationId")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
checkUser := c.Ctx.Request.Form.Get("checkUser")
|
||||
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
|
||||
|
||||
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
isHuman := false
|
||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
||||
if captchaProvider == nil {
|
||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
|
||||
if checkUser == "true" && user == nil &&
|
||||
object.GetUserByFields(organization.Name, dest) == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
sendResp := errors.New("Invalid dest type.")
|
||||
switch destType {
|
||||
case "email":
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError("Invalid Email address")
|
||||
return
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
}
|
||||
org := object.GetOrganization(orgId)
|
||||
if org == nil {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
}
|
||||
|
||||
if sendResp != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()}
|
||||
} else {
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
}
|
||||
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// ResetEmailOrPhone ...
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
// @router /api/reset-email-or-phone [post]
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
checkDest := dest
|
||||
if destType == "phone" {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
phonePrefix := "86"
|
||||
if org != nil && org.PhonePrefix != "" {
|
||||
phonePrefix = org.PhonePrefix
|
||||
}
|
||||
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
|
||||
}
|
||||
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
|
||||
c.ResponseError(ret)
|
||||
return
|
||||
}
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
user.Email = dest
|
||||
object.SetUserField(user, "email", user.Email)
|
||||
case "phone":
|
||||
user.Phone = dest
|
||||
object.SetUserField(user, "phone", user.Phone)
|
||||
default:
|
||||
c.ResponseError("Unknown type.")
|
||||
return
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
}
|
||||
116
controllers/webhook.go
Normal file
116
controllers/webhook.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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/astaxie/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetWebhooks
|
||||
// @Title GetWebhooks
|
||||
// @Tag Webhook API
|
||||
// @Description get webhooks
|
||||
// @Param owner query string true "The owner of webhooks"
|
||||
// @Success 200 {array} object.Webhook The Response object
|
||||
// @router /get-webhooks [get]
|
||||
func (c *ApiController) GetWebhooks() {
|
||||
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 == "" {
|
||||
c.Data["json"] = object.GetWebhooks(owner)
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
|
||||
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
c.ResponseOk(webhooks, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// @Title GetWebhook
|
||||
// @Tag Webhook API
|
||||
// @Description get webhook
|
||||
// @Param id query string true "The id of the webhook"
|
||||
// @Success 200 {object} object.Webhook The Response object
|
||||
// @router /get-webhook [get]
|
||||
func (c *ApiController) GetWebhook() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetWebhook(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateWebhook
|
||||
// @Tag Webhook API
|
||||
// @Description update webhook
|
||||
// @Param id query string true "The id of the webhook"
|
||||
// @Param body body object.Webhook true "The details of the webhook"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-webhook [post]
|
||||
func (c *ApiController) UpdateWebhook() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var webhook object.Webhook
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateWebhook(id, &webhook))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddWebhook
|
||||
// @Tag Webhook API
|
||||
// @Description add webhook
|
||||
// @Param body body object.Webhook true "The details of the webhook"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-webhook [post]
|
||||
func (c *ApiController) AddWebhook() {
|
||||
var webhook object.Webhook
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddWebhook(&webhook))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteWebhook
|
||||
// @Tag Webhook API
|
||||
// @Description delete webhook
|
||||
// @Param body body object.Webhook true "The details of the webhook"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-webhook [post]
|
||||
func (c *ApiController) DeleteWebhook() {
|
||||
var webhook object.Webhook
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteWebhook(&webhook))
|
||||
c.ServeJSON()
|
||||
}
|
||||
23
cred/bcrypt.go
Normal file
23
cred/bcrypt.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package cred
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
type BcryptCredManager struct{}
|
||||
|
||||
func NewBcryptCredManager() *BcryptCredManager {
|
||||
cm := &BcryptCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
|
||||
return err == nil
|
||||
}
|
||||
33
cred/manager.go
Normal file
33
cred/manager.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 The casbin 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
|
||||
|
||||
type CredManager interface {
|
||||
GetHashedPassword(password string, userSalt string, organizationSalt string) string
|
||||
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
|
||||
}
|
||||
|
||||
func GetCredManager(passwordType string) CredManager {
|
||||
if passwordType == "plain" {
|
||||
return NewPlainCredManager()
|
||||
} else if passwordType == "salt" {
|
||||
return NewSha256SaltCredManager()
|
||||
} else if passwordType == "md5-salt" {
|
||||
return NewMd5UserSaltCredManager()
|
||||
} else if passwordType == "bcrypt" {
|
||||
return NewBcryptCredManager()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
48
cred/md5-user-salt.go
Normal file
48
cred/md5-user-salt.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2021 The casbin 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/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
type Md5UserSaltCredManager struct{}
|
||||
|
||||
func getMd5(data []byte) []byte {
|
||||
hash := md5.Sum(data)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func getMd5HexDigest(s string) string {
|
||||
b := getMd5([]byte(s))
|
||||
res := hex.EncodeToString(b)
|
||||
return res
|
||||
}
|
||||
|
||||
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
|
||||
cm := &Sha256SaltCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getMd5HexDigest(password)
|
||||
res := getMd5HexDigest(hash + userSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
30
cred/plain.go
Normal file
30
cred/plain.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2021 The casbin 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
|
||||
|
||||
type PlainCredManager struct{}
|
||||
|
||||
func NewPlainCredManager() *PlainCredManager {
|
||||
cm := &PlainCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
return password
|
||||
}
|
||||
|
||||
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == plainPwd
|
||||
}
|
||||
48
cred/sha256-salt.go
Normal file
48
cred/sha256-salt.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2021 The casbin 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/hex"
|
||||
)
|
||||
|
||||
type Sha256SaltCredManager struct{}
|
||||
|
||||
func getSha256(data []byte) []byte {
|
||||
hash := sha256.Sum256(data)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func getSha256HexDigest(s string) string {
|
||||
b := getSha256([]byte(s))
|
||||
res := hex.EncodeToString(b)
|
||||
return res
|
||||
}
|
||||
|
||||
func NewSha256SaltCredManager() *Sha256SaltCredManager {
|
||||
cm := &Sha256SaltCredManager{}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
|
||||
hash := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash + organizationSalt)
|
||||
return res
|
||||
}
|
||||
|
||||
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
|
||||
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
|
||||
}
|
||||
27
cred/sha256-salt_test.go
Normal file
27
cred/sha256-salt_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2021 The casbin 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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSaltedPassword(t *testing.T) {
|
||||
password := "123456"
|
||||
salt := "123"
|
||||
cm := NewSha256SaltCredManager()
|
||||
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
|
||||
}
|
||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3.1'
|
||||
services:
|
||||
restart: always
|
||||
casdoor:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
- db
|
||||
environment:
|
||||
RUNNING_IN_DOCKER: "true"
|
||||
volumes:
|
||||
- ./conf:/conf/
|
||||
db:
|
||||
restart: always
|
||||
image: mysql:8.0.25
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
volumes:
|
||||
- /usr/local/docker/mysql:/var/lib/mysql
|
||||
49
go.mod
49
go.mod
@@ -1,23 +1,40 @@
|
||||
module github.com/casdoor/casdoor
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-playground/validator/v10 v10.4.0 // indirect
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
||||
github.com/astaxie/beego v1.12.3
|
||||
github.com/aws/aws-sdk-go v1.37.30
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||
github.com/casdoor/go-sms-sender v0.0.5
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/json-iterator/go v1.1.10 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/ugorji/go v1.1.12 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/jinzhu/configor v1.2.1 // indirect
|
||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.6.0
|
||||
github.com/russellhaering/goxmldsig v1.1.1
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
xorm.io/core v0.7.2
|
||||
xorm.io/xorm v0.8.1
|
||||
xorm.io/xorm v1.0.3
|
||||
)
|
||||
|
||||
620
go.sum
620
go.sum
@@ -1,224 +1,635 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak=
|
||||
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
|
||||
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
|
||||
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
||||
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
|
||||
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.0 h1:72qIR/m8ybvL8L5TIyfgrigqkrw7kVYAvjEvpT85l70=
|
||||
github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
||||
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
|
||||
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
|
||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
|
||||
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russellhaering/gosaml2 v0.6.0 h1:OED8FLgczXxXAPlKhnJHQfmEig52tDX2qeXdPtZRIKc=
|
||||
github.com/russellhaering/gosaml2 v0.6.0/go.mod h1:CtzxpPr4+bevsATaqR0rw3aqrNlX274b+3C6vFTLCk8=
|
||||
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
|
||||
github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM=
|
||||
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.1.12 h1:CzCiySZwEUgSwlgT8bTjFfV3rR6+Ti0atNsQkCRJnek=
|
||||
github.com/ugorji/go v1.1.12/go.mod h1:Ne4Hz4JKQXNr/qi+hC0ovwseF9muoPjAZp2lylyih70=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.1.12 h1:pv4DBnMb5X9XXCNC0DyEmhU3I/61gWDdyH7iZps5DLs=
|
||||
github.com/ugorji/go/codec v1.1.12/go.mod h1:U/SFD954ms+MwaHihwfeIz/sGz5OFgHt81tHc+Duy5k=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
|
||||
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
|
||||
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -227,28 +638,53 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
|
||||
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||
xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ=
|
||||
xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
|
||||
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
||||
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||
|
||||
97
i18n/generate.go
Normal file
97
i18n/generate.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2021 The casbin 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 i18n
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type I18nData map[string]map[string]string
|
||||
|
||||
var reI18n *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
reI18n, _ = regexp.Compile("i18next.t\\(\"(.*?)\"\\)")
|
||||
}
|
||||
|
||||
func getAllI18nStrings(fileContent string) []string {
|
||||
res := []string{}
|
||||
|
||||
matches := reI18n.FindAllStringSubmatch(fileContent, -1)
|
||||
if matches == nil {
|
||||
return res
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
res = append(res, match[1])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getAllJsFilePaths() []string {
|
||||
path := "../web/src"
|
||||
|
||||
res := []string{}
|
||||
err := filepath.Walk(path,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(info.Name(), ".js") {
|
||||
return nil
|
||||
}
|
||||
|
||||
res = append(res, path)
|
||||
fmt.Println(path, info.Name())
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func parseToData() *I18nData {
|
||||
allWords := []string{}
|
||||
paths := getAllJsFilePaths()
|
||||
for _, path := range paths {
|
||||
fileContent := util.ReadStringFromPath(path)
|
||||
words := getAllI18nStrings(fileContent)
|
||||
allWords = append(allWords, words...)
|
||||
}
|
||||
fmt.Printf("%v\n", allWords)
|
||||
|
||||
data := I18nData{}
|
||||
for _, word := range allWords {
|
||||
tokens := strings.Split(word, ":")
|
||||
namespace := tokens[0]
|
||||
key := tokens[1]
|
||||
|
||||
if _, ok := data[namespace]; !ok {
|
||||
data[namespace] = map[string]string{}
|
||||
}
|
||||
data[namespace][key] = key
|
||||
}
|
||||
|
||||
return &data
|
||||
}
|
||||
37
i18n/generate_test.go
Normal file
37
i18n/generate_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2021 The casbin 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 i18n
|
||||
|
||||
import "testing"
|
||||
|
||||
func applyToOtherLanguage(dataEn *I18nData, lang string) {
|
||||
dataOther := readI18nFile(lang)
|
||||
println(dataOther)
|
||||
|
||||
applyData(dataEn, dataOther)
|
||||
writeI18nFile(lang, dataEn)
|
||||
}
|
||||
|
||||
func TestGenerateI18nStrings(t *testing.T) {
|
||||
dataEn := parseToData()
|
||||
writeI18nFile("en", dataEn)
|
||||
|
||||
applyToOtherLanguage(dataEn, "de")
|
||||
applyToOtherLanguage(dataEn, "fr")
|
||||
applyToOtherLanguage(dataEn, "ja")
|
||||
applyToOtherLanguage(dataEn, "ko")
|
||||
applyToOtherLanguage(dataEn, "ru")
|
||||
applyToOtherLanguage(dataEn, "zh")
|
||||
}
|
||||
63
i18n/util.go
Normal file
63
i18n/util.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 The casbin 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 i18n
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getI18nFilePath(language string) string {
|
||||
return fmt.Sprintf("../web/src/locales/%s/data.json", language)
|
||||
}
|
||||
|
||||
func readI18nFile(language string) *I18nData {
|
||||
s := util.ReadStringFromPath(getI18nFilePath(language))
|
||||
|
||||
data := &I18nData{}
|
||||
err := util.JsonToStruct(s, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func writeI18nFile(language string, data *I18nData) {
|
||||
s := util.StructToJsonFormatted(data)
|
||||
s = strings.ReplaceAll(s, "\\u0026", "&")
|
||||
println(s)
|
||||
|
||||
util.WriteStringToPath(s, getI18nFilePath(language))
|
||||
}
|
||||
|
||||
func applyData(data1 *I18nData, data2 *I18nData) {
|
||||
for namespace, pairs2 := range *data2 {
|
||||
if _, ok := (*data1)[namespace]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pairs1 := (*data1)[namespace]
|
||||
|
||||
for key, value := range pairs2 {
|
||||
if _, ok := pairs1[key]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
pairs1[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
116
idp/baidu.go
Normal file
116
idp/baidu.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type BaiduIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewBaiduIdProvider(clientId string, clientSecret string, redirectUrl string) *BaiduIdProvider {
|
||||
idp := &BaiduIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize",
|
||||
TokenURL: "https://openapi.baidu.com/oauth/2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"userid":"2097322476",
|
||||
"username":"wl19871011",
|
||||
"realname":"阳光",
|
||||
"userdetail":"喜欢自由",
|
||||
"birthday":"1987-01-01",
|
||||
"marriage":"恋爱",
|
||||
"sex":"男",
|
||||
"blood":"O",
|
||||
"constellation":"射手",
|
||||
"figure":"小巧",
|
||||
"education":"大学/专科",
|
||||
"trade":"计算机/电子产品",
|
||||
"job":"未知",
|
||||
"birthday_year":"1987",
|
||||
"birthday_month":"01",
|
||||
"birthday_day":"01",
|
||||
}
|
||||
*/
|
||||
|
||||
type BaiduUserInfo struct {
|
||||
OpenId string `json:"openid"`
|
||||
Username string `json:"username"`
|
||||
Portrait string `json:"portrait"`
|
||||
}
|
||||
|
||||
func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
resp, err := idp.Client.Get(fmt.Sprintf("https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=%s", token.AccessToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
baiduUser := BaiduUserInfo{}
|
||||
if err = json.Unmarshal(data, &baiduUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: baiduUser.OpenId,
|
||||
Username: baiduUser.Username,
|
||||
DisplayName: baiduUser.Username,
|
||||
AvatarUrl: fmt.Sprintf("https://himg.bdimg.com/sys/portrait/item/%s", baiduUser.Portrait),
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
365
idp/dingtalk.go
Normal file
365
idp/dingtalk.go
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// A total of three steps are required:
|
||||
//
|
||||
// 1. Construct the link and get the temporary authorization code
|
||||
// tmp_auth_code through the code at the end of the url.
|
||||
//
|
||||
// 2. Use hmac256 to calculate the signature, and then submit it together with timestamp,
|
||||
// tmp_auth_code, accessKey to obtain unionid, userid, accessKey.
|
||||
//
|
||||
// 3. Get detailed information through userid.
|
||||
|
||||
type DingTalkIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
// NewDingTalkIdProvider ...
|
||||
func NewDingTalkIdProvider(clientId string, clientSecret string, redirectUrl string) *DingTalkIdProvider {
|
||||
idp := &DingTalkIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
// SetHttpClient ...
|
||||
func (idp *DingTalkIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *DingTalkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://oapi.dingtalk.com/sns/getuserinfo_bycode",
|
||||
TokenURL: "https://oapi.dingtalk.com/gettoken",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
// DingTalk not allow to set scopes,here it is just a placeholder,
|
||||
// convenient to use later
|
||||
Scopes: []string{"", ""},
|
||||
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type DingTalkAccessToken struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
AccessToken string `json:"access_token"` // Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
type DingTalkIds struct {
|
||||
UserId string `json:"user_id"`
|
||||
UnionId string `json:"union_id"`
|
||||
}
|
||||
|
||||
type InfoResp struct {
|
||||
Errcode int `json:"errcode"`
|
||||
UserInfo struct {
|
||||
Nick string `json:"nick"`
|
||||
Unionid string `json:"unionid"`
|
||||
Openid string `json:"openid"`
|
||||
MainOrgAuthHighLevel bool `json:"main_org_auth_high_level"`
|
||||
} `json:"user_info"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/dingtalk-retrieve-user-information?spm=ding_open_doc.document.0.0.51b91a31wWV3tY#doc-api-dingtalk-GetUser
|
||||
func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
||||
signature := EncodeSHA256(timestamp, idp.Config.ClientSecret)
|
||||
u := fmt.Sprintf(
|
||||
"%s?accessKey=%s×tamp=%s&signature=%s", idp.Config.Endpoint.AuthURL,
|
||||
idp.Config.ClientID, timestamp, signature)
|
||||
|
||||
tmpCode := struct {
|
||||
TmpAuthCode string `json:"tmp_auth_code"`
|
||||
}{code}
|
||||
bs, _ := json.Marshal(tmpCode)
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := http.Post(u, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
info := InfoResp{}
|
||||
_ = json.Unmarshal(body, &info)
|
||||
errCode := info.Errcode
|
||||
if errCode != 0 {
|
||||
return nil, fmt.Errorf("%d: %s", errCode, info.Errmsg)
|
||||
}
|
||||
|
||||
u2 := fmt.Sprintf("%s?appkey=%s&appsecret=%s", idp.Config.Endpoint.TokenURL, idp.Config.ClientID, idp.Config.ClientSecret)
|
||||
resp, _ = http.Get(u2)
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
body, _ = io.ReadAll(resp.Body)
|
||||
tokenResp := DingTalkAccessToken{}
|
||||
_ = json.Unmarshal(body, &tokenResp)
|
||||
if tokenResp.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("%d: %s", tokenResp.ErrCode, tokenResp.ErrMsg)
|
||||
}
|
||||
|
||||
// use unionid to get userid
|
||||
unionid := info.UserInfo.Unionid
|
||||
userid, err := idp.GetUseridByUnionid(tokenResp.AccessToken, unionid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Since DingTalk does not require scopes, put userid and unionid into
|
||||
// idp.config.scopes to facilitate GetUserInfo() to obtain these two parameters.
|
||||
idp.Config.Scopes = []string{unionid, userid}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
type UnionIdResponse struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Result struct {
|
||||
ContactType string `json:"contact_type"`
|
||||
Userid string `json:"userid"`
|
||||
} `json:"result"`
|
||||
RequestId string `json:"request_id"`
|
||||
}
|
||||
|
||||
// GetUseridByUnionid ...
|
||||
func (idp *DingTalkIdProvider) GetUseridByUnionid(accesstoken, unionid string) (userid string, err error) {
|
||||
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=%s&unionid=%s",
|
||||
accesstoken, unionid)
|
||||
useridInfo, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uresp := UnionIdResponse{}
|
||||
_ = json.Unmarshal([]byte(useridInfo), &uresp)
|
||||
errcode := uresp.Errcode
|
||||
if errcode != 0 {
|
||||
return "", fmt.Errorf("%d: %s", errcode, uresp.Errmsg)
|
||||
}
|
||||
return uresp.Result.Userid, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"errcode":0,
|
||||
"result":{
|
||||
"boss":false,
|
||||
"unionid":"5M6zgZBKQPCxdiPdANeJ6MgiEiE",
|
||||
"role_list":[
|
||||
{
|
||||
"group_name":"默认",
|
||||
"name":"主管理员",
|
||||
"id":2062489174
|
||||
}
|
||||
],
|
||||
"exclusive_account":false,
|
||||
"mobile":"15236176076",
|
||||
"active":true,
|
||||
"admin":true,
|
||||
"avatar":"https://static-legacy.dingtalk.com/media/lALPDeRETW9WAnnNAyDNAyA_800_800.png",
|
||||
"hide_mobile":false,
|
||||
"userid":"manager4713",
|
||||
"senior":false,
|
||||
"dept_order_list":[
|
||||
{
|
||||
"dept_id":1,
|
||||
"order":176294576350761512
|
||||
}
|
||||
],
|
||||
"real_authed":true,
|
||||
"name":"刘继坤",
|
||||
"dept_id_list":[
|
||||
1
|
||||
],
|
||||
"state_code":"86",
|
||||
"email":"",
|
||||
"leader_in_dept":[
|
||||
{
|
||||
"leader":false,
|
||||
"dept_id":1
|
||||
}
|
||||
]
|
||||
},
|
||||
"errmsg":"ok",
|
||||
"request_id":"3sug9d2exsla"
|
||||
}
|
||||
*/
|
||||
|
||||
type DingTalkUserResponse struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Result struct {
|
||||
Extension string `json:"extension"`
|
||||
Unionid string `json:"unionid"`
|
||||
Boss bool `json:"boss"`
|
||||
UnionEmpExt struct {
|
||||
CorpId string `json:"corpId"`
|
||||
Userid string `json:"userid"`
|
||||
UnionEmpMapList []struct {
|
||||
CorpId string `json:"corpId"`
|
||||
Userid string `json:"userid"`
|
||||
} `json:"unionEmpMapList"`
|
||||
} `json:"unionEmpExt"`
|
||||
RoleList []struct {
|
||||
GroupName string `json:"group_name"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"role_list"`
|
||||
Admin bool `json:"admin"`
|
||||
Remark string `json:"remark"`
|
||||
Title string `json:"title"`
|
||||
HiredDate int64 `json:"hired_date"`
|
||||
Userid string `json:"userid"`
|
||||
WorkPlace string `json:"work_place"`
|
||||
DeptOrderList []struct {
|
||||
DeptId int `json:"dept_id"`
|
||||
Order int64 `json:"order"`
|
||||
} `json:"dept_order_list"`
|
||||
RealAuthed bool `json:"real_authed"`
|
||||
DeptIdList []int `json:"dept_id_list"`
|
||||
JobNumber string `json:"job_number"`
|
||||
Email string `json:"email"`
|
||||
LeaderInDept []struct {
|
||||
DeptId int `json:"dept_id"`
|
||||
Leader bool `json:"leader"`
|
||||
} `json:"leader_in_dept"`
|
||||
ManagerUserid string `json:"manager_userid"`
|
||||
Mobile string `json:"mobile"`
|
||||
Active bool `json:"active"`
|
||||
Telephone string `json:"telephone"`
|
||||
Avatar string `json:"avatar"`
|
||||
HideMobile bool `json:"hide_mobile"`
|
||||
Senior bool `json:"senior"`
|
||||
Name string `json:"name"`
|
||||
StateCode string `json:"state_code"`
|
||||
} `json:"result"`
|
||||
RequestId string `json:"request_id"`
|
||||
}
|
||||
|
||||
// GetUserInfo Use userid and access_token to get UserInfo
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/query-user-details
|
||||
func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var dtUserInfo DingTalkUserResponse
|
||||
accessToken := token.AccessToken
|
||||
|
||||
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/v2/user/get?access_token=%s&userid=%s",
|
||||
accessToken, idp.Config.Scopes[1])
|
||||
|
||||
userinfoResp, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userinfoResp), &dtUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(dtUserInfo.Result.RoleList[0].Id),
|
||||
Username: dtUserInfo.Result.RoleList[0].Name,
|
||||
DisplayName: dtUserInfo.Result.Name,
|
||||
Email: dtUserInfo.Result.Email,
|
||||
AvatarUrl: dtUserInfo.Result.Avatar,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// EncodeSHA256 Use the HmacSHA256 algorithm to sign, the signature data is the current timestamp,
|
||||
// and the key is the appSecret corresponding to the appId. Use this key to calculate the timestamp signature value.
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/signature-calculation-for-logon-free-scenarios-1?spm=ding_open_doc.document.0.0.63262ea7l6iEm1#topic-2021698
|
||||
func EncodeSHA256(message, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(message))
|
||||
sum := h.Sum(nil)
|
||||
msg1 := base64.StdEncoding.EncodeToString(sum)
|
||||
|
||||
uv := url.Values{}
|
||||
uv.Add("0", msg1)
|
||||
msg2 := uv.Encode()[2:]
|
||||
return msg2
|
||||
}
|
||||
194
idp/facebook.go
Normal file
194
idp/facebook.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type FacebookIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewFacebookIdProvider(clientId string, clientSecret string, redirectUrl string) *FacebookIdProvider {
|
||||
idp := &FacebookIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *FacebookIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *FacebookIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.facebook.com/oauth/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email,public_profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type FacebookAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
TokenType string `json:"token_type"` //Access token type
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
type FacebookCheckToken struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
|
||||
type FacebookCheckTokenData struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
|
||||
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("https://graph.facebook.com/oauth/access_token?%s", params.Encode())
|
||||
|
||||
accessTokenResp, err := idp.GetUrlResp(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var facebookAccessToken FacebookAccessToken
|
||||
if err = json.Unmarshal([]byte(accessTokenResp), &facebookAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: facebookAccessToken.AccessToken,
|
||||
TokenType: "FacebookAccessToken",
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "id": "123456789",
|
||||
// "name": "Example Name",
|
||||
// "name_format": "{first} {last}",
|
||||
// "picture": {
|
||||
// "data": {
|
||||
// "height": 50,
|
||||
// "is_silhouette": false,
|
||||
// "url": "https://example.com",
|
||||
// "width": 50
|
||||
// }
|
||||
// },
|
||||
// "email": "test@example.com"
|
||||
//}
|
||||
|
||||
type FacebookUserInfo struct {
|
||||
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
|
||||
Name string `json:"name"` // The person's full name.
|
||||
NameFormat string `json:"name_format"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
|
||||
Picture struct { // The person's profile picture.
|
||||
Data struct { // This struct is different as https://developers.facebook.com/docs/graph-api/reference/user/picture/
|
||||
Height int `json:"height"`
|
||||
IsSilhouette bool `json:"is_silhouette"`
|
||||
Url string `json:"url"`
|
||||
Width int `json:"width"`
|
||||
} `json:"data"`
|
||||
} `json:"picture"`
|
||||
Email string `json:"email"` // The User's primary email address listed on their profile. This field will not be returned if no valid email address is available.
|
||||
}
|
||||
|
||||
// GetUserInfo use FacebookAccessToken gotten before return FacebookUserInfo
|
||||
// get more detail via: https://developers.facebook.com/docs/graph-api/reference/user
|
||||
func (idp *FacebookIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var facebookUserInfo FacebookUserInfo
|
||||
accessToken := token.AccessToken
|
||||
|
||||
userIdUrl := fmt.Sprintf("https://graph.facebook.com/me?access_token=%s", accessToken)
|
||||
userIdResp, err := idp.GetUrlResp(userIdUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userIdResp), &facebookUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://graph.facebook.com/%s?fields=id,name,name_format,picture,email&access_token=%s", facebookUserInfo.Id, accessToken)
|
||||
userInfoResp, err := idp.GetUrlResp(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userInfoResp), &facebookUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: facebookUserInfo.Id,
|
||||
DisplayName: facebookUserInfo.Name,
|
||||
Email: facebookUserInfo.Email,
|
||||
AvatarUrl: facebookUserInfo.Picture.Data.Url,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *FacebookIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
229
idp/gitee.go
Normal file
229
idp/gitee.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GiteeIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGiteeIdProvider(clientId string, clientSecret string, redirectUrl string) *GiteeIdProvider {
|
||||
idp := &GiteeIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GiteeIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *GiteeIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://gitee.com/oauth/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"user_info emails"},
|
||||
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type GiteeAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Scope string `json:"scope"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// The POST Url format of submission is: https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
|
||||
// get more detail via: https://gitee.com/api/v5/oauth_doc#/
|
||||
func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
bs, _ := json.Marshal(params.Encode())
|
||||
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenResp := GiteeAccessToken{}
|
||||
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
TokenType: tokenResp.TokenType,
|
||||
RefreshToken: tokenResp.RefreshToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(tokenResp.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id": 999999,
|
||||
"login": "xxx",
|
||||
"name": "xxx",
|
||||
"avatar_url": "https://gitee.com/assets/no_portrait.png",
|
||||
"url": "https://gitee.com/api/v5/users/xxx",
|
||||
"html_url": "https://gitee.com/xxx",
|
||||
"followers_url": "https://gitee.com/api/v5/users/xxx/followers",
|
||||
"following_url": "https://gitee.com/api/v5/users/xxx/following_url{/other_user}",
|
||||
"gists_url": "https://gitee.com/api/v5/users/xxx/gists{/gist_id}",
|
||||
"starred_url": "https://gitee.com/api/v5/users/xxx/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://gitee.com/api/v5/users/xxx/subscriptions",
|
||||
"organizations_url": "https://gitee.com/api/v5/users/xxx/orgs",
|
||||
"repos_url": "https://gitee.com/api/v5/users/xxx/repos",
|
||||
"events_url": "https://gitee.com/api/v5/users/xxx/events{/privacy}",
|
||||
"received_events_url": "https://gitee.com/api/v5/users/xxx/received_events",
|
||||
"type": "User",
|
||||
"blog": null,
|
||||
"weibo": null,
|
||||
"bio": "个人博客:https://gitee.com/xxx/xxx/pages",
|
||||
"public_repos": 2,
|
||||
"public_gists": 0,
|
||||
"followers": 0,
|
||||
"following": 0,
|
||||
"stared": 0,
|
||||
"watched": 2,
|
||||
"created_at": "2019-08-03T23:21:16+08:00",
|
||||
"updated_at": "2021-06-14T12:47:09+08:00",
|
||||
"email": null
|
||||
}
|
||||
*/
|
||||
|
||||
type GiteeUserResponse struct {
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
Bio string `json:"bio"`
|
||||
Blog string `json:"blog"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Email string `json:"email"`
|
||||
EventsUrl string `json:"events_url"`
|
||||
Followers int `json:"followers"`
|
||||
FollowersUrl string `json:"followers_url"`
|
||||
Following int `json:"following"`
|
||||
FollowingUrl string `json:"following_url"`
|
||||
GistsUrl string `json:"gists_url"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Id int `json:"id"`
|
||||
Login string `json:"login"`
|
||||
MemberRole string `json:"member_role"`
|
||||
Name string `json:"name"`
|
||||
OrganizationsUrl string `json:"organizations_url"`
|
||||
PublicGists int `json:"public_gists"`
|
||||
PublicRepos int `json:"public_repos"`
|
||||
ReceivedEventsUrl string `json:"received_events_url"`
|
||||
ReposUrl string `json:"repos_url"`
|
||||
Stared int `json:"stared"`
|
||||
StarredUrl string `json:"starred_url"`
|
||||
SubscriptionsUrl string `json:"subscriptions_url"`
|
||||
Type string `json:"type"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Url string `json:"url"`
|
||||
Watched int `json:"watched"`
|
||||
Weibo string `json:"weibo"`
|
||||
}
|
||||
|
||||
// GetUserInfo Use userid and access_token to get UserInfo
|
||||
// get more detail via: https://gitee.com/api/v5/swagger#/getV5User
|
||||
func (idp *GiteeIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var gtUserInfo GiteeUserResponse
|
||||
accessToken := token.AccessToken
|
||||
|
||||
u := fmt.Sprintf("https://gitee.com/api/v5/user?access_token=%s",
|
||||
accessToken)
|
||||
|
||||
userinfoResp, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userinfoResp), >UserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(gtUserInfo.Id),
|
||||
Username: gtUserInfo.Name,
|
||||
DisplayName: gtUserInfo.Name,
|
||||
Email: gtUserInfo.Email,
|
||||
AvatarUrl: gtUserInfo.AvatarUrl,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *GiteeIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
194
idp/github.go
Normal file
194
idp/github.go
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GithubIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGithubIdProvider(clientId string, clientSecret string, redirectUrl string) *GithubIdProvider {
|
||||
idp := &GithubIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://github.com/login/oauth/authorize",
|
||||
TokenURL: "https://github.com/login/oauth/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"user:email", "read:user"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
//{
|
||||
// "login": "jimgreen",
|
||||
// "id": 3781234,
|
||||
// "node_id": "MDQ6VXNlcjM3O123456=",
|
||||
// "avatar_url": "https://avatars.githubusercontent.com/u/3781234?v=4",
|
||||
// "gravatar_id": "",
|
||||
// "url": "https://api.github.com/users/jimgreen",
|
||||
// "html_url": "https://github.com/jimgreen",
|
||||
// "followers_url": "https://api.github.com/users/jimgreen/followers",
|
||||
// "following_url": "https://api.github.com/users/jimgreen/following{/other_user}",
|
||||
// "gists_url": "https://api.github.com/users/jimgreen/gists{/gist_id}",
|
||||
// "starred_url": "https://api.github.com/users/jimgreen/starred{/owner}{/repo}",
|
||||
// "subscriptions_url": "https://api.github.com/users/jimgreen/subscriptions",
|
||||
// "organizations_url": "https://api.github.com/users/jimgreen/orgs",
|
||||
// "repos_url": "https://api.github.com/users/jimgreen/repos",
|
||||
// "events_url": "https://api.github.com/users/jimgreen/events{/privacy}",
|
||||
// "received_events_url": "https://api.github.com/users/jimgreen/received_events",
|
||||
// "type": "User",
|
||||
// "site_admin": false,
|
||||
// "name": "Jim Green",
|
||||
// "company": "Casbin",
|
||||
// "blog": "https://casbin.org",
|
||||
// "location": "Bay Area",
|
||||
// "email": "jimgreen@gmail.com",
|
||||
// "hireable": true,
|
||||
// "bio": "My bio",
|
||||
// "twitter_username": null,
|
||||
// "public_repos": 45,
|
||||
// "public_gists": 3,
|
||||
// "followers": 123,
|
||||
// "following": 31,
|
||||
// "created_at": "2016-03-06T13:16:13Z",
|
||||
// "updated_at": "2020-05-30T12:15:29Z",
|
||||
// "private_gists": 0,
|
||||
// "total_private_repos": 12,
|
||||
// "owned_private_repos": 12,
|
||||
// "disk_usage": 46331,
|
||||
// "collaborators": 5,
|
||||
// "two_factor_authentication": true,
|
||||
// "plan": {
|
||||
// "name": "free",
|
||||
// "space": 976562499,
|
||||
// "collaborators": 0,
|
||||
// "private_repos": 10000
|
||||
// }
|
||||
//}
|
||||
|
||||
type GitHubUserInfo struct {
|
||||
Login string `json:"login"`
|
||||
Id int `json:"id"`
|
||||
NodeId string `json:"node_id"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
GravatarId string `json:"gravatar_id"`
|
||||
Url string `json:"url"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
FollowersUrl string `json:"followers_url"`
|
||||
FollowingUrl string `json:"following_url"`
|
||||
GistsUrl string `json:"gists_url"`
|
||||
StarredUrl string `json:"starred_url"`
|
||||
SubscriptionsUrl string `json:"subscriptions_url"`
|
||||
OrganizationsUrl string `json:"organizations_url"`
|
||||
ReposUrl string `json:"repos_url"`
|
||||
EventsUrl string `json:"events_url"`
|
||||
ReceivedEventsUrl string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
Name string `json:"name"`
|
||||
Company string `json:"company"`
|
||||
Blog string `json:"blog"`
|
||||
Location string `json:"location"`
|
||||
Email string `json:"email"`
|
||||
Hireable bool `json:"hireable"`
|
||||
Bio string `json:"bio"`
|
||||
TwitterUsername interface{} `json:"twitter_username"`
|
||||
PublicRepos int `json:"public_repos"`
|
||||
PublicGists int `json:"public_gists"`
|
||||
Followers int `json:"followers"`
|
||||
Following int `json:"following"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PrivateGists int `json:"private_gists"`
|
||||
TotalPrivateRepos int `json:"total_private_repos"`
|
||||
OwnedPrivateRepos int `json:"owned_private_repos"`
|
||||
DiskUsage int `json:"disk_usage"`
|
||||
Collaborators int `json:"collaborators"`
|
||||
TwoFactorAuthentication bool `json:"two_factor_authentication"`
|
||||
Plan struct {
|
||||
Name string `json:"name"`
|
||||
Space int `json:"space"`
|
||||
Collaborators int `json:"collaborators"`
|
||||
PrivateRepos int `json:"private_repos"`
|
||||
} `json:"plan"`
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Add("Authorization", "token "+token.AccessToken)
|
||||
resp, err := idp.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var githubUserInfo GitHubUserInfo
|
||||
err = json.Unmarshal(body, &githubUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(githubUserInfo.Id),
|
||||
Username: githubUserInfo.Login,
|
||||
DisplayName: githubUserInfo.Name,
|
||||
Email: githubUserInfo.Email,
|
||||
AvatarUrl: githubUserInfo.AvatarUrl,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
230
idp/gitlab.go
Normal file
230
idp/gitlab.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GitlabIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
|
||||
idp := &GitlabIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://gitlab.com/oauth/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"read_user+profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type GitlabProviderToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
|
||||
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gtoken := &GitlabProviderToken{}
|
||||
if err = json.Unmarshal(data, gtoken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
|
||||
token := &oauth2.Token{
|
||||
AccessToken: gtoken.AccessToken,
|
||||
TokenType: gtoken.TokenType,
|
||||
RefreshToken: gtoken.RefreshToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(7200), 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id":5162115,
|
||||
"name":"shiluo",
|
||||
"username":"shiluo",
|
||||
"state":"active",
|
||||
"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
|
||||
"web_url":"https://gitlab.com/shiluo",
|
||||
"created_at":"2019-12-23T02:50:10.348Z",
|
||||
"bio":"",
|
||||
"bio_html":"",
|
||||
"location":"China",
|
||||
"public_email":"silo1999@163.com",
|
||||
"skype":"",
|
||||
"linkedin":"",
|
||||
"twitter":"",
|
||||
"website_url":"",
|
||||
"organization":"",
|
||||
"job_title":"",
|
||||
"pronouns":null,
|
||||
"bot":false,
|
||||
"work_information":null,
|
||||
"followers":0,
|
||||
"following":0,
|
||||
"last_sign_in_at":"2019-12-26T13:24:42.941Z",
|
||||
"confirmed_at":"2019-12-23T02:52:10.778Z",
|
||||
"last_activity_on":"2021-08-19",
|
||||
"email":"silo1999@163.com",
|
||||
"theme_id":1,
|
||||
"color_scheme_id":1,
|
||||
"projects_limit":100000,
|
||||
"current_sign_in_at":"2021-08-19T09:46:46.004Z",
|
||||
"identities":[
|
||||
{
|
||||
"provider":"github",
|
||||
"extern_uid":"51157931",
|
||||
"saml_provider_id":null
|
||||
}
|
||||
],
|
||||
"can_create_group":true,
|
||||
"can_create_project":true,
|
||||
"two_factor_enabled":false,
|
||||
"external":false,
|
||||
"private_profile":false,
|
||||
"commit_email":"silo1999@163.com",
|
||||
"shared_runners_minutes_limit":null,
|
||||
"extra_shared_runners_minutes_limit":null
|
||||
}
|
||||
*/
|
||||
|
||||
type GitlabUserInfo struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
State string `json:"state"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
WebUrl string `json:"web_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Bio string `json:"bio"`
|
||||
BioHtml string `json:"bio_html"`
|
||||
Location string `json:"location"`
|
||||
PublicEmail string `json:"public_email"`
|
||||
Skype string `json:"skype"`
|
||||
Linkedin string `json:"linkedin"`
|
||||
Twitter string `json:"twitter"`
|
||||
WebsiteUrl string `json:"website_url"`
|
||||
Organization string `json:"organization"`
|
||||
JobTitle string `json:"job_title"`
|
||||
Pronouns interface{} `json:"pronouns"`
|
||||
Bot bool `json:"bot"`
|
||||
WorkInformation interface{} `json:"work_information"`
|
||||
Followers int `json:"followers"`
|
||||
Following int `json:"following"`
|
||||
LastSignInAt time.Time `json:"last_sign_in_at"`
|
||||
ConfirmedAt time.Time `json:"confirmed_at"`
|
||||
LastActivityOn string `json:"last_activity_on"`
|
||||
Email string `json:"email"`
|
||||
ThemeId int `json:"theme_id"`
|
||||
ColorSchemeId int `json:"color_scheme_id"`
|
||||
ProjectsLimit int `json:"projects_limit"`
|
||||
CurrentSignInAt time.Time `json:"current_sign_in_at"`
|
||||
Identities []struct {
|
||||
Provider string `json:"provider"`
|
||||
ExternUid string `json:"extern_uid"`
|
||||
SamlProviderId interface{} `json:"saml_provider_id"`
|
||||
} `json:"identities"`
|
||||
CanCreateGroup bool `json:"can_create_group"`
|
||||
CanCreateProject bool `json:"can_create_project"`
|
||||
TwoFactorEnabled bool `json:"two_factor_enabled"`
|
||||
External bool `json:"external"`
|
||||
PrivateProfile bool `json:"private_profile"`
|
||||
CommitEmail string `json:"commit_email"`
|
||||
SharedRunnersMinutesLimit interface{} `json:"shared_runners_minutes_limit"`
|
||||
ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
|
||||
}
|
||||
|
||||
// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
|
||||
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token=" + token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guser := GitlabUserInfo{}
|
||||
if err = json.Unmarshal(data, &guser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(guser.Id),
|
||||
Username: guser.Username,
|
||||
DisplayName: guser.Name,
|
||||
AvatarUrl: guser.AvatarUrl,
|
||||
Email: guser.Email,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
121
idp/google.go
Normal file
121
idp/google.go
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GoogleIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider {
|
||||
idp := &GoogleIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"profile", "email"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
//{
|
||||
// "id": "110613473084924141234",
|
||||
// "email": "jimgreen@gmail.com",
|
||||
// "verified_email": true,
|
||||
// "name": "Jim Green",
|
||||
// "given_name": "Jim",
|
||||
// "family_name": "Green",
|
||||
// "picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
|
||||
// "locale": "en"
|
||||
//}
|
||||
|
||||
type GoogleUserInfo struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
VerifiedEmail bool `json:"verified_email"`
|
||||
Name string `json:"name"`
|
||||
GivenName string `json:"given_name"`
|
||||
FamilyName string `json:"family_name"`
|
||||
Picture string `json:"picture"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken)
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var googleUserInfo GoogleUserInfo
|
||||
err = json.Unmarshal(body, &googleUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if googleUserInfo.Email == "" {
|
||||
return nil, errors.New("google email is empty")
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: googleUserInfo.Id,
|
||||
Username: googleUserInfo.Email,
|
||||
DisplayName: googleUserInfo.Name,
|
||||
Email: googleUserInfo.Email,
|
||||
AvatarUrl: googleUserInfo.Picture,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
271
idp/goth.go
Normal file
271
idp/goth.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/providers/amazon"
|
||||
"github.com/markbates/goth/providers/apple"
|
||||
"github.com/markbates/goth/providers/azuread"
|
||||
"github.com/markbates/goth/providers/bitbucket"
|
||||
"github.com/markbates/goth/providers/digitalocean"
|
||||
"github.com/markbates/goth/providers/discord"
|
||||
"github.com/markbates/goth/providers/dropbox"
|
||||
"github.com/markbates/goth/providers/facebook"
|
||||
"github.com/markbates/goth/providers/gitea"
|
||||
"github.com/markbates/goth/providers/github"
|
||||
"github.com/markbates/goth/providers/gitlab"
|
||||
"github.com/markbates/goth/providers/google"
|
||||
"github.com/markbates/goth/providers/heroku"
|
||||
"github.com/markbates/goth/providers/instagram"
|
||||
"github.com/markbates/goth/providers/kakao"
|
||||
"github.com/markbates/goth/providers/line"
|
||||
"github.com/markbates/goth/providers/linkedin"
|
||||
"github.com/markbates/goth/providers/microsoftonline"
|
||||
"github.com/markbates/goth/providers/paypal"
|
||||
"github.com/markbates/goth/providers/salesforce"
|
||||
"github.com/markbates/goth/providers/shopify"
|
||||
"github.com/markbates/goth/providers/slack"
|
||||
"github.com/markbates/goth/providers/tumblr"
|
||||
"github.com/markbates/goth/providers/twitter"
|
||||
"github.com/markbates/goth/providers/yahoo"
|
||||
"github.com/markbates/goth/providers/yandex"
|
||||
"github.com/markbates/goth/providers/zoom"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GothIdProvider struct {
|
||||
Provider goth.Provider
|
||||
Session goth.Session
|
||||
}
|
||||
|
||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) *GothIdProvider {
|
||||
var idp GothIdProvider
|
||||
switch providerType {
|
||||
case "Amazon":
|
||||
idp = GothIdProvider{
|
||||
Provider: amazon.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &amazon.Session{},
|
||||
}
|
||||
case "Apple":
|
||||
idp = GothIdProvider{
|
||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
||||
Session: &apple.Session{},
|
||||
}
|
||||
case "AzureAD":
|
||||
idp = GothIdProvider{
|
||||
Provider: azuread.New(clientId, clientSecret, redirectUrl, nil),
|
||||
Session: &azuread.Session{},
|
||||
}
|
||||
case "Bitbucket":
|
||||
idp = GothIdProvider{
|
||||
Provider: bitbucket.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &bitbucket.Session{},
|
||||
}
|
||||
case "DigitalOcean":
|
||||
idp = GothIdProvider{
|
||||
Provider: digitalocean.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &digitalocean.Session{},
|
||||
}
|
||||
case "Discord":
|
||||
idp = GothIdProvider{
|
||||
Provider: discord.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &discord.Session{},
|
||||
}
|
||||
case "Dropbox":
|
||||
idp = GothIdProvider{
|
||||
Provider: dropbox.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &dropbox.Session{},
|
||||
}
|
||||
case "Facebook":
|
||||
idp = GothIdProvider{
|
||||
Provider: facebook.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &facebook.Session{},
|
||||
}
|
||||
case "Gitea":
|
||||
idp = GothIdProvider{
|
||||
Provider: gitea.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &gitea.Session{},
|
||||
}
|
||||
case "GitHub":
|
||||
idp = GothIdProvider{
|
||||
Provider: github.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &github.Session{},
|
||||
}
|
||||
case "GitLab":
|
||||
idp = GothIdProvider{
|
||||
Provider: gitlab.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &gitlab.Session{},
|
||||
}
|
||||
case "Google":
|
||||
idp = GothIdProvider{
|
||||
Provider: google.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &google.Session{},
|
||||
}
|
||||
case "Heroku":
|
||||
idp = GothIdProvider{
|
||||
Provider: heroku.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &heroku.Session{},
|
||||
}
|
||||
case "Instagram":
|
||||
idp = GothIdProvider{
|
||||
Provider: instagram.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &instagram.Session{},
|
||||
}
|
||||
case "Kakao":
|
||||
idp = GothIdProvider{
|
||||
Provider: kakao.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &kakao.Session{},
|
||||
}
|
||||
case "Linkedin":
|
||||
idp = GothIdProvider{
|
||||
Provider: linkedin.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &linkedin.Session{},
|
||||
}
|
||||
case "Line":
|
||||
idp = GothIdProvider{
|
||||
Provider: line.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &line.Session{},
|
||||
}
|
||||
case "MicrosoftOnline":
|
||||
idp = GothIdProvider{
|
||||
Provider: microsoftonline.New(clientId, clientSecret, redirectUrl),
|
||||
Session: µsoftonline.Session{},
|
||||
}
|
||||
case "Paypal":
|
||||
idp = GothIdProvider{
|
||||
Provider: paypal.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &paypal.Session{},
|
||||
}
|
||||
case "SalesForce":
|
||||
idp = GothIdProvider{
|
||||
Provider: salesforce.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &salesforce.Session{},
|
||||
}
|
||||
case "Shopify":
|
||||
idp = GothIdProvider{
|
||||
Provider: shopify.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &shopify.Session{},
|
||||
}
|
||||
case "Slack":
|
||||
idp = GothIdProvider{
|
||||
Provider: slack.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &slack.Session{},
|
||||
}
|
||||
case "Tumblr":
|
||||
idp = GothIdProvider{
|
||||
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &tumblr.Session{},
|
||||
}
|
||||
case "Twitter":
|
||||
idp = GothIdProvider{
|
||||
Provider: twitter.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &twitter.Session{},
|
||||
}
|
||||
case "Yahoo":
|
||||
idp = GothIdProvider{
|
||||
Provider: yahoo.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &yahoo.Session{},
|
||||
}
|
||||
case "Yandex":
|
||||
idp = GothIdProvider{
|
||||
Provider: yandex.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &yandex.Session{},
|
||||
}
|
||||
case "Zoom":
|
||||
idp = GothIdProvider{
|
||||
Provider: zoom.New(clientId, clientSecret, redirectUrl),
|
||||
Session: &zoom.Session{},
|
||||
}
|
||||
}
|
||||
|
||||
return &idp
|
||||
}
|
||||
|
||||
//Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
|
||||
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
|
||||
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
|
||||
idpClient.Set(reflect.ValueOf(client))
|
||||
}
|
||||
|
||||
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
var expireAt time.Time
|
||||
//Need to construct variables supported by goth
|
||||
//to call the function to obtain accessToken
|
||||
value := url.Values{}
|
||||
value.Add("code", code)
|
||||
accessToken, err := idp.Session.Authorize(idp.Provider, value)
|
||||
//Get ExpiresAt's value
|
||||
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
|
||||
if valueOfExpire.IsValid() {
|
||||
expireAt = valueOfExpire.Interface().(time.Time)
|
||||
}
|
||||
token := oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
Expiry: expireAt,
|
||||
}
|
||||
return &token, err
|
||||
}
|
||||
|
||||
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
gothUser, err := idp.Provider.FetchUser(idp.Session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getUser(gothUser), nil
|
||||
}
|
||||
|
||||
func getUser(gothUser goth.User) *UserInfo {
|
||||
user := UserInfo{
|
||||
Id: gothUser.UserID,
|
||||
Username: gothUser.Name,
|
||||
DisplayName: gothUser.NickName,
|
||||
Email: gothUser.Email,
|
||||
AvatarUrl: gothUser.AvatarURL,
|
||||
}
|
||||
//Some idp return an empty Name
|
||||
//so construct the Name with firstname and lastname or nickname
|
||||
if user.Username == "" {
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.Username = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.Username = gothUser.NickName
|
||||
}
|
||||
}
|
||||
if user.DisplayName == "" {
|
||||
if gothUser.FirstName != "" && gothUser.LastName != "" {
|
||||
user.DisplayName = getName(gothUser.FirstName, gothUser.LastName)
|
||||
} else {
|
||||
user.DisplayName = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func getName(firstName, lastName string) string {
|
||||
if util.IsChinese(firstName) || util.IsChinese(lastName) {
|
||||
return fmt.Sprintf("%s%s", lastName, firstName)
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s", firstName, lastName)
|
||||
}
|
||||
}
|
||||
215
idp/lark.go
Normal file
215
idp/lark.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type LarkIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewLarkIdProvider(clientId string, clientSecret string, redirectUrl string) *LarkIdProvider {
|
||||
idp := &LarkIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *LarkIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *LarkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
|
||||
"expire": 7140
|
||||
}
|
||||
*/
|
||||
|
||||
type LarkAccessToken struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
TenantAccessToken string `json:"tenant_access_token"`
|
||||
Expire int `json:"expire"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
|
||||
func (idp *LarkIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := &struct {
|
||||
AppID string `json:"app_id"`
|
||||
AppSecret string `json:"app_secret"`
|
||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
|
||||
|
||||
appToken := &LarkAccessToken{}
|
||||
if err = json.Unmarshal(data, appToken); err != nil || appToken.Code != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &oauth2.Token{
|
||||
AccessToken: appToken.TenantAccessToken,
|
||||
TokenType: "Bearer",
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(appToken.Expire), 0),
|
||||
}
|
||||
|
||||
raw := make(map[string]interface{})
|
||||
raw["code"] = code
|
||||
t = t.WithExtra(raw)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"access_token": "u-6U1SbDiM6XIH2DcTCPyeub",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 7140,
|
||||
"name": "zhangsan",
|
||||
"en_name": "Three Zhang",
|
||||
"avatar_url": "www.feishu.cn/avatar/icon",
|
||||
"avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
|
||||
"avatar_middle": "www.feishu.cn/avatar/icon_middle",
|
||||
"avatar_big": "www.feishu.cn/avatar/icon_big",
|
||||
"open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
|
||||
"union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
|
||||
"email": "zhangsan@feishu.cn",
|
||||
"user_id": "5d9bdxxx",
|
||||
"mobile": "+86130002883xx",
|
||||
"tenant_key": "736588c92lxf175d",
|
||||
"refresh_expires_in": 2591940,
|
||||
"refresh_token": "ur-t9HHgRCjMqGqIU9v05Zhos"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type LarkUserInfo struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Name string `json:"name"`
|
||||
EnName string `json:"en_name"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
AvatarThumb string `json:"avatar_thumb"`
|
||||
AvatarMiddle string `json:"avatar_middle"`
|
||||
AvatarBig string `json:"avatar_big"`
|
||||
OpenId string `json:"open_id"`
|
||||
UnionId string `json:"union_id"`
|
||||
Email string `json:"email"`
|
||||
UserId string `json:"user_id"`
|
||||
Mobile string `json:"mobile"`
|
||||
TenantKey string `json:"tenant_key"`
|
||||
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// GetUserInfo use LarkAccessToken gotten before return LinkedInUserInfo
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
|
||||
func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
body := &struct {
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
}{"authorization_code", token.Extra("code").(string)}
|
||||
data, _ := json.Marshal(body)
|
||||
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
|
||||
|
||||
resp, err := idp.Client.Do(req)
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var larkUserInfo LarkUserInfo
|
||||
if err = json.Unmarshal(data, &larkUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: larkUserInfo.Data.OpenId,
|
||||
DisplayName: larkUserInfo.Data.EnName,
|
||||
Username: larkUserInfo.Data.Name,
|
||||
Email: larkUserInfo.Data.Email,
|
||||
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||
bs, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
330
idp/linkedin.go
Normal file
330
idp/linkedin.go
Normal file
@@ -0,0 +1,330 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type LinkedInIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewLinkedInIdProvider(clientId string, clientSecret string, redirectUrl string) *LinkedInIdProvider {
|
||||
idp := &LinkedInIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://www.linkedIn.com/oauth/v2/accessToken",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email,public_profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type LinkedInAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
|
||||
func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
bs, _ := json.Marshal(params.Encode())
|
||||
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenResp := LinkedInAccessToken{}
|
||||
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
TokenType: "Bearer",
|
||||
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"firstName": {
|
||||
"localized": {
|
||||
"zh_CN": "继坤"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"localized": {
|
||||
"zh_CN": "刘"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"profilePicture": {
|
||||
"displayImage": "urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg",
|
||||
"displayImage~": {
|
||||
"paging": {
|
||||
"count": 10,
|
||||
"start": 0,
|
||||
"links": []
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"artifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100)",
|
||||
"authorizationMethod": "PUBLIC",
|
||||
"data": {
|
||||
"com.linkedin.digitalmedia.mediaartifact.StillImage": {
|
||||
"mediaType": "image/jpeg",
|
||||
"rawCodecSpec": {
|
||||
"name": "jpeg",
|
||||
"type": "image"
|
||||
},
|
||||
"displaySize": {
|
||||
"width": 100.0,
|
||||
"uom": "PX",
|
||||
"height": 100.0
|
||||
},
|
||||
"storageSize": {
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"storageAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
},
|
||||
"displayAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
}
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"identifier": "https://media.licdn.cn/dms/image/C5603AQHbdR8RkG62yg/profile-displayphoto-shrink_100_100/0/1625279434135?e=1630540800&v=beta&t=Z-bQKf_jFv8L1uwr6X5AJLoTQRWZrueT7qrITDSvxWM",
|
||||
"index": 0,
|
||||
"mediaType": "image/jpeg",
|
||||
"file": "urn:li:digitalmediaFile:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100,0)",
|
||||
"identifierType": "EXTERNAL_URL",
|
||||
"identifierExpiresInSeconds": 1630540800
|
||||
}
|
||||
]
|
||||
},
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "vvMfLsLIRs"
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserInfo struct {
|
||||
FirstName struct {
|
||||
Localized map[string]string `json:"localized"`
|
||||
PreferredLocale struct {
|
||||
Country string `json:"country"`
|
||||
Language string `json:"language"`
|
||||
} `json:"preferredLocale"`
|
||||
} `json:"firstName"`
|
||||
LastName struct {
|
||||
Localized map[string]string `json:"localized"`
|
||||
PreferredLocale struct {
|
||||
Country string `json:"country"`
|
||||
Language string `json:"language"`
|
||||
} `json:"preferredLocale"`
|
||||
} `json:"lastName"`
|
||||
ProfilePicture struct {
|
||||
DisplayImage string `json:"displayImage"`
|
||||
DisplayImage1 struct {
|
||||
Paging struct {
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Links []interface{} `json:"links"`
|
||||
} `json:"paging"`
|
||||
Elements []struct {
|
||||
Artifact string `json:"artifact"`
|
||||
AuthorizationMethod string `json:"authorizationMethod"`
|
||||
Data struct {
|
||||
ComLinkedinDigitalmediaMediaartifactStillImage struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
RawCodecSpec struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
} `json:"rawCodecSpec"`
|
||||
DisplaySize struct {
|
||||
Width float64 `json:"width"`
|
||||
Uom string `json:"uom"`
|
||||
Height float64 `json:"height"`
|
||||
} `json:"displaySize"`
|
||||
StorageSize struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
} `json:"storageSize"`
|
||||
StorageAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"storageAspectRatio"`
|
||||
DisplayAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"displayAspectRatio"`
|
||||
} `json:"com.linkedin.digitalmedia.mediaartifact.StillImage"`
|
||||
} `json:"data"`
|
||||
Identifiers []struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Index int `json:"index"`
|
||||
MediaType string `json:"mediaType"`
|
||||
File string `json:"file"`
|
||||
IdentifierType string `json:"identifierType"`
|
||||
IdentifierExpiresInSeconds int `json:"identifierExpiresInSeconds"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"elements"`
|
||||
} `json:"displayImage~"`
|
||||
} `json:"profilePicture"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"handle": "urn:li:emailAddress:3775708763",
|
||||
"handle~": {
|
||||
"emailAddress": "hsimpson@linkedin.com"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserEmail struct {
|
||||
Elements []struct {
|
||||
Handle struct {
|
||||
EmailAddress string `json:"emailAddress"`
|
||||
} `json:"handle~"`
|
||||
Handle1 string `json:"handle"`
|
||||
} `json:"elements"`
|
||||
}
|
||||
|
||||
// GetUserInfo use LinkedInAccessToken gotten before return LinkedInUserInfo
|
||||
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
|
||||
func (idp *LinkedInIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var linkedInUserInfo LinkedInUserInfo
|
||||
bs, err := idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var linkedInUserEmail LinkedInUserEmail
|
||||
bs, err = idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/emailAddress?q=members&projection=(elements*(handle~))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserEmail); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username := ""
|
||||
for _, name := range linkedInUserInfo.FirstName.Localized {
|
||||
username += name
|
||||
}
|
||||
for _, name := range linkedInUserInfo.LastName.Localized {
|
||||
username += name
|
||||
}
|
||||
userInfo := UserInfo{
|
||||
Id: linkedInUserInfo.Id,
|
||||
DisplayName: username,
|
||||
Username: username,
|
||||
Email: linkedInUserEmail.Elements[0].Handle.EmailAddress,
|
||||
AvatarUrl: linkedInUserInfo.ProfilePicture.DisplayImage1.Elements[0].Identifiers[0].Identifier,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([]byte, error) {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
87
idp/provider.go
Normal file
87
idp/provider.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
Id string
|
||||
Username string
|
||||
DisplayName string
|
||||
Email string
|
||||
AvatarUrl string
|
||||
}
|
||||
|
||||
type IdProvider interface {
|
||||
SetHttpClient(client *http.Client)
|
||||
GetToken(code string) (*oauth2.Token, error)
|
||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
|
||||
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, redirectUrl string) IdProvider {
|
||||
if typ == "GitHub" {
|
||||
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Google" {
|
||||
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "QQ" {
|
||||
return NewQqIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "WeChat" {
|
||||
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Facebook" {
|
||||
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "DingTalk" {
|
||||
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Weibo" {
|
||||
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Gitee" {
|
||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "LinkedIn" {
|
||||
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "WeCom" {
|
||||
if subType == "Internal" {
|
||||
return NewWeComInternalIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if subType == "Third-party" {
|
||||
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else if typ == "Lark" {
|
||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "GitLab" {
|
||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if typ == "Baidu" {
|
||||
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if isGothSupport(typ) {
|
||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var gothList = []string{"Apple", "AzureAd", "Slack"}
|
||||
|
||||
func isGothSupport(provider string) bool {
|
||||
for _, value := range gothList {
|
||||
if strings.EqualFold(value, provider) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
185
idp/qq.go
Normal file
185
idp/qq.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type QqIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewQqIdProvider(clientId string, clientSecret string, redirectUrl string) *QqIdProvider {
|
||||
idp := &QqIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.qq.com/oauth2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"get_user_info"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/token?%s", params.Encode())
|
||||
resp, err := idp.Client.Get(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
tokenContent, err := io.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("token=(.*?)&")
|
||||
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
||||
accessToken := matched[0][1]
|
||||
token := &oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "ret": 0,
|
||||
// "msg": "",
|
||||
// "is_lost": 0,
|
||||
// "nickname": "飞翔的企鹅",
|
||||
// "gender": "男",
|
||||
// "gender_type": 1,
|
||||
// "province": "",
|
||||
// "city": "安道尔城",
|
||||
// "year": "1968",
|
||||
// "constellation": "",
|
||||
// "figureurl": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/30",
|
||||
// "figureurl_1": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/50",
|
||||
// "figureurl_2": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/100",
|
||||
// "figureurl_qq_1": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=40&t=1557635654",
|
||||
// "figureurl_qq_2": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=100&t=1557635654",
|
||||
// "figureurl_qq": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=640&t=1557635654",
|
||||
// "figureurl_type": "1",
|
||||
// "is_yellow_vip": "0",
|
||||
// "vip": "0",
|
||||
// "yellow_vip_level": "0",
|
||||
// "level": "0",
|
||||
// "is_yellow_year_vip": "0"
|
||||
//}
|
||||
|
||||
type QqUserInfo struct {
|
||||
Ret int `json:"ret"`
|
||||
Msg string `json:"msg"`
|
||||
IsLost int `json:"is_lost"`
|
||||
Nickname string `json:"nickname"`
|
||||
Gender string `json:"gender"`
|
||||
GenderType int `json:"gender_type"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Year string `json:"year"`
|
||||
Constellation string `json:"constellation"`
|
||||
Figureurl string `json:"figureurl"`
|
||||
Figureurl1 string `json:"figureurl_1"`
|
||||
Figureurl2 string `json:"figureurl_2"`
|
||||
FigureurlQq1 string `json:"figureurl_qq_1"`
|
||||
FigureurlQq2 string `json:"figureurl_qq_2"`
|
||||
FigureurlQq string `json:"figureurl_qq"`
|
||||
FigureurlType string `json:"figureurl_type"`
|
||||
IsYellowVip string `json:"is_yellow_vip"`
|
||||
Vip string `json:"vip"`
|
||||
YellowVipLevel string `json:"yellow_vip_level"`
|
||||
Level string `json:"level"`
|
||||
IsYellowYearVip string `json:"is_yellow_year_vip"`
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
openIdUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/me?access_token=%s", token.AccessToken)
|
||||
resp, err := idp.Client.Get(openIdUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
openIdBody, err := io.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
||||
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
||||
openId := matched[0][1]
|
||||
if openId == "" {
|
||||
return nil, errors.New("openId is empty")
|
||||
}
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s", token.AccessToken, idp.Config.ClientID, openId)
|
||||
resp, err = idp.Client.Get(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
userInfoBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qqUserInfo QqUserInfo
|
||||
err = json.Unmarshal(userInfoBody, &qqUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if qqUserInfo.Ret != 0 {
|
||||
return nil, fmt.Errorf("ret expected 0, got %d", qqUserInfo.Ret)
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: openId,
|
||||
DisplayName: qqUserInfo.Nickname,
|
||||
AvatarUrl: qqUserInfo.FigureurlQq1,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
187
idp/wechat.go
Normal file
187
idp/wechat.go
Normal file
@@ -0,0 +1,187 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeChatIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeChatIdProvider(clientId string, clientSecret string, redirectUrl string) *WeChatIdProvider {
|
||||
idp := &WeChatIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeChatIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeChatIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.qq.com/oauth2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"snsapi_login"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WechatAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
RefreshToken string `json:"refresh_token"` //User refresh access_token
|
||||
Openid string `json:"openid"` //Unique ID of authorized user
|
||||
Scope string `json:"scope"` //The scope of user authorization, separated by commas. (,)
|
||||
Unionid string `json:"unionid"` //This field will appear if and only if the website application has been authorized by the user's UserInfo.
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
func (idp *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("appid", idp.Config.ClientID)
|
||||
params.Add("secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?%s", params.Encode())
|
||||
tokenResponse, err := idp.Client.Get(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(tokenResponse.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(tokenResponse.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wechatAccessToken WechatAccessToken
|
||||
if err = json.Unmarshal(buf.Bytes(), &wechatAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: wechatAccessToken.AccessToken,
|
||||
TokenType: "WeChatAccessToken",
|
||||
RefreshToken: wechatAccessToken.RefreshToken,
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
|
||||
raw := make(map[string]string)
|
||||
raw["Openid"] = wechatAccessToken.Openid
|
||||
token.WithExtra(raw)
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "openid": "of_Hl5zVpyj0vwzIlAyIlnXe1234",
|
||||
// "nickname": "飞翔的企鹅",
|
||||
// "sex": 1,
|
||||
// "language": "zh_CN",
|
||||
// "city": "Shanghai",
|
||||
// "province": "Shanghai",
|
||||
// "country": "CN",
|
||||
// "headimgurl": "https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/Q0j4TwGTfTK6xc7vGca4KtibJib5dslRianc9VHt9k2N7fewYOl8fak7grRM7nS5V6HcvkkIkGThWUXPjDbXkQFYA\/132",
|
||||
// "privilege": [],
|
||||
// "unionid": "oxW9O1VAL8x-zfWP2hrqW9c81234"
|
||||
//}
|
||||
|
||||
type WechatUserInfo struct {
|
||||
Openid string `json:"openid"` // The ID of an ordinary user, which is unique to the current developer account
|
||||
Nickname string `json:"nickname"` // Ordinary user nickname
|
||||
Sex int `json:"sex"` // Ordinary user gender, 1 is male, 2 is female
|
||||
Language string `json:"language"`
|
||||
City string `json:"city"` // City filled in by general user's personal data
|
||||
Province string `json:"province"` // Province filled in by ordinary user's personal information
|
||||
Country string `json:"country"` // Country, such as China is CN
|
||||
Headimgurl string `json:"headimgurl"` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have a avatar
|
||||
Privilege []string `json:"privilege"` // User Privilege information, json array, such as Wechat Woka user (chinaunicom)
|
||||
Unionid string `json:"unionid"` // Unified user identification. For an application under a WeChat open platform account, the unionid of the same user is unique.
|
||||
}
|
||||
|
||||
// GetUserInfo use WechatAccessToken gotten before return WechatUserInfo
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
|
||||
func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var wechatUserInfo WechatUserInfo
|
||||
accessToken := token.AccessToken
|
||||
openid := token.Extra("Openid")
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid)
|
||||
resp, err := idp.Client.Get(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(buf.Bytes(), &wechatUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := wechatUserInfo.Unionid
|
||||
if id == "" {
|
||||
id = wechatUserInfo.Openid
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: id,
|
||||
Username: wechatUserInfo.Nickname,
|
||||
DisplayName: wechatUserInfo.Nickname,
|
||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
171
idp/wecom_internal.go
Normal file
171
idp/wecom_internal.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
//This idp is using wecom internal application api as idp
|
||||
type WeComInternalIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComInternalIdProvider {
|
||||
idp := &WeComInternalIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeComInternalIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *WeComInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var config = &oauth2.Config{
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WecomInterToken struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developer.work.weixin.qq.com/document/path/91039
|
||||
func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
pTokenParams := &struct {
|
||||
CorpId string `json:"corpid"`
|
||||
Corpsecret string `json:"corpsecret"`
|
||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", pTokenParams.CorpId, pTokenParams.Corpsecret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pToken := &WecomInterToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pToken.Errcode != 0 {
|
||||
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.AccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
raw := make(map[string]interface{})
|
||||
raw["code"] = code
|
||||
token = token.WithExtra(raw)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
type WecomInternalUserResp struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
UserId string `json:"UserId"`
|
||||
OpenId string `json:"OpenId"`
|
||||
}
|
||||
|
||||
type WecomInternalUserInfo struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
OpenId string `json:"open_userid"`
|
||||
}
|
||||
|
||||
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
//Get userid first
|
||||
accessToken := token.AccessToken
|
||||
code := token.Extra("code").(string)
|
||||
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", accessToken, code))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userResp := &WecomInternalUserResp{}
|
||||
err = json.Unmarshal(data, userResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userResp.Errcode != 0 {
|
||||
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
|
||||
}
|
||||
if userResp.OpenId != "" {
|
||||
return nil, fmt.Errorf("not an internal user")
|
||||
}
|
||||
//Use userid and accesstoken to get user information
|
||||
resp, err = idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infoResp := &WecomInternalUserInfo{}
|
||||
err = json.Unmarshal(data, infoResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if infoResp.Errcode != 0 {
|
||||
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
|
||||
}
|
||||
userInfo := UserInfo{
|
||||
Id: infoResp.OpenId,
|
||||
Username: infoResp.Name,
|
||||
DisplayName: infoResp.Name,
|
||||
Email: infoResp.Email,
|
||||
AvatarUrl: infoResp.Avatar,
|
||||
}
|
||||
|
||||
if userInfo.Id == "" {
|
||||
userInfo.Id = userInfo.Username
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
209
idp/wecom_third_party.go
Normal file
209
idp/wecom_third_party.go
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeComIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComIdProvider {
|
||||
idp := &WeComIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeComIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeComIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.qq.com/oauth2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"snsapi_login"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WeComProviderToken struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
ProviderAccessToken string `json:"provider_access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
func (idp *WeComIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
pTokenParams := &struct {
|
||||
CorpId string `json:"corpid"`
|
||||
ProviderSecret string `json:"provider_secret"`
|
||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
|
||||
|
||||
pToken := &WeComProviderToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pToken.Errcode != 0 {
|
||||
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.ProviderAccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
raw := make(map[string]interface{})
|
||||
raw["code"] = code
|
||||
token = token.WithExtra(raw)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"errcode":0,
|
||||
"errmsg":"ok",
|
||||
"usertype": 1,
|
||||
"user_info":{
|
||||
"userid":"xxxx",
|
||||
"open_userid":"xxx",
|
||||
"name":"xxxx",
|
||||
"avatar":"xxxx"
|
||||
},
|
||||
"corp_info":{
|
||||
"corpid":"wxCorpId",
|
||||
},
|
||||
"agent":[
|
||||
{"agentid":0,"auth_type":1},
|
||||
{"agentid":1,"auth_type":1},
|
||||
{"agentid":2,"auth_type":1}
|
||||
],
|
||||
"auth_info":{
|
||||
"department":[
|
||||
{
|
||||
"id":2,
|
||||
"writable":true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type WeComUserInfo struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Usertype int `json:"usertype"`
|
||||
UserInfo struct {
|
||||
Userid string `json:"userid"`
|
||||
OpenUserid string `json:"open_userid"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
} `json:"user_info"`
|
||||
CorpInfo struct {
|
||||
Corpid string `json:"corpid"`
|
||||
} `json:"corp_info"`
|
||||
Agent []struct {
|
||||
Agentid int `json:"agentid"`
|
||||
AuthType int `json:"auth_type"`
|
||||
} `json:"agent"`
|
||||
AuthInfo struct {
|
||||
Department []struct {
|
||||
Id int `json:"id"`
|
||||
Writable bool `json:"writable"`
|
||||
} `json:"department"`
|
||||
} `json:"auth_info"`
|
||||
}
|
||||
|
||||
// GetUserInfo use WeComProviderToken gotten before return WeComUserInfo
|
||||
// get more detail via: https://work.weixin.qq.com/api/doc/90001/90143/91125
|
||||
func (idp *WeComIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
accessToken := token.AccessToken
|
||||
code := token.Extra("code").(string)
|
||||
|
||||
requestBody := &struct {
|
||||
AuthCode string `json:"auth_code"`
|
||||
}{code}
|
||||
data, err := idp.postWithBody(requestBody, fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=%s", accessToken))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wecomUserInfo := WeComUserInfo{}
|
||||
err = json.Unmarshal(data, &wecomUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wecomUserInfo.Errcode != 0 {
|
||||
return nil, fmt.Errorf("wecomUserInfo.Errcode = %d, wecomUserInfo.Errmsg = %s", wecomUserInfo.Errcode, wecomUserInfo.Errmsg)
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: wecomUserInfo.UserInfo.OpenUserid,
|
||||
Username: wecomUserInfo.UserInfo.Name,
|
||||
DisplayName: wecomUserInfo.UserInfo.Name,
|
||||
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||
bs, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
266
idp/weibo.go
Normal file
266
idp/weibo.go
Normal file
@@ -0,0 +1,266 @@
|
||||
// Copyright 2021 The casbin 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 idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeiBoIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeiBoIdProvider(clientId string, clientSecret string, redirectUrl string) *WeiBoIdProvider {
|
||||
idp := &WeiBoIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeiBoIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeiBoIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://api.weibo.com/oauth2/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{""},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WeiboAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RemindIn string `json:"remind_in"` // This parameter is about to be obsolete, developers please use expires_in
|
||||
Uid string `json:"uid"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
// accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, params)
|
||||
// resp, err := idp.GetUrlResp(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var weiboAccessToken WeiboAccessToken
|
||||
if err = json.Unmarshal(bs, &weiboAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: weiboAccessToken.AccessToken,
|
||||
TokenType: "WeiboAccessToken",
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(weiboAccessToken.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
idp.Config.Scopes[0] = weiboAccessToken.Uid
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id": 1404376560,
|
||||
"screen_name": "zaku",
|
||||
"name": "zaku",
|
||||
"province": "11",
|
||||
"city": "5",
|
||||
"location": "北京 朝阳区",
|
||||
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
|
||||
"url": "http://blog.sina.com.cn/zaku",
|
||||
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
|
||||
"domain": "zaku",
|
||||
"gender": "m",
|
||||
"followers_count": 1204,
|
||||
"friends_count": 447,
|
||||
"statuses_count": 2908,
|
||||
"favourites_count": 0,
|
||||
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
|
||||
"following": false,
|
||||
"allow_all_act_msg": false,
|
||||
"geo_enabled": true,
|
||||
"verified": false,
|
||||
"status": {
|
||||
"created_at": "Tue May 24 18:04:53 +0800 2011",
|
||||
"id": 11142488790,
|
||||
"text": "我的相机到了。",
|
||||
"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"in_reply_to_status_id": "",
|
||||
"in_reply_to_user_id": "",
|
||||
"in_reply_to_screen_name": "",
|
||||
"geo": null,
|
||||
"mid": "5610221544300749636",
|
||||
"annotations": [],
|
||||
"reposts_count": 5,
|
||||
"comments_count": 8
|
||||
},
|
||||
"allow_all_comment": true,
|
||||
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
|
||||
"verified_reason": "",
|
||||
"follow_me": false,
|
||||
"online_status": 0,
|
||||
"bi_followers_count": 215
|
||||
}
|
||||
*/
|
||||
|
||||
type WeiboUserinfo struct {
|
||||
Id int `json:"id"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
Name string `json:"name"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Location string `json:"location"`
|
||||
Description string `json:"description"`
|
||||
Url string `json:"url"`
|
||||
ProfileImageUrl string `json:"profile_image_url"`
|
||||
Domain string `json:"domain"`
|
||||
Gender string `json:"gender"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FriendsCount int `json:"friends_count"`
|
||||
StatusesCount int `json:"statuses_count"`
|
||||
FavouritesCount int `json:"favourites_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Following bool `json:"following"`
|
||||
AllowAllActMsg bool `json:"allow_all_act_msg"`
|
||||
GeoEnabled bool `json:"geo_enabled"`
|
||||
Verified bool `json:"verified"`
|
||||
Status struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Source string `json:"source"`
|
||||
Favorited bool `json:"favorited"`
|
||||
Truncated bool `json:"truncated"`
|
||||
InReplyToStatusId string `json:"in_reply_to_status_id"`
|
||||
InReplyToUserId string `json:"in_reply_to_user_id"`
|
||||
InReplyToScreenName string `json:"in_reply_to_screen_name"`
|
||||
Geo interface{} `json:"geo"`
|
||||
Mid string `json:"mid"`
|
||||
Annotations []interface{} `json:"annotations"`
|
||||
RepostsCount int `json:"reposts_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
} `json:"status"`
|
||||
AllowAllComment bool `json:"allow_all_comment"`
|
||||
AvatarLarge string `json:"avatar_large"`
|
||||
VerifiedReason string `json:"verified_reason"`
|
||||
FollowMe bool `json:"follow_me"`
|
||||
OnlineStatus int `json:"online_status"`
|
||||
BiFollowersCount int `json:"bi_followers_count"`
|
||||
}
|
||||
|
||||
// GetUserInfo use WeiboAccessToken gotten before return UserInfo
|
||||
// get more detail via: https://open.weibo.com/wiki/2/users/show
|
||||
func (idp *WeiBoIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var weiboUserInfo WeiboUserinfo
|
||||
accessToken := token.AccessToken
|
||||
uid := idp.Config.Scopes[0]
|
||||
id, _ := strconv.Atoi(uid)
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://api.weibo.com/2/users/show.json?access_token=%s&uid=%d", accessToken, id)
|
||||
resp, err := idp.GetUrlResp(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(resp), &weiboUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// weibo user email need to get separately through this url, need user authorization.
|
||||
e := struct {
|
||||
Email string `json:"email"`
|
||||
}{}
|
||||
emailUrl := fmt.Sprintf("https://api.weibo.com/2/account/profile/email.json?access_token=%s", accessToken)
|
||||
resp, err = idp.GetUrlResp(emailUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(resp), &e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(weiboUserInfo.Id),
|
||||
Username: weiboUserInfo.Name,
|
||||
DisplayName: weiboUserInfo.Name,
|
||||
AvatarUrl: weiboUserInfo.AvatarLarge,
|
||||
Email: e.Email,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *WeiBoIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// Config holds shared configuration on server.
|
||||
type Config struct {
|
||||
HTTPPort string
|
||||
DBDataSource string
|
||||
}
|
||||
|
||||
// NewConfig reads shared configuration from .env file.
|
||||
func NewConfig() (*Config, error) {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("godotenv.Load: %v", err)
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
DBDataSource: os.Getenv("DB_DATABASE_SOURCE"),
|
||||
HTTPPort: os.Getenv("HTTP_PORT"),
|
||||
}
|
||||
|
||||
if cfg.DBDataSource == "" {
|
||||
return nil, errors.New("DB_DATABASE_SOURCE is empty")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/handler/application"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/handler/user"
|
||||
"github.com/casdoor/casdoor/internal/store"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var corsConfig = cors.Config{
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
|
||||
AllowOrigins: []string{"http://localhost:3000"},
|
||||
MaxAge: 300,
|
||||
}
|
||||
|
||||
func New(userStore *store.UserStore, applicationStore *store.ApplicationStore) http.Handler {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
r.Use(cors.New(corsConfig))
|
||||
|
||||
//r.StaticFS("/", http.Dir("web/build/index.html"))
|
||||
|
||||
apiGroup := r.Group("/api")
|
||||
userHandler := user.New(userStore)
|
||||
apiGroup.GET("/get-users", userHandler.GetUsers)
|
||||
apiGroup.GET("/get-user", userHandler.GetUser)
|
||||
apiGroup.POST("/update-user", userHandler.UpdateUser)
|
||||
apiGroup.POST("/add-user", userHandler.AddUser)
|
||||
apiGroup.POST("/delete-user", userHandler.DeleteUser)
|
||||
|
||||
applicationHandler := application.New(applicationStore)
|
||||
apiGroup.GET("/applications", applicationHandler.List)
|
||||
apiGroup.POST("/applications", applicationHandler.Create)
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/object"
|
||||
"github.com/casdoor/casdoor/internal/store"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
applicationStore *store.ApplicationStore
|
||||
}
|
||||
|
||||
func New(applicationStore *store.ApplicationStore) *Handler {
|
||||
return &Handler{applicationStore: applicationStore}
|
||||
}
|
||||
|
||||
func (h *Handler) Create(g *gin.Context) {
|
||||
app := &object.Application{}
|
||||
err := g.BindJSON(app)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
app.Id = uuid.New().String()
|
||||
app.CreatedTime = time.Now().Format(time.RFC3339)
|
||||
app.CreatedBy = "TODO"
|
||||
err = h.applicationStore.Create(app)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) List(g *gin.Context) {
|
||||
limit, _ := strconv.Atoi(g.DefaultQuery("limit", "20"))
|
||||
offset, _ := strconv.Atoi(g.DefaultQuery("offset", "0"))
|
||||
apps, err := h.applicationStore.List(limit, offset)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
g.JSON(http.StatusOK, apps)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/object"
|
||||
"github.com/casdoor/casdoor/internal/store"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
userStore *store.UserStore
|
||||
}
|
||||
|
||||
func New(userStore *store.UserStore) *Handler {
|
||||
return &Handler{userStore: userStore}
|
||||
}
|
||||
|
||||
func (h *Handler) GetUsers(g *gin.Context) {
|
||||
owner := g.GetString("owner")
|
||||
user, err := h.userStore.GetUsers(owner)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
g.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
func (h *Handler) GetUser(g *gin.Context) {
|
||||
id := g.GetString("id")
|
||||
u, err := h.userStore.GetUser(id)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
g.JSON(http.StatusOK, u)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateUser(g *gin.Context) {
|
||||
id := g.GetString("id")
|
||||
|
||||
var user object.User
|
||||
err := g.BindJSON(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ok, err := h.userStore.UpdateUser(id, &user)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
g.JSON(http.StatusOK, ok)
|
||||
}
|
||||
|
||||
func (h *Handler) AddUser(g *gin.Context) {
|
||||
var user object.User
|
||||
err := g.BindJSON(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ok, err := h.userStore.AddUser(&user)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
g.JSON(http.StatusOK, ok)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteUser(g *gin.Context) {
|
||||
var user object.User
|
||||
err := g.BindJSON(&user)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
ok, err := h.userStore.DeleteUser(&user)
|
||||
if err != nil {
|
||||
_ = g.Error(err)
|
||||
return
|
||||
}
|
||||
g.JSON(http.StatusOK, ok)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package object
|
||||
|
||||
type Application struct {
|
||||
Id string `xorm:"notnull pk" json:"id"`
|
||||
Name string `xorm:"notnull pk" json:"name"`
|
||||
|
||||
CreatedTime string `json:"createdTime"`
|
||||
CreatedBy string `xorm:"notnull" json:"createdBy"`
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 store
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/internal/object"
|
||||
"github.com/casdoor/casdoor/internal/store/shared"
|
||||
)
|
||||
|
||||
type ApplicationStore struct {
|
||||
db *shared.DB
|
||||
}
|
||||
|
||||
func NewApplicationStore(db *shared.DB) *ApplicationStore {
|
||||
return &ApplicationStore{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ApplicationStore) Create(app *object.Application) error {
|
||||
_, err := a.db.GetEngine().Insert(app)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *ApplicationStore) List(limit, offset int) ([]*object.Application, error) {
|
||||
var apps []*object.Application
|
||||
err := a.db.GetEngine().Desc("created_time").Limit(limit, offset).Find(&apps)
|
||||
return apps, err
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/internal/config"
|
||||
"github.com/casdoor/casdoor/internal/object"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
engine *xorm.Engine
|
||||
driverName string
|
||||
dataSourceName string
|
||||
}
|
||||
|
||||
func NewDB(cfg *config.Config) (*DB, error) {
|
||||
db := &DB{
|
||||
dataSourceName: cfg.DBDataSource,
|
||||
driverName: "mysql",
|
||||
}
|
||||
engine, err := xorm.NewEngine(db.driverName, db.dataSourceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xorm.NewEngine: %v", err)
|
||||
}
|
||||
err = engine.Ping()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("engine.Ping(): %v", err)
|
||||
}
|
||||
|
||||
db.engine = engine
|
||||
|
||||
err = db.createTable()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("db.createTable(): %v", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetEngine() *xorm.Engine {
|
||||
return db.engine
|
||||
}
|
||||
|
||||
func (db *DB) createTable() error {
|
||||
err := db.engine.Sync2(new(object.User), new(object.Application))
|
||||
return err
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2020 The casbin 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 store
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/internal/object"
|
||||
"github.com/casdoor/casdoor/internal/store/shared"
|
||||
"github.com/casdoor/casdoor/pkg/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type UserStore struct {
|
||||
db *shared.DB
|
||||
}
|
||||
|
||||
func NewUserStore(db *shared.DB) *UserStore {
|
||||
return &UserStore{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserStore) GetUsers(owner string) ([]*object.User, error) {
|
||||
var users []*object.User
|
||||
err := u.db.GetEngine().Desc("created_time").Find(&users, &object.User{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *UserStore) getUser(owner string, name string) (*object.User, error) {
|
||||
user := object.User{Owner: owner, Name: name}
|
||||
existed, err := u.db.GetEngine().Get(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserStore) GetUser(id string) (*object.User, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return u.getUser(owner, name)
|
||||
}
|
||||
|
||||
func (u *UserStore) UpdateUser(id string, user *object.User) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
|
||||
data, err := u.getUser(owner, name)
|
||||
if err != nil || data == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = u.db.GetEngine().Id(core.PK{owner, name}).AllCols().Update(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (u *UserStore) AddUser(user *object.User) (bool, error) {
|
||||
affected, err := u.db.GetEngine().Insert(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (u *UserStore) DeleteUser(user *object.User) (bool, error) {
|
||||
affected, err := u.db.GetEngine().Id(core.PK{user.Owner, user.Name}).Delete(&object.User{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
56
k8s.yaml
Normal file
56
k8s.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
# this is only an EXAMPLE of deploying casddor in kubernetes
|
||||
# please modify this file according to your requirements
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||
#namespace: casdoor
|
||||
name: casdoor-svc
|
||||
labels:
|
||||
app: casdoor
|
||||
spec:
|
||||
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this filed
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 8000
|
||||
selector:
|
||||
app: casdoor
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
|
||||
#namespace: casdoor
|
||||
name: casdoor-deployment
|
||||
labels:
|
||||
app: casdoor
|
||||
spec:
|
||||
#EDIT IT: if you don't use redis, casdoor should not have multiple replicas
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: casdoor
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: casdoor
|
||||
spec:
|
||||
containers:
|
||||
- name: casdoor-container
|
||||
image: casbin/casdoor:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
volumeMounts:
|
||||
# the mounted directory path in THE CONTAINER
|
||||
- mountPath: /conf
|
||||
name: conf
|
||||
env:
|
||||
- name: RUNNING_IN_DOCKER
|
||||
value: "true"
|
||||
#if you want to deploy this in real prod env, consider the config map
|
||||
volumes:
|
||||
- name: conf
|
||||
hostPath:
|
||||
#EDIT IT: the mounted directory path in THE HOST
|
||||
path: /conf
|
||||
72
main.go
Normal file
72
main.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 The casbin 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 main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
_ "github.com/casdoor/casdoor/routers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||
flag.Parse()
|
||||
object.InitAdapter(*createDatabase)
|
||||
object.InitDb()
|
||||
object.InitDefaultStorageProvider()
|
||||
object.InitLdapAutoSynchronizer()
|
||||
proxy.InitHttpClient()
|
||||
authz.InitAuthz()
|
||||
|
||||
go object.RunSyncUsersJob()
|
||||
|
||||
//beego.DelStaticPath("/static")
|
||||
beego.SetStaticPath("/static", "web/build/static")
|
||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||
beego.SetStaticPath("/swagger", "swagger")
|
||||
beego.SetStaticPath("/files", "files")
|
||||
// https://studygolang.com/articles/2303
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||
if beego.AppConfig.String("redisEndpoint") == "" {
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "file"
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
|
||||
} else {
|
||||
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
|
||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
|
||||
}
|
||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||
|
||||
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999,"perm":"0770"}`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//logs.SetLevel(logs.LevelInformational)
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
beego.Run()
|
||||
}
|
||||
214
object/adapter.go
Normal file
214
object/adapter.go
Normal file
@@ -0,0 +1,214 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"xorm.io/core"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var adapter *Adapter
|
||||
|
||||
func InitConfig() {
|
||||
err := beego.LoadAppConfig("ini", "../conf/app.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
InitAdapter(true)
|
||||
}
|
||||
|
||||
func InitAdapter(createDatabase bool) {
|
||||
|
||||
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
|
||||
if createDatabase {
|
||||
adapter.CreateDatabase()
|
||||
}
|
||||
adapter.createTable()
|
||||
}
|
||||
|
||||
// Adapter represents the MySQL adapter for policy storage.
|
||||
type Adapter struct {
|
||||
driverName string
|
||||
dataSourceName string
|
||||
dbName string
|
||||
Engine *xorm.Engine
|
||||
}
|
||||
|
||||
// finalizer is the destructor for Adapter.
|
||||
func finalizer(a *Adapter) {
|
||||
err := a.Engine.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewAdapter is the constructor for Adapter.
|
||||
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
|
||||
a := &Adapter{}
|
||||
a.driverName = driverName
|
||||
a.dataSourceName = dataSourceName
|
||||
a.dbName = dbName
|
||||
|
||||
// Open the DB, create it if not existed.
|
||||
a.open()
|
||||
|
||||
// Call the destructor when the object is released.
|
||||
runtime.SetFinalizer(a, finalizer)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) CreateDatabase() error {
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Adapter) open() {
|
||||
dataSourceName := a.dataSourceName + a.dbName
|
||||
if a.driverName != "mysql" {
|
||||
dataSourceName = a.dataSourceName
|
||||
}
|
||||
|
||||
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
a.Engine = engine
|
||||
}
|
||||
|
||||
func (a *Adapter) close() {
|
||||
_ = a.Engine.Close()
|
||||
a.Engine = nil
|
||||
}
|
||||
|
||||
func (a *Adapter) createTable() {
|
||||
showSql, _ := beego.AppConfig.Bool("showSql")
|
||||
a.Engine.ShowSQL(showSql)
|
||||
|
||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
a.Engine.SetTableMapper(tbMapper)
|
||||
|
||||
err := a.Engine.Sync2(new(Organization))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(User))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Role))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Permission))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Resource))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Token))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Record))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Syncer))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
|
||||
session := adapter.Engine.Prepare()
|
||||
if offset != -1 && limit != -1 {
|
||||
session.Limit(limit, offset)
|
||||
}
|
||||
if owner != "" {
|
||||
session = session.And("owner=?", owner)
|
||||
}
|
||||
if field != "" && value != "" {
|
||||
if filterField(field) {
|
||||
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
|
||||
}
|
||||
}
|
||||
if sortField == "" || sortOrder == "" {
|
||||
sortField = "created_time"
|
||||
}
|
||||
if sortOrder == "ascend" {
|
||||
session = session.Asc(util.SnakeString(sortField))
|
||||
} else {
|
||||
session = session.Desc(util.SnakeString(sortField))
|
||||
}
|
||||
return session
|
||||
}
|
||||
284
object/application.go
Normal file
284
object/application.go
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Logo string `xorm:"varchar(100)" json:"logo"`
|
||||
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
EnablePassword bool `json:"enablePassword"`
|
||||
EnableSignUp bool `json:"enableSignUp"`
|
||||
EnableSigninSession bool `json:"enableSigninSession"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||
ExpireInHours int `json:"expireInHours"`
|
||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
|
||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
|
||||
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
|
||||
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
|
||||
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
|
||||
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
|
||||
}
|
||||
|
||||
func GetApplicationCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetApplications(owner string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
|
||||
applications := []*Application{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&applications)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetApplicationsByOrganizationName(owner string, organization string) []*Application {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
func getProviderMap(owner string) map[string]*Provider {
|
||||
providers := GetProviders(owner)
|
||||
m := map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
//if provider.Category != "OAuth" {
|
||||
// continue
|
||||
//}
|
||||
|
||||
m[provider.Name] = GetMaskedProvider(provider)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func extendApplicationWithProviders(application *Application) {
|
||||
m := getProviderMap(application.Owner)
|
||||
for _, providerItem := range application.Providers {
|
||||
if provider, ok := m[providerItem.Name]; ok {
|
||||
providerItem.Provider = provider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extendApplicationWithOrg(application *Application) {
|
||||
organization := getOrganization(application.Owner, application.Organization)
|
||||
application.OrganizationObj = organization
|
||||
}
|
||||
|
||||
func getApplication(owner string, name string) *Application {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
application := Application{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByOrganizationName(organization string) *Application {
|
||||
application := Application{}
|
||||
existed, err := adapter.Engine.Where("organization=?", organization).Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByUser(user *User) *Application {
|
||||
if user.SignupApplication != "" {
|
||||
return getApplication("admin", user.SignupApplication)
|
||||
} else {
|
||||
return GetApplicationByOrganizationName(user.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByUserId(userId string) (*Application, *User) {
|
||||
var application *Application
|
||||
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
if owner == "app" {
|
||||
application = getApplication("admin", name)
|
||||
return application, nil
|
||||
}
|
||||
|
||||
user := GetUser(userId)
|
||||
application = GetApplicationByUser(user)
|
||||
|
||||
return application, user
|
||||
}
|
||||
|
||||
func GetApplicationByClientId(clientId string) *Application {
|
||||
application := Application{}
|
||||
existed, err := adapter.Engine.Where("client_id=?", clientId).Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplication(id string) *Application {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getApplication(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedApplication(application *Application, userId string) *Application {
|
||||
if isUserIdGlobalAdmin(userId) {
|
||||
return application
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if application.ClientSecret != "" {
|
||||
application.ClientSecret = "***"
|
||||
}
|
||||
return application
|
||||
}
|
||||
|
||||
func GetMaskedApplications(applications []*Application, userId string) []*Application {
|
||||
if isUserIdGlobalAdmin(userId) {
|
||||
return applications
|
||||
}
|
||||
|
||||
for _, application := range applications {
|
||||
application = GetMaskedApplication(application, userId)
|
||||
}
|
||||
return applications
|
||||
}
|
||||
|
||||
func UpdateApplication(id string, application *Application) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getApplication(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "app-built-in" {
|
||||
application.Name = name
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddApplication(application *Application) bool {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
application.ClientSecret = util.GenerateClientSecret()
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteApplication(application *Application) bool {
|
||||
if application.Name == "app-built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (application *Application) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
}
|
||||
111
object/application_item.go
Normal file
111
object/application_item.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
func (application *Application) GetProviderByCategory(category string) *Provider {
|
||||
providers := GetProviders(application.Owner)
|
||||
m := map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
if provider.Category != category {
|
||||
continue
|
||||
}
|
||||
|
||||
m[provider.Name] = provider
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
if provider, ok := m[providerItem.Name]; ok {
|
||||
return provider
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (application *Application) GetEmailProvider() *Provider {
|
||||
return application.GetProviderByCategory("Email")
|
||||
}
|
||||
|
||||
func (application *Application) GetSmsProvider() *Provider {
|
||||
return application.GetProviderByCategory("SMS")
|
||||
}
|
||||
|
||||
func (application *Application) GetStorageProvider() *Provider {
|
||||
return application.GetProviderByCategory("Storage")
|
||||
}
|
||||
|
||||
func (application *Application) getSignupItem(itemName string) *SignupItem {
|
||||
for _, signupItem := range application.SignupItems {
|
||||
if signupItem.Name == itemName {
|
||||
return signupItem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (application *Application) IsSignupItemVisible(itemName string) bool {
|
||||
signupItem := application.getSignupItem(itemName)
|
||||
if signupItem == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return signupItem.Visible
|
||||
}
|
||||
|
||||
func (application *Application) IsSignupItemRequired(itemName string) bool {
|
||||
signupItem := application.getSignupItem(itemName)
|
||||
if signupItem == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return signupItem.Required
|
||||
}
|
||||
|
||||
func (application *Application) GetSignupItemRule(itemName string) string {
|
||||
signupItem := application.getSignupItem(itemName)
|
||||
if signupItem == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return signupItem.Rule
|
||||
}
|
||||
|
||||
func (application *Application) getAllPromptedProviderItems() []*ProviderItem {
|
||||
res := []*ProviderItem{}
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.isProviderPrompted() {
|
||||
res = append(res, providerItem)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (application *Application) isAffiliationPrompted() bool {
|
||||
signupItem := application.getSignupItem("Affiliation")
|
||||
if signupItem == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return signupItem.Prompted
|
||||
}
|
||||
|
||||
func (application *Application) HasPromptPage() bool {
|
||||
providerItems := application.getAllPromptedProviderItems()
|
||||
if len(providerItems) != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return application.isAffiliationPrompted()
|
||||
}
|
||||
72
object/avatar.go
Normal file
72
object/avatar.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
var defaultStorageProvider *Provider = nil
|
||||
|
||||
func InitDefaultStorageProvider() {
|
||||
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
|
||||
if defaultStorageProviderStr != "" {
|
||||
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFile(url string) (*bytes.Buffer, error) {
|
||||
httpClient := proxy.GetHttpClient(url)
|
||||
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fileBuffer := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(fileBuffer, resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileBuffer, nil
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrl(organization string, username string, url string) string {
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
|
||||
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||
|
||||
fileBuffer, err := downloadFile(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, _, err = UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uploadedFileUrl
|
||||
}
|
||||
39
object/avatar_test.go
Normal file
39
object/avatar_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
func TestSyncPermanentAvatars(t *testing.T) {
|
||||
InitConfig()
|
||||
InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
users := GetGlobalUsers()
|
||||
for i, user := range users {
|
||||
if user.Avatar == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
updateUserColumn("permanent_avatar", user)
|
||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||
}
|
||||
}
|
||||
40
object/captcha.go
Normal file
40
object/captcha.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
)
|
||||
|
||||
func GetCaptcha() (string, []byte) {
|
||||
id := captcha.NewLen(5)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err := captcha.WriteImage(&buffer, id, 200, 80)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return id, buffer.Bytes()
|
||||
}
|
||||
|
||||
func VerifyCaptcha(id string, digits string) bool {
|
||||
res := captcha.VerifyString(id, digits)
|
||||
|
||||
return res
|
||||
}
|
||||
161
object/cert.go
Normal file
161
object/cert.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
CryptoAlgorithm string `xorm:"varchar(100)" json:"cryptoAlgorithm"`
|
||||
BitSize int `json:"bitSize"`
|
||||
ExpireInYears int `json:"expireInYears"`
|
||||
|
||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||
}
|
||||
|
||||
func GetMaskedCert(cert *Cert) *Cert {
|
||||
if cert == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func GetMaskedCerts(certs []*Cert) []*Cert {
|
||||
for _, cert := range certs {
|
||||
cert = GetMaskedCert(cert)
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetCertCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetCerts(owner string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
|
||||
certs := []*Cert{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&certs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return certs
|
||||
}
|
||||
|
||||
func getCert(owner string, name string) *Cert {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
cert := Cert{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &cert
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetCert(id string) *Cert {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getCert(owner, name)
|
||||
}
|
||||
|
||||
func UpdateCert(id string, cert *Cert) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getCert(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddCert(cert *Cert) bool {
|
||||
if cert.PublicKey == "" || cert.PrivateKey == "" {
|
||||
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
||||
cert.PublicKey = publicKey
|
||||
cert.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteCert(cert *Cert) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{cert.Owner, cert.Name}).Delete(&Cert{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Cert) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func getCertByApplication(application *Application) *Cert {
|
||||
if application.Cert != "" {
|
||||
return getCert("admin", application.Cert)
|
||||
} else {
|
||||
return GetDefaultCert()
|
||||
}
|
||||
}
|
||||
|
||||
func GetDefaultCert() *Cert {
|
||||
return getCert("admin", "cert-built-in")
|
||||
}
|
||||
189
object/check.go
Normal file
189
object/check.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
reWhiteSpace *regexp.Regexp
|
||||
reFieldWhiteList *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
reWhiteSpace, _ = regexp.Compile(`\s`)
|
||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
||||
if organization == nil {
|
||||
return "organization does not exist"
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Username") {
|
||||
if len(username) <= 1 {
|
||||
return "username must have at least 2 characters"
|
||||
} else if reWhiteSpace.MatchString(username) {
|
||||
return "username cannot contain white spaces"
|
||||
} else if HasUserByField(organization.Name, "name", username) {
|
||||
return "username already exists"
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) <= 5 {
|
||||
return "password must have at least 6 characters"
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
if email == "" {
|
||||
if application.IsSignupItemRequired("Email") {
|
||||
return "email cannot be empty"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
return "email already exists"
|
||||
} else if !util.IsEmailValid(email) {
|
||||
return "email is invalid"
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Phone") {
|
||||
if phone == "" {
|
||||
if application.IsSignupItemRequired("Phone") {
|
||||
return "phone cannot be empty"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
return "phone already exists"
|
||||
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
|
||||
return "phone number is invalid"
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if displayName == "" {
|
||||
return "displayName cannot be blank"
|
||||
} else if application.GetSignupItemRule("Display name") == "Personal" {
|
||||
if !isValidPersonalName(displayName) {
|
||||
return "displayName is not valid personal name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Affiliation") {
|
||||
if affiliation == "" {
|
||||
return "affiliation cannot be blank"
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string) string {
|
||||
organization := GetOrganizationByUser(user)
|
||||
if organization == nil {
|
||||
return "organization does not exist"
|
||||
}
|
||||
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||
return ""
|
||||
}
|
||||
return "password incorrect"
|
||||
} else {
|
||||
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
|
||||
}
|
||||
}
|
||||
|
||||
func checkLdapUserPassword(user *User, password string) (*User, string) {
|
||||
ldaps := GetLdaps(user.Owner)
|
||||
ldapLoginSuccess := false
|
||||
for _, ldapServer := range ldaps {
|
||||
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
|
||||
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, []string{}, nil)
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err.Error()
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
continue
|
||||
} else if len(searchResult.Entries) > 1 {
|
||||
return nil, "Error: multiple accounts with same uid, please check your ldap server"
|
||||
}
|
||||
|
||||
dn := searchResult.Entries[0].DN
|
||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||
ldapLoginSuccess = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
return nil, "ldap user name or password incorrect"
|
||||
}
|
||||
return user, ""
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string) (*User, string) {
|
||||
user := GetUserByFields(organization, username)
|
||||
if user == nil || user.IsDeleted == true {
|
||||
return nil, "the user does not exist, please sign up first"
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
return nil, "the user is forbidden to sign in, please contact the administrator"
|
||||
}
|
||||
//for ldap users
|
||||
if user.Ldap != "" {
|
||||
return checkLdapUserPassword(user, password)
|
||||
}
|
||||
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return user, ""
|
||||
}
|
||||
|
||||
func filterField(field string) bool {
|
||||
return reFieldWhiteList.MatchString(field)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 The casbin Authors. All Rights Reserved.
|
||||
// Copyright 2021 The casbin 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.
|
||||
@@ -12,18 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
import "regexp"
|
||||
|
||||
func GetOwnerAndNameFromId(id string) (string, string) {
|
||||
tokens := strings.Split(id, "/")
|
||||
if len(tokens) != 2 {
|
||||
panic(errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id))
|
||||
var rePersonalName *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tokens[0], tokens[1]
|
||||
}
|
||||
|
||||
func isValidPersonalName(s string) bool {
|
||||
return rePersonalName.MatchString(s)
|
||||
}
|
||||
31
object/email.go
Normal file
31
object/email.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 The casbin 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.
|
||||
|
||||
// modified from https://github.com/casbin/casnode/blob/master/service/mail.go
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/go-gomail/gomail"
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
message.SetAddressHeader("From", provider.ClientId, sender)
|
||||
message.SetHeader("To", dest)
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
return dialer.DialAndSend(message)
|
||||
}
|
||||
165
object/init.go
Normal file
165
object/init.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
//go:embed token_jwt_key.pem
|
||||
var tokenJwtPublicKey string
|
||||
|
||||
//go:embed token_jwt_key.key
|
||||
var tokenJwtPrivateKey string
|
||||
|
||||
func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInCert()
|
||||
initBuiltInLdap()
|
||||
}
|
||||
|
||||
func initBuiltInOrganization() {
|
||||
organization := getOrganization("admin", "built-in")
|
||||
if organization != nil {
|
||||
return
|
||||
}
|
||||
|
||||
organization = &Organization{
|
||||
Owner: "admin",
|
||||
Name: "built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
PasswordType: "plain",
|
||||
}
|
||||
AddOrganization(organization)
|
||||
}
|
||||
|
||||
func initBuiltInUser() {
|
||||
user := getUser("built-in", "admin")
|
||||
if user != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user = &User{
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ranking: 1,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
IsDeleted: false,
|
||||
SignupApplication: "built-in-app",
|
||||
CreatedIp: "127.0.0.1",
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
AddUser(user)
|
||||
}
|
||||
|
||||
func initBuiltInApplication() {
|
||||
application := getApplication("admin", "app-built-in")
|
||||
if application != nil {
|
||||
return
|
||||
}
|
||||
|
||||
application = &Application{
|
||||
Owner: "admin",
|
||||
Name: "app-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Casdoor",
|
||||
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||
HomepageUrl: "https://casdoor.org",
|
||||
Organization: "built-in",
|
||||
Cert: "cert-built-in",
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
Providers: []*ProviderItem{},
|
||||
SignupItems: []*SignupItem{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
|
||||
},
|
||||
RedirectUris: []string{},
|
||||
ExpireInHours: 168,
|
||||
}
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func initBuiltInCert() {
|
||||
cert := getCert("admin", "cert-built-in")
|
||||
if cert != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cert = &Cert{
|
||||
Owner: "admin",
|
||||
Name: "cert-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Cert",
|
||||
Scope: "JWT",
|
||||
Type: "x509",
|
||||
CryptoAlgorithm: "RSA",
|
||||
BitSize: 4096,
|
||||
ExpireInYears: 20,
|
||||
PublicKey: tokenJwtPublicKey,
|
||||
PrivateKey: tokenJwtPrivateKey,
|
||||
}
|
||||
AddCert(cert)
|
||||
}
|
||||
|
||||
func initBuiltInLdap() {
|
||||
ldap := GetLdap("ldap-built-in")
|
||||
if ldap != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ldap = &Ldap{
|
||||
Id: "ldap-built-in",
|
||||
Owner: "built-in",
|
||||
ServerName: "BuildIn LDAP Server",
|
||||
Host: "example.com",
|
||||
Port: 389,
|
||||
Admin: "cn=buildin,dc=example,dc=com",
|
||||
Passwd: "123",
|
||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||
AutoSync: 0,
|
||||
LastSync: "",
|
||||
}
|
||||
AddLdap(ldap)
|
||||
}
|
||||
402
object/ldap.go
Normal file
402
object/ldap.go
Normal file
@@ -0,0 +1,402 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
)
|
||||
|
||||
type Ldap struct {
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
|
||||
AutoSync int `json:"autoSync"`
|
||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||
}
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
//Gcn string
|
||||
Uuid string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
//GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
|
||||
returnAnyNotEmpty := func(strs ...string) string {
|
||||
for _, str := range strs {
|
||||
if str != "" {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
res := make([]LdapRespUser, 0)
|
||||
for _, user := range users {
|
||||
res = append(res, LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
Uuid: user.Uuid,
|
||||
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
|
||||
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
|
||||
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
|
||||
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(adminUser, adminPasswd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||
}
|
||||
|
||||
return &ldapConn{Conn: conn}, nil
|
||||
}
|
||||
|
||||
//FIXME: The Base DN does not necessarily contain the Group
|
||||
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
//}
|
||||
|
||||
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := l.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
var ldapUserItem ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
switch attribute.Name {
|
||||
case "uidNumber":
|
||||
ldapUserItem.UidNumber = attribute.Values[0]
|
||||
case "uid":
|
||||
ldapUserItem.Uid = attribute.Values[0]
|
||||
case "cn":
|
||||
ldapUserItem.Cn = attribute.Values[0]
|
||||
case "gidNumber":
|
||||
ldapUserItem.GidNumber = attribute.Values[0]
|
||||
case "entryUUID":
|
||||
ldapUserItem.Uuid = attribute.Values[0]
|
||||
case "mail":
|
||||
ldapUserItem.Mail = attribute.Values[0]
|
||||
case "email":
|
||||
ldapUserItem.Email = attribute.Values[0]
|
||||
case "emailAddress":
|
||||
ldapUserItem.EmailAddress = attribute.Values[0]
|
||||
case "telephoneNumber":
|
||||
ldapUserItem.TelephoneNumber = attribute.Values[0]
|
||||
case "mobile":
|
||||
ldapUserItem.Mobile = attribute.Values[0]
|
||||
case "mobileTelephoneNumber":
|
||||
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
|
||||
case "registeredAddress":
|
||||
ldapUserItem.RegisteredAddress = attribute.Values[0]
|
||||
case "postalAddress":
|
||||
ldapUserItem.PostalAddress = attribute.Values[0]
|
||||
}
|
||||
}
|
||||
ldapUsers = append(ldapUsers, ldapUserItem)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
func AddLdap(ldap *Ldap) bool {
|
||||
if len(ldap.Id) == 0 {
|
||||
ldap.Id = util.GenerateId()
|
||||
}
|
||||
|
||||
if len(ldap.CreatedTime) == 0 {
|
||||
ldap.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckLdapExist(ldap *Ldap) bool {
|
||||
var result []*Ldap
|
||||
err := adapter.Engine.Find(&result, &Ldap{
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Admin: ldap.Admin,
|
||||
Passwd: ldap.Passwd,
|
||||
BaseDn: ldap.BaseDn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetLdaps(owner string) []*Ldap {
|
||||
var ldaps []*Ldap
|
||||
err := adapter.Engine.Desc("created_time").Find(&ldaps, &Ldap{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ldaps
|
||||
}
|
||||
|
||||
func GetLdap(id string) *Ldap {
|
||||
if util.IsStrsEmpty(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ldap := Ldap{Id: id}
|
||||
existed, err := adapter.Engine.Get(&ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &ldap
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLdap(ldap *Ldap) bool {
|
||||
if GetLdap(ldap.Id) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteLdap(ldap *Ldap) bool {
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Delete(&Ldap{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range users {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
|
||||
for _, user := range users {
|
||||
found := false
|
||||
if len(existUuids) > 0 {
|
||||
for _, existUuid := range existUuids {
|
||||
if user.Uuid == existUuid {
|
||||
existUsers = append(existUsers, user)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found && !AddUser(&User{
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Password: "123",
|
||||
DisplayName: user.Cn,
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
var existUuids []string
|
||||
existUuidSet := make(map[string]struct{})
|
||||
|
||||
//whereStr := ""
|
||||
//for i, uuid := range uuids {
|
||||
// if i == 0 {
|
||||
// whereStr = fmt.Sprintf("'%s'", uuid)
|
||||
// } else {
|
||||
// whereStr = fmt.Sprintf(",'%s'", uuid)
|
||||
// }
|
||||
//}
|
||||
|
||||
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(results) > 0 {
|
||||
for _, result := range results {
|
||||
existUuidSet[result.Ldap] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for uuid, _ := range existUuidSet {
|
||||
existUuids = append(existUuids, uuid)
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func buildLdapUserName(uid, uidNum string) string {
|
||||
var result User
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
|
||||
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if result.Name == uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
||||
111
object/ldap_autosync.go
Normal file
111
object/ldap_autosync.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type LdapAutoSynchronizer struct {
|
||||
sync.Mutex
|
||||
ldapIdToStopChan map[string]chan struct{}
|
||||
}
|
||||
|
||||
var globalLdapAutoSynchronizer *LdapAutoSynchronizer
|
||||
|
||||
func InitLdapAutoSynchronizer() {
|
||||
globalLdapAutoSynchronizer = NewLdapAutoSynchronizer()
|
||||
globalLdapAutoSynchronizer.LdapAutoSynchronizerStartUpAll()
|
||||
}
|
||||
|
||||
func NewLdapAutoSynchronizer() *LdapAutoSynchronizer {
|
||||
return &LdapAutoSynchronizer{
|
||||
ldapIdToStopChan: make(map[string]chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func GetLdapAutoSynchronizer() *LdapAutoSynchronizer {
|
||||
return globalLdapAutoSynchronizer
|
||||
}
|
||||
|
||||
//start autosync for specified ldap, old existing autosync goroutine will be ceased
|
||||
func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
ldap := GetLdap(ldapId)
|
||||
if ldap == nil {
|
||||
return fmt.Errorf("ldap %s doesn't exist", ldapId)
|
||||
}
|
||||
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
|
||||
res <- struct{}{}
|
||||
delete(l.ldapIdToStopChan, ldapId)
|
||||
}
|
||||
|
||||
stopChan := make(chan struct{})
|
||||
l.ldapIdToStopChan[ldapId] = stopChan
|
||||
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
|
||||
go l.syncRoutine(ldap, stopChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LdapAutoSynchronizer) StopAutoSync(ldapId string) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
|
||||
res <- struct{}{}
|
||||
delete(l.ldapIdToStopChan, ldapId)
|
||||
}
|
||||
}
|
||||
|
||||
//autosync goroutine
|
||||
func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
||||
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
UpdateLdapSyncTime(ldap.Id)
|
||||
//fetch all users
|
||||
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
|
||||
if err != nil {
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap.BaseDn)
|
||||
if err != nil {
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
|
||||
if len(*failed) != 0 {
|
||||
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
|
||||
} else {
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
|
||||
}
|
||||
select {
|
||||
case <-stopChan:
|
||||
logs.Info(fmt.Sprintf("autoSync goroutine for %s stopped", ldap.Id))
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//start all autosync goroutine for existing ldap servers in each organizations
|
||||
func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
|
||||
organizations := []*Organization{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&organizations)
|
||||
if err != nil {
|
||||
logs.Info("failed to Star up LdapAutoSynchronizer; ")
|
||||
}
|
||||
for _, org := range organizations {
|
||||
for _, ldap := range GetLdaps(org.Name) {
|
||||
if ldap.AutoSync != 0 {
|
||||
l.StartAutoSync(ldap.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
object/oidc_discovery.go
Normal file
109
object/oidc_discovery.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type OidcDiscovery struct {
|
||||
Issuer string `json:"issuer"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||
JwksUri string `json:"jwks_uri"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||
ResponseModesSupported []string `json:"response_modes_supported"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||
SubjectTypesSupported []string `json:"subject_types_supported"`
|
||||
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
||||
ScopesSupported []string `json:"scopes_supported"`
|
||||
ClaimsSupported []string `json:"claims_supported"`
|
||||
RequestParameterSupported bool `json:"request_parameter_supported"`
|
||||
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
protocol := "https://"
|
||||
if strings.HasPrefix(host, "localhost") {
|
||||
protocol = "http://"
|
||||
}
|
||||
|
||||
if host == "localhost:8000" {
|
||||
return fmt.Sprintf("%s%s", protocol, "localhost:7001"), fmt.Sprintf("%s%s", protocol, "localhost:8000")
|
||||
} else {
|
||||
return fmt.Sprintf("%s%s", protocol, host), fmt.Sprintf("%s%s", protocol, host)
|
||||
}
|
||||
}
|
||||
|
||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
origin := beego.AppConfig.String("origin")
|
||||
if origin != "" {
|
||||
originFrontend = origin
|
||||
originBackend = origin
|
||||
}
|
||||
|
||||
// Examples:
|
||||
// https://login.okta.com/.well-known/openid-configuration
|
||||
// https://auth0.auth0.com/.well-known/openid-configuration
|
||||
// https://accounts.google.com/.well-known/openid-configuration
|
||||
// https://access.line.me/.well-known/openid-configuration
|
||||
oidcDiscovery := OidcDiscovery{
|
||||
Issuer: originFrontend,
|
||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||
JwksUri: fmt.Sprintf("%s/api/certs", originBackend),
|
||||
ResponseTypesSupported: []string{"id_token"},
|
||||
ResponseModesSupported: []string{"login", "code", "link"},
|
||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IdTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
||||
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"},
|
||||
RequestParameterSupported: true,
|
||||
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
|
||||
}
|
||||
|
||||
return oidcDiscovery
|
||||
}
|
||||
|
||||
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
cert := GetDefaultCert()
|
||||
|
||||
//follows the protocol rfc 7517(draft)
|
||||
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
||||
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
||||
certPemBlock := []byte(cert.PublicKey)
|
||||
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
||||
|
||||
var jwk jose.JSONWebKey
|
||||
jwk.Key = x509Cert.PublicKey
|
||||
jwk.Certificates = []*x509.Certificate{x509Cert}
|
||||
jwk.KeyID = cert.Name
|
||||
|
||||
var jwks jose.JSONWebKeySet
|
||||
jwks.Keys = []jose.JSONWebKey{jwk}
|
||||
return jwks, nil
|
||||
}
|
||||
169
object/organization.go
Normal file
169
object/organization.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
}
|
||||
|
||||
func GetOrganizationCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetOrganizations(owner string) []*Organization {
|
||||
organizations := []*Organization{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return organizations
|
||||
}
|
||||
|
||||
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization {
|
||||
organizations := []*Organization{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&organizations)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return organizations
|
||||
}
|
||||
|
||||
func getOrganization(owner string, name string) *Organization {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
organization := Organization{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &organization
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetOrganization(id string) *Organization {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getOrganization(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedOrganization(organization *Organization) *Organization {
|
||||
if organization == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if organization.MasterPassword != "" {
|
||||
organization.MasterPassword = "***"
|
||||
}
|
||||
return organization
|
||||
}
|
||||
|
||||
func GetMaskedOrganizations(organizations []*Organization) []*Organization {
|
||||
for _, organization := range organizations {
|
||||
organization = GetMaskedOrganization(organization)
|
||||
}
|
||||
return organizations
|
||||
}
|
||||
|
||||
func UpdateOrganization(id string, organization *Organization) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getOrganization(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if name == "built-in" {
|
||||
organization.Name = name
|
||||
}
|
||||
|
||||
if name != organization.Name {
|
||||
applications := GetApplicationsByOrganizationName("admin", name)
|
||||
for _, application := range applications {
|
||||
application.Organization = organization.Name
|
||||
UpdateApplication(application.GetId(), application)
|
||||
}
|
||||
}
|
||||
|
||||
if organization.MasterPassword != "" {
|
||||
credManager := cred.GetCredManager(organization.PasswordType)
|
||||
if credManager != nil {
|
||||
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
|
||||
organization.MasterPassword = hashedPassword
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddOrganization(organization *Organization) bool {
|
||||
affected, err := adapter.Engine.Insert(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteOrganization(organization *Organization) bool {
|
||||
if organization.Name == "built-in" {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetOrganizationByUser(user *User) *Organization {
|
||||
return getOrganization("admin", user.Owner)
|
||||
}
|
||||
129
object/permission.go
Normal file
129
object/permission.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Permission struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
|
||||
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
|
||||
Resources []string `xorm:"mediumtext" json:"resources"`
|
||||
Actions []string `xorm:"mediumtext" json:"actions"`
|
||||
Effect string `xorm:"varchar(100)" json:"effect"`
|
||||
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetPermissionCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Permission{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetPermissions(owner string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func GetPaginationPermissions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Permission {
|
||||
permissions := []*Permission{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
func getPermission(owner string, name string) *Permission {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
permission := Permission{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &permission
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetPermission(id string) *Permission {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getPermission(owner, name)
|
||||
}
|
||||
|
||||
func UpdatePermission(id string, permission *Permission) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getPermission(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddPermission(permission *Permission) bool {
|
||||
affected, err := adapter.Engine.Insert(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeletePermission(permission *Permission) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (permission *Permission) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", permission.Owner, permission.Name)
|
||||
}
|
||||
186
object/provider.go
Normal file
186
object/provider.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Content string `xorm:"varchar(1000)" json:"content"`
|
||||
|
||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||
SignName string `xorm:"varchar(100)" json:"signName"`
|
||||
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
|
||||
AppId string `xorm:"varchar(100)" json:"appId"`
|
||||
|
||||
Endpoint string `xorm:"varchar(1000)" json:"endpoint"`
|
||||
IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"`
|
||||
Domain string `xorm:"varchar(100)" json:"domain"`
|
||||
Bucket string `xorm:"varchar(100)" json:"bucket"`
|
||||
|
||||
Metadata string `xorm:"mediumtext" json:"metadata"`
|
||||
IdP string `xorm:"mediumtext" json:"idP"`
|
||||
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
|
||||
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
|
||||
|
||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||
}
|
||||
|
||||
func GetMaskedProvider(provider *Provider) *Provider {
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if provider.ClientSecret != "" {
|
||||
provider.ClientSecret = "***"
|
||||
}
|
||||
if provider.ClientSecret2 != "" {
|
||||
provider.ClientSecret2 = "***"
|
||||
}
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func GetMaskedProviders(providers []*Provider) []*Provider {
|
||||
for _, provider := range providers {
|
||||
provider = GetMaskedProvider(provider)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetProviderCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Provider{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetProviders(owner string) []*Provider {
|
||||
providers := []*Provider{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
|
||||
providers := []*Provider{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&providers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func getProvider(owner string, name string) *Provider {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
provider := Provider{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &provider
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetProvider(id string) *Provider {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getProvider(owner, name)
|
||||
}
|
||||
|
||||
func GetDefaultHumanCheckProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &provider
|
||||
}
|
||||
|
||||
func UpdateProvider(id string, provider *Provider) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getProvider(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) bool {
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteProvider(provider *Provider) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{provider.Owner, provider.Name}).Delete(&Provider{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (p *Provider) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
42
object/provider_item.go
Normal file
42
object/provider_item.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
type ProviderItem struct {
|
||||
Name string `json:"name"`
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
AlertType string `json:"alertType"`
|
||||
Provider *Provider `json:"provider"`
|
||||
}
|
||||
|
||||
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.Name == providerName {
|
||||
return providerItem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pi *ProviderItem) IsProviderVisible() bool {
|
||||
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
|
||||
}
|
||||
|
||||
func (pi *ProviderItem) isProviderPrompted() bool {
|
||||
return pi.IsProviderVisible() && pi.Prompted
|
||||
}
|
||||
174
object/record.go
Normal file
174
object/record.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
var logPostOnly bool
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
|
||||
if err != nil {
|
||||
//panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
|
||||
Owner string `xorm:"varchar(100) index" json:"owner"`
|
||||
Name string `xorm:"varchar(100) index" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||
Action string `xorm:"varchar(1000)" json:"action"`
|
||||
|
||||
ExtendedUser *User `xorm:"-" json:"extendedUser"`
|
||||
|
||||
IsTriggered bool `json:"isTriggered"`
|
||||
}
|
||||
|
||||
func NewRecord(ctx *context.Context) *Record {
|
||||
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
|
||||
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
|
||||
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
|
||||
if len(requestUri) > 1000 {
|
||||
requestUri = requestUri[0:1000]
|
||||
}
|
||||
|
||||
record := Record{
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
ClientIp: ip,
|
||||
User: "",
|
||||
Method: ctx.Request.Method,
|
||||
RequestUri: requestUri,
|
||||
Action: action,
|
||||
IsTriggered: false,
|
||||
}
|
||||
return &record
|
||||
}
|
||||
|
||||
func AddRecord(record *Record) bool {
|
||||
if logPostOnly {
|
||||
if record.Method == "GET" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if record.Organization == "app" {
|
||||
return false
|
||||
}
|
||||
|
||||
record.Owner = record.Organization
|
||||
|
||||
errWebhook := SendWebhooks(record)
|
||||
if errWebhook == nil {
|
||||
record.IsTriggered = true
|
||||
} else {
|
||||
fmt.Println(errWebhook)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetRecordCount(field, value string) int {
|
||||
session := GetSession("", -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Record{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetRecords() []*Record {
|
||||
records := []*Record{}
|
||||
err := adapter.Engine.Desc("id").Find(&records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string) []*Record {
|
||||
records := []*Record{}
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func GetRecordsByField(record *Record) []*Record {
|
||||
records := []*Record{}
|
||||
err := adapter.Engine.Find(&records, record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func SendWebhooks(record *Record) error {
|
||||
webhooks := getWebhooksByOrganization(record.Organization)
|
||||
for _, webhook := range webhooks {
|
||||
if !webhook.IsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
matched := false
|
||||
for _, event := range webhook.Events {
|
||||
if record.Action == event {
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
if webhook.IsUserExtended {
|
||||
user := getUser(record.Organization, record.User)
|
||||
record.ExtendedUser = user
|
||||
}
|
||||
|
||||
err := sendWebhook(webhook, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
145
object/resource.go
Normal file
145
object/resource.go
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||
FileName string `xorm:"varchar(100)" json:"fileName"`
|
||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||
FileSize int `json:"fileSize"`
|
||||
Url string `xorm:"varchar(1000)" json:"url"`
|
||||
Description string `xorm:"varchar(1000)" json:"description"`
|
||||
}
|
||||
|
||||
func GetResourceCount(owner, user, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Resource{User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetResources(owner string, user string) []*Resource {
|
||||
if owner == "built-in" {
|
||||
owner = ""
|
||||
user = ""
|
||||
}
|
||||
|
||||
resources := []*Resource{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner, User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func GetPaginationResources(owner, user string, offset, limit int, field, value, sortField, sortOrder string) []*Resource {
|
||||
if owner == "built-in" {
|
||||
owner = ""
|
||||
user = ""
|
||||
}
|
||||
|
||||
resources := []*Resource{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&resources, &Resource{User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
func getResource(owner string, name string) *Resource {
|
||||
resource := Resource{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &resource
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetResource(id string) *Resource {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
return getResource(owner, name)
|
||||
}
|
||||
|
||||
func UpdateResource(id string, resource *Resource) bool {
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
if getResource(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//return affected != 0
|
||||
return true
|
||||
}
|
||||
|
||||
func AddResource(resource *Resource) bool {
|
||||
affected, err := adapter.Engine.Insert(resource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteResource(resource *Resource) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{resource.Owner, resource.Name}).Delete(&Resource{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (resource *Resource) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", resource.Owner, resource.Name)
|
||||
}
|
||||
|
||||
func AddOrUpdateResource(resource *Resource) bool {
|
||||
if getResource(resource.Owner, resource.Name) == nil {
|
||||
return AddResource(resource)
|
||||
} else {
|
||||
return UpdateResource(resource.GetId(), resource)
|
||||
}
|
||||
}
|
||||
123
object/role.go
Normal file
123
object/role.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
}
|
||||
|
||||
func GetRoleCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Role{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetRoles(owner string) []*Role {
|
||||
roles := []*Role{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&roles, &Role{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func GetPaginationRoles(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Role {
|
||||
roles := []*Role{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&roles)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func getRole(owner string, name string) *Role {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
role := Role{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &role
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetRole(id string) *Role {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getRole(owner, name)
|
||||
}
|
||||
|
||||
func UpdateRole(id string, role *Role) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getRole(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddRole(role *Role) bool {
|
||||
affected, err := adapter.Engine.Insert(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteRole(role *Role) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (role *Role) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
|
||||
}
|
||||
135
object/saml.go
Normal file
135
object/saml.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
|
||||
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return assertionInfo.NameID, nil
|
||||
}
|
||||
|
||||
func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
|
||||
provider := GetProvider(id)
|
||||
if provider.Category != "SAML" {
|
||||
return "", "", fmt.Errorf("Provider %s's category is not SAML", provider.Name)
|
||||
}
|
||||
sp, err := buildSp(provider, "")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
auth := ""
|
||||
method := ""
|
||||
if provider.EnableSignAuthnRequest {
|
||||
post, err := sp.BuildAuthBodyPost(relayState)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
auth = string(post[:])
|
||||
method = "POST"
|
||||
} else {
|
||||
auth, err = sp.BuildAuthURL(relayState)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
method = "GET"
|
||||
}
|
||||
return auth, method, nil
|
||||
}
|
||||
|
||||
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
|
||||
certStore := dsig.MemoryX509CertificateStore{
|
||||
Roots: []*x509.Certificate{},
|
||||
}
|
||||
origin := beego.AppConfig.String("origin")
|
||||
certEncodedData := ""
|
||||
if samlResponse != "" {
|
||||
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
|
||||
} else if provider.IdP != "" {
|
||||
certEncodedData = provider.IdP
|
||||
}
|
||||
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idpCert, err := x509.ParseCertificate(certData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certStore.Roots = append(certStore.Roots, idpCert)
|
||||
sp := &saml2.SAMLServiceProvider{
|
||||
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
|
||||
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
|
||||
IDPCertificateStore: &certStore,
|
||||
SignAuthnRequests: false,
|
||||
SPKeyStore: dsig.RandomKeyStoreForTest(),
|
||||
}
|
||||
if provider.Endpoint != "" {
|
||||
sp.IdentityProviderSSOURL = provider.Endpoint
|
||||
sp.IdentityProviderIssuer = provider.IssuerUrl
|
||||
}
|
||||
if provider.EnableSignAuthnRequest {
|
||||
sp.SignAuthnRequests = true
|
||||
sp.SPKeyStore = buildSpKeyStore()
|
||||
}
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func parseSamlResponse(samlResponse string, providerType string) string {
|
||||
de, err := base64.StdEncoding.DecodeString(samlResponse)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
deStr := strings.Replace(string(de), "\n", "", -1)
|
||||
tagMap := map[string]string{
|
||||
"Aliyun IDaaS": "ds",
|
||||
"Keycloak": "dsig",
|
||||
}
|
||||
tag := tagMap[providerType]
|
||||
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
|
||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||
return res[1]
|
||||
}
|
||||
|
||||
func buildSpKeyStore() dsig.X509KeyStore {
|
||||
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &dsig.TLSCertKeyStore {
|
||||
PrivateKey: keyPair.PrivateKey,
|
||||
Certificate: keyPair.Certificate,
|
||||
}
|
||||
}
|
||||
23
object/signup_item.go
Normal file
23
object/signup_item.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
34
object/sms.go
Normal file
34
object/sms.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import "github.com/casdoor/go-sms-sender"
|
||||
|
||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
if provider.Type == go_sms_sender.TencentCloud {
|
||||
params["0"] = content
|
||||
} else {
|
||||
params["code"] = content
|
||||
}
|
||||
|
||||
err = client.SendMessage(params, phoneNumbers...)
|
||||
return err
|
||||
}
|
||||
122
object/storage.go
Normal file
122
object/storage.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/storage"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
var isCloudIntranet bool
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
|
||||
if err != nil {
|
||||
//panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getProviderEndpoint(provider *Provider) string {
|
||||
endpoint := provider.Endpoint
|
||||
if provider.IntranetEndpoint != "" && isCloudIntranet {
|
||||
endpoint = provider.IntranetEndpoint
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
|
||||
|
||||
host := ""
|
||||
if provider.Type != "Local File System" {
|
||||
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
||||
host = util.GetUrlHost(provider.Domain)
|
||||
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
|
||||
host = fmt.Sprintf("https://%s", host)
|
||||
}
|
||||
} else {
|
||||
// provider.Domain = "http://localhost:8000" or "https://door.casbin.com"
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
}
|
||||
|
||||
fileUrl := util.UrlJoin(host, objectKey)
|
||||
if hasTimestamp {
|
||||
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
|
||||
}
|
||||
|
||||
return fileUrl, objectKey
|
||||
}
|
||||
|
||||
func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
|
||||
endpoint := getProviderEndpoint(provider)
|
||||
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
|
||||
if storageProvider == nil {
|
||||
return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
|
||||
}
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
}
|
||||
|
||||
fileUrl, objectKey := getUploadFileUrl(provider, fullFilePath, true)
|
||||
|
||||
_, err := storageProvider.Put(objectKey, fileBuffer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return fileUrl, objectKey, nil
|
||||
}
|
||||
|
||||
func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
|
||||
var fileUrl string
|
||||
var objectKey string
|
||||
var err error
|
||||
times := 0
|
||||
for {
|
||||
fileUrl, objectKey, err = uploadFile(provider, fullFilePath, fileBuffer)
|
||||
if err != nil {
|
||||
times += 1
|
||||
if times >= 5 {
|
||||
return "", "", err
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fileUrl, objectKey, nil
|
||||
}
|
||||
|
||||
func DeleteFile(provider *Provider, objectKey string) error {
|
||||
endpoint := getProviderEndpoint(provider)
|
||||
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
|
||||
if storageProvider == nil {
|
||||
return fmt.Errorf("the provider type: %s is not supported", provider.Type)
|
||||
}
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
}
|
||||
|
||||
return storageProvider.Delete(objectKey)
|
||||
}
|
||||
208
object/syncer.go
Normal file
208
object/syncer.go
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2021 The casbin 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 object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type TableColumn struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CasdoorName string `json:"casdoorName"`
|
||||
IsHashed bool `json:"isHashed"`
|
||||
Values []string `json:"values"`
|
||||
}
|
||||
|
||||
type Syncer struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
|
||||
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
|
||||
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
|
||||
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
|
||||
ErrorText string `xorm:"mediumtext" json:"errorText"`
|
||||
SyncInterval int `json:"syncInterval"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
|
||||
Adapter *Adapter `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
func GetSyncerCount(owner, field, value string) int {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Syncer{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetSyncers(owner string) []*Syncer {
|
||||
syncers := []*Syncer{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&syncers, &Syncer{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return syncers
|
||||
}
|
||||
|
||||
func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
|
||||
syncers := []*Syncer{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&syncers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return syncers
|
||||
}
|
||||
|
||||
func getSyncer(owner string, name string) *Syncer {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
syncer := Syncer{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &syncer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetSyncer(id string) *Syncer {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getSyncer(owner, name)
|
||||
}
|
||||
|
||||
func GetMaskedSyncer(syncer *Syncer) *Syncer {
|
||||
if syncer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if syncer.Password != "" {
|
||||
syncer.Password = "***"
|
||||
}
|
||||
return syncer
|
||||
}
|
||||
|
||||
func GetMaskedSyncers(syncers []*Syncer) []*Syncer {
|
||||
for _, syncer := range syncers {
|
||||
syncer = GetMaskedSyncer(syncer)
|
||||
}
|
||||
return syncers
|
||||
}
|
||||
|
||||
func UpdateSyncer(id string, syncer *Syncer) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getSyncer(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
addSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func updateSyncerErrorText(syncer *Syncer, line string) bool {
|
||||
s := getSyncer(syncer.Owner, syncer.Name)
|
||||
if s == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
s.ErrorText = s.ErrorText + line
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{s.Owner, s.Name}).Cols("error_text").Update(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddSyncer(syncer *Syncer) bool {
|
||||
affected, err := adapter.Engine.Insert(syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
addSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteSyncer(syncer *Syncer) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{syncer.Owner, syncer.Name}).Delete(&Syncer{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if affected == 1 {
|
||||
deleteSyncerJob(syncer)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func (syncer *Syncer) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", syncer.Owner, syncer.Name)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTableColumnsTypeMap() map[string]string {
|
||||
m := map[string]string{}
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
m[tableColumn.Name] = tableColumn.Type
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getTable() string {
|
||||
if syncer.DatabaseType == "mssql" {
|
||||
return fmt.Sprintf("[%s]", syncer.Table)
|
||||
} else {
|
||||
return syncer.Table
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user