mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
578 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
321af5ec87 | ||
|
|
9ccf340b3d | ||
|
|
d7dcacb899 | ||
|
|
8addd483c2 | ||
|
|
4d27e5ac9b | ||
|
|
31290f6e80 | ||
|
|
c31cc4dbee | ||
|
|
cc1f94cf61 | ||
|
|
a9132b8d50 | ||
|
|
7b4a9891aa | ||
|
|
dcaaf38ecc | ||
|
|
653a8aa850 | ||
|
|
274af11385 | ||
|
|
170b246901 | ||
|
|
5250432ce3 | ||
|
|
53d6e973eb | ||
|
|
c9bdd62d53 | ||
|
|
7eb98af528 | ||
|
|
385e3e21b3 | ||
|
|
127ec63d76 | ||
|
|
4988b5f260 | ||
|
|
f28a50f92c | ||
|
|
e8efe8e609 | ||
|
|
d7e7457bc5 | ||
|
|
daff927811 | ||
|
|
8861ec0a61 | ||
|
|
bd4321f61a | ||
|
|
d831482fe0 | ||
|
|
9ea79ea389 | ||
|
|
b6fdf840ef | ||
|
|
73f262ce4b | ||
|
|
f63434adc3 | ||
|
|
42948386ec | ||
|
|
32e0621b0a | ||
|
|
78e05b84e9 | ||
|
|
76ebd21163 | ||
|
|
38aaf545c6 | ||
|
|
527d7ef671 | ||
|
|
e54b80e061 | ||
|
|
27f95a8b2c | ||
|
|
a1e8903128 | ||
|
|
b00ac2aef4 | ||
|
|
8865d15ed9 | ||
|
|
fc3d4b7f33 | ||
|
|
934cc44540 | ||
|
|
106de3dd4c | ||
|
|
9b55cfcbe3 | ||
|
|
8137f1023a | ||
|
|
d670e33b6f | ||
|
|
3d3bb8fc94 | ||
|
|
9c880eae8a | ||
|
|
54a71630f1 | ||
|
|
923a8453cc | ||
|
|
00447ca819 | ||
|
|
ad4ee9d822 | ||
|
|
40e9dbfda2 | ||
|
|
b9da6911e6 | ||
|
|
81f9211098 | ||
|
|
8290081365 | ||
|
|
81af610c11 | ||
|
|
cfa2cf58f3 | ||
|
|
01c17d28f6 | ||
|
|
efd2a5e8c5 | ||
|
|
c437b00727 | ||
|
|
5df0d1ddc6 | ||
|
|
c3521b0d87 | ||
|
|
148916d35e | ||
|
|
06c7da944c | ||
|
|
f2ba741499 | ||
|
|
0f9927686b | ||
|
|
59855a71ef | ||
|
|
fffb21bb4f | ||
|
|
30bb6cdfab | ||
|
|
8771477884 | ||
|
|
55a5070691 | ||
|
|
1182b51e4b | ||
|
|
9976ecc2aa | ||
|
|
3bd8107fcf | ||
|
|
a48c67de5c | ||
|
|
bb0a7c014e | ||
|
|
80729b6b06 | ||
|
|
ff168d1c9e | ||
|
|
331100370c | ||
|
|
47babf2ed7 | ||
|
|
c1388bf23f | ||
|
|
3f4dfe9b0b | ||
|
|
0caac20d77 | ||
|
|
9d0a65a516 | ||
|
|
ab061ba7a6 | ||
|
|
2d24d064d5 | ||
|
|
458685026b | ||
|
|
b0448a4565 | ||
|
|
f902f59b31 | ||
|
|
2b8bfbe544 | ||
|
|
564a25e578 | ||
|
|
f50b08d301 | ||
|
|
26566d9ce0 | ||
|
|
f42d438be2 | ||
|
|
1fbc39b614 | ||
|
|
36af471c3c | ||
|
|
a5e86c9a36 | ||
|
|
4b27dd40b9 | ||
|
|
ff0aac9d8a | ||
|
|
bf24c8253e | ||
|
|
fd92352302 | ||
|
|
fcb66a11a3 | ||
|
|
63d2021a73 | ||
|
|
d110d9c74e | ||
|
|
157bf81dcb | ||
|
|
1beb1b1b4f | ||
|
|
13f523679c | ||
|
|
ed816d9aea | ||
|
|
ebfb51eed5 | ||
|
|
0127b1ea9e | ||
|
|
61f41a1459 | ||
|
|
d3fbc133a2 | ||
|
|
ce7a8e7a3d | ||
|
|
2b45610080 | ||
|
|
5b4dd6731c | ||
|
|
80157a032a | ||
|
|
7b1bf9a4e2 | ||
|
|
337c4d9ce5 | ||
|
|
9afc36a009 | ||
|
|
ebff1387c1 | ||
|
|
801502ec77 | ||
|
|
c07f59201e | ||
|
|
98b799d821 | ||
|
|
02714956d8 | ||
|
|
4d435d58c5 | ||
|
|
1c2fd3be50 | ||
|
|
6222439e21 | ||
|
|
46d46ac90b | ||
|
|
a5fe61eb15 | ||
|
|
0bfec36983 | ||
|
|
11607ecf24 | ||
|
|
9970e5535b | ||
|
|
99492e9c34 | ||
|
|
1d6ecffb3b | ||
|
|
8c33349dcb | ||
|
|
cfb2c55a4b | ||
|
|
e24d29f1d3 | ||
|
|
debd6ef2b9 | ||
|
|
58e9c21e4f | ||
|
|
c7c3083e3e | ||
|
|
63d061e9f2 | ||
|
|
1887bdb90a | ||
|
|
69addb068a | ||
|
|
a62c3ce0f9 | ||
|
|
aecc94bdb3 | ||
|
|
fcb7f2905a | ||
|
|
34c7169ca3 | ||
|
|
6e450061f4 | ||
|
|
fc3d7e9f43 | ||
|
|
514db4f9a2 | ||
|
|
23a8b65ce2 | ||
|
|
f8e6662bc8 | ||
|
|
89be2456fb | ||
|
|
bfd3451717 | ||
|
|
0b759d6a32 | ||
|
|
65e05084e6 | ||
|
|
f25a4ed386 | ||
|
|
b2a1d41829 | ||
|
|
2741a92d31 | ||
|
|
3570ce86d7 | ||
|
|
d809902d1e | ||
|
|
ec4232c00a | ||
|
|
dec793bfac | ||
|
|
fe3fe0527a | ||
|
|
a8855e86ed | ||
|
|
179d1c38e6 | ||
|
|
287da73a4f | ||
|
|
e9ed917888 | ||
|
|
70d842c971 | ||
|
|
bcda228e05 | ||
|
|
000dafc5e6 | ||
|
|
4ea824fe71 | ||
|
|
f72f539516 | ||
|
|
1b192a184f | ||
|
|
0edf896b4c | ||
|
|
7dec4deb70 | ||
|
|
d511221899 | ||
|
|
d2b7de5221 | ||
|
|
79c5be02e2 | ||
|
|
09df37438d | ||
|
|
8561e4c6fd | ||
|
|
976cb4d988 | ||
|
|
20f7d5103c | ||
|
|
d049ec2832 | ||
|
|
00fe2a527e | ||
|
|
f53ec3b43e | ||
|
|
4d0417f114 | ||
|
|
00903b64db | ||
|
|
a3038cad00 | ||
|
|
ef99f1afac | ||
|
|
cc00d4bd04 | ||
|
|
250f26ff6f | ||
|
|
62b786ca4a | ||
|
|
9cfb47e658 | ||
|
|
ccef27b637 | ||
|
|
8dee61fd39 | ||
|
|
ae64bd0e19 | ||
|
|
a75e93dbfc | ||
|
|
0aa66c5729 | ||
|
|
4c2434219d | ||
|
|
8c65fda33b | ||
|
|
14f2751bbb | ||
|
|
d37a543c39 | ||
|
|
4947e0c483 | ||
|
|
f87c630b86 | ||
|
|
73789ea734 | ||
|
|
5a16c9c495 | ||
|
|
9dd8357f67 | ||
|
|
623e77a268 | ||
|
|
73403b98df | ||
|
|
7aa1a2f336 | ||
|
|
08e42d2f56 | ||
|
|
69248fd7bb | ||
|
|
0af188c6ab | ||
|
|
8b6d32d0d1 | ||
|
|
3c11c25d69 | ||
|
|
12e6cc63e8 | ||
|
|
d429c8b59a | ||
|
|
ddd7a3551b | ||
|
|
49ede92e06 | ||
|
|
b813213328 | ||
|
|
14065b3ca9 | ||
|
|
41f9dc490a | ||
|
|
48d25ca73f | ||
|
|
e49e04034c | ||
|
|
c7860f8e5c | ||
|
|
adb982114a | ||
|
|
5b8983deac | ||
|
|
1730bc3b98 | ||
|
|
4374064d98 | ||
|
|
c1d7994045 | ||
|
|
7bda367cc8 | ||
|
|
3de7a2ccd2 | ||
|
|
9cc278df31 | ||
|
|
a0bab07e2f | ||
|
|
149bbd92ca | ||
|
|
1de3c989d7 | ||
|
|
2e547945e2 | ||
|
|
4d4a9a27af | ||
|
|
5b41d0995f | ||
|
|
ab98d72022 | ||
|
|
8a7cd3f74d | ||
|
|
35cb56d6e9 | ||
|
|
a7a2ffd16b | ||
|
|
dbeda324e0 | ||
|
|
bf1fe99d98 | ||
|
|
41429ec7c7 | ||
|
|
ee9ed13a66 | ||
|
|
490ce286ff | ||
|
|
099c2c0a21 | ||
|
|
46e15f7fa1 | ||
|
|
6677e63e08 | ||
|
|
6d6a3a3d6b | ||
|
|
5006246cf1 | ||
|
|
6187fb9b86 | ||
|
|
86f57ccd56 | ||
|
|
854096bac7 | ||
|
|
9d2b3bb157 | ||
|
|
22bda62e59 | ||
|
|
c8ad1dcc7a | ||
|
|
7212a7a328 | ||
|
|
f6b1b7b6bb | ||
|
|
53a9d72c4a | ||
|
|
ca10fbcde5 | ||
|
|
b4d33e5dfc | ||
|
|
37bed989f1 | ||
|
|
507eda8a7d | ||
|
|
cbe67e9ebc | ||
|
|
cd38200506 | ||
|
|
5d5cf92a19 | ||
|
|
98230d0ee3 | ||
|
|
e47c04a0b0 | ||
|
|
b65ccac58f | ||
|
|
bee69cc250 | ||
|
|
a350e175c7 | ||
|
|
2cd8188ffb | ||
|
|
b8a2d65a1d | ||
|
|
95f72c854d | ||
|
|
cd8bcfbbb5 | ||
|
|
08f89d2844 | ||
|
|
f3f156bf57 | ||
|
|
73e6f576f4 | ||
|
|
20236cab8a | ||
|
|
6c7b65edad | ||
|
|
21782293ea | ||
|
|
e52478c9aa | ||
|
|
319cba8653 | ||
|
|
df3acccc50 | ||
|
|
19282cac60 | ||
|
|
933c12b48d | ||
|
|
8a73890646 | ||
|
|
d6df5184a6 | ||
|
|
91e1a374b4 | ||
|
|
c725de7b5b | ||
|
|
ad1fd633b1 | ||
|
|
ef9284030b | ||
|
|
89e5740027 | ||
|
|
106f6bbcea | ||
|
|
b53c7a624b | ||
|
|
6fa788a8f9 | ||
|
|
19f0788f47 | ||
|
|
7767226000 | ||
|
|
4eb6b73903 | ||
|
|
cb46ecb002 | ||
|
|
e694921fe6 | ||
|
|
8266661855 | ||
|
|
cf7ddbe0e1 | ||
|
|
bdb3ab366f | ||
|
|
1fc71a0738 | ||
|
|
0b7520e3b1 | ||
|
|
48c8ccfe97 | ||
|
|
ad79b4f77c | ||
|
|
fd876b8636 | ||
|
|
21e896642a | ||
|
|
b4c395f116 | ||
|
|
a38116014d | ||
|
|
54223db1e9 | ||
|
|
8a897abc7b | ||
|
|
fe7b078ec8 | ||
|
|
ce66bdcbd4 | ||
|
|
f65e563c0c | ||
|
|
e860ba6c22 | ||
|
|
50b6605a43 | ||
|
|
d181200642 | ||
|
|
386ba25a44 | ||
|
|
5331d194df | ||
|
|
814ecd233e | ||
|
|
f9a8ac4e9b | ||
|
|
1bd5eecd54 | ||
|
|
2fa1d9998d | ||
|
|
9d9cfebd9e | ||
|
|
cc8a7a18d2 | ||
|
|
552c795041 | ||
|
|
3d757e8d90 | ||
|
|
1b5ecaed80 | ||
|
|
77c93ed96b | ||
|
|
3eeed99af2 | ||
|
|
a143dfbb37 | ||
|
|
6b1da3b18a | ||
|
|
f0894f3415 | ||
|
|
539295c1b7 | ||
|
|
54f91a5bfb | ||
|
|
f3c0d05c39 | ||
|
|
9f9cc7ed42 | ||
|
|
814b2da0ca | ||
|
|
7a9bb0c84b | ||
|
|
36ae2af29b | ||
|
|
d192f23e6e | ||
|
|
67cd0cd55c | ||
|
|
9e53d8b429 | ||
|
|
f5abf82b95 | ||
|
|
8dd3a25b52 | ||
|
|
d0aa46db08 | ||
|
|
f0ff70566b | ||
|
|
efecf5113b | ||
|
|
980569de68 | ||
|
|
9c74952607 | ||
|
|
70c2a4a6e4 | ||
|
|
99b136b824 | ||
|
|
ae4116c0f6 | ||
|
|
3080d7d868 | ||
|
|
08cf8ae1d6 | ||
|
|
3ed0f873f3 | ||
|
|
5060d3f408 | ||
|
|
ef24bea562 | ||
|
|
c18efaa33d | ||
|
|
b6b14f6f1c | ||
|
|
a90a52f426 | ||
|
|
bd5ea38446 | ||
|
|
f8248f46e5 | ||
|
|
b09c454bf0 | ||
|
|
d825689da4 | ||
|
|
a64dda2a2e | ||
|
|
8ce8789631 | ||
|
|
2b2f367c30 | ||
|
|
4d194457d8 | ||
|
|
6f7cf8805d | ||
|
|
36e39503c8 | ||
|
|
e7a24c9cd4 | ||
|
|
bd8a8922cc | ||
|
|
396b2e0fb6 | ||
|
|
36e382fadb | ||
|
|
d2666f2440 | ||
|
|
397ca97f0f | ||
|
|
631bb439a4 | ||
|
|
a4003e188e | ||
|
|
f4a9ba2db8 | ||
|
|
895a62c475 | ||
|
|
ac5a0d9dcb | ||
|
|
b8644d5c93 | ||
|
|
4d8baf1af0 | ||
|
|
f70987de09 | ||
|
|
9d02b1530c | ||
|
|
3d532dae01 | ||
|
|
02247b067f | ||
|
|
63c2668171 | ||
|
|
5575039126 | ||
|
|
f97c8f2ce4 | ||
|
|
6baab1d006 | ||
|
|
17adf04860 | ||
|
|
f871ecb425 | ||
|
|
4a425cde78 | ||
|
|
f56252b0cc | ||
|
|
7ae6c66beb | ||
|
|
be90fb96ea | ||
|
|
dd3350b169 | ||
|
|
cdd1ba1fe3 | ||
|
|
10ea12c867 | ||
|
|
e6358d948a | ||
|
|
1101572f78 | ||
|
|
0cf8836832 | ||
|
|
6e4562b0e6 | ||
|
|
856f1e176a | ||
|
|
1653a57e7b | ||
|
|
5f03e580c2 | ||
|
|
d370ae48a2 | ||
|
|
38baeba254 | ||
|
|
478f9b9390 | ||
|
|
82ad3821c4 | ||
|
|
07afaf12d5 | ||
|
|
2c98cbd020 | ||
|
|
1fe9d18a1a | ||
|
|
02a75a931a | ||
|
|
af7a432f29 | ||
|
|
770aca923f | ||
|
|
d50a74c7c6 | ||
|
|
fcd8554ea7 | ||
|
|
98da0723fc | ||
|
|
f037d7fea6 | ||
|
|
676e68a2bb | ||
|
|
c82d9c7722 | ||
|
|
70c4b17518 | ||
|
|
995a44de0a | ||
|
|
4f5fc18c66 | ||
|
|
4a94f379b4 | ||
|
|
fb4651b11f | ||
|
|
f961db3130 | ||
|
|
c63464c4c9 | ||
|
|
cca8b26375 | ||
|
|
cd0afeca15 | ||
|
|
86362bb0dd | ||
|
|
c4979a22dd | ||
|
|
859c5b3fa1 | ||
|
|
57a26b90cb | ||
|
|
7fa45fa471 | ||
|
|
47b5b286d8 | ||
|
|
ef37cb3ea5 | ||
|
|
b95bb9aa6a | ||
|
|
cfad4fa8a6 | ||
|
|
3f33485ca9 | ||
|
|
1c1dfde2c4 | ||
|
|
86e92941df | ||
|
|
09b788fef6 | ||
|
|
d54d80b0b8 | ||
|
|
3ff29ba3b0 | ||
|
|
2efb45943c | ||
|
|
d43b8cf0e5 | ||
|
|
5e8b5e1a15 | ||
|
|
03447abf5c | ||
|
|
c22d3a9967 | ||
|
|
5df3672f3b | ||
|
|
c68574efc3 | ||
|
|
e5d76cbce8 | ||
|
|
f121ed4062 | ||
|
|
111105d48b | ||
|
|
d7cb1a2fab | ||
|
|
01d34b54eb | ||
|
|
f33f796421 | ||
|
|
0504be2441 | ||
|
|
93eca76bf8 | ||
|
|
b3d02b0c37 | ||
|
|
67ff9eb379 | ||
|
|
cd6963daa6 | ||
|
|
44de49cbb0 | ||
|
|
8864afd784 | ||
|
|
51e4db73f0 | ||
|
|
11fca74fec | ||
|
|
fff2cd32d5 | ||
|
|
d993a299ef | ||
|
|
650408f61c | ||
|
|
ef26cdbbee | ||
|
|
168ab569b9 | ||
|
|
b418abc7c8 | ||
|
|
280ddb336e | ||
|
|
d93f36a180 | ||
|
|
941bcd3cd1 | ||
|
|
7591db3168 | ||
|
|
e68c840dad | ||
|
|
45f7b7d962 | ||
|
|
1bf8b6079d | ||
|
|
c59565c038 | ||
|
|
53d7916772 | ||
|
|
7beec38881 | ||
|
|
b130cc6c8e | ||
|
|
f8488208fb | ||
|
|
2efaf40370 | ||
|
|
75b99148c6 | ||
|
|
f36b038010 | ||
|
|
f84de28338 | ||
|
|
2d92cd6cb2 | ||
|
|
1a8d163d45 | ||
|
|
954f36dae6 | ||
|
|
76ab835347 | ||
|
|
5f4e873f4a | ||
|
|
889fcfd0e0 | ||
|
|
0326836544 | ||
|
|
18f64b0357 | ||
|
|
786859294d | ||
|
|
d10580b56b | ||
|
|
2b51a98d27 | ||
|
|
96c4309230 | ||
|
|
5ce5f37867 | ||
|
|
02f2968ee5 | ||
|
|
eee6a9132d | ||
|
|
9d90af4a3d | ||
|
|
72305de2d8 | ||
|
|
8f58f76001 | ||
|
|
a969b6117b | ||
|
|
97762119b3 | ||
|
|
4bdead5ef2 | ||
|
|
8987cf2199 | ||
|
|
27c9b09116 | ||
|
|
0ac0f71fef | ||
|
|
c1547cab8b | ||
|
|
3f454743a9 | ||
|
|
c2f12eed26 | ||
|
|
1b1b692ac0 | ||
|
|
dc8ed2d510 | ||
|
|
049c1c66aa | ||
|
|
8028660a8f | ||
|
|
e6532025f8 | ||
|
|
3361d36bbf | ||
|
|
5f20efb0c7 | ||
|
|
3c90868338 | ||
|
|
2ab45b1019 | ||
|
|
c5b55283d1 | ||
|
|
d937d5b5d4 | ||
|
|
84b1ddc5d9 | ||
|
|
a235254e48 | ||
|
|
40aac6c2dd | ||
|
|
70c496e9fe | ||
|
|
4bc91f2381 | ||
|
|
dd3a3e1081 | ||
|
|
23bfc3d34a | ||
|
|
7ab88dd663 | ||
|
|
ebb44773c2 | ||
|
|
122ab148a4 | ||
|
|
0deb60ac2c | ||
|
|
fd8ab2688e | ||
|
|
b7062c8dda | ||
|
|
97fe2ddfd0 | ||
|
|
9cc9a36766 | ||
|
|
d51848aa50 | ||
|
|
00cc89d71c | ||
|
|
971ce294ad | ||
|
|
ccc7412947 | ||
|
|
3e4fc7ceb4 | ||
|
|
87973951b8 | ||
|
|
96b04a269d | ||
|
|
f0f1b21226 | ||
|
|
26405985a2 | ||
|
|
3063355eb7 | ||
|
|
04053c8465 | ||
|
|
6f36a26694 | ||
|
|
aaf196b477 | ||
|
|
06f78a7518 | ||
|
|
c6699686ad | ||
|
|
64e7c6093e | ||
|
|
54262a850d | ||
|
|
13b0dbc960 | ||
|
|
963cecb365 | ||
|
|
ab10f062f7 | ||
|
|
e15d79b834 | ||
|
|
bb87fdd958 |
19
.coveragerc
19
.coveragerc
@@ -1,24 +1,15 @@
|
||||
[run]
|
||||
branch = True
|
||||
source =
|
||||
alliance_auth
|
||||
authentication
|
||||
corputils
|
||||
eveonline
|
||||
fleetactivitytracking
|
||||
fleetup
|
||||
groupmanagement
|
||||
hrapplications
|
||||
notifications
|
||||
optimer
|
||||
permissions_tool
|
||||
services
|
||||
srp
|
||||
timerboard
|
||||
allianceauth
|
||||
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/example/*
|
||||
*/project_template/*
|
||||
*/bin/*
|
||||
*/tests/*
|
||||
*/tests.py
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
*/*.py.example linguist-language=Python
|
||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -8,6 +8,7 @@ __pycache__/
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
@@ -41,7 +42,6 @@ nosetests.xml
|
||||
coverage.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
@@ -53,17 +53,25 @@ docs/_build/
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
.vagrant/
|
||||
alliance_auth/settings.py
|
||||
*Thumbs.db
|
||||
nginx_config.txt
|
||||
|
||||
# custom staticfiles
|
||||
static/*
|
||||
|
||||
#celerybeat
|
||||
*.pid
|
||||
celerybeat-schedule
|
||||
|
||||
#pycharm
|
||||
.idea/*
|
||||
.idea/*
|
||||
/nbproject/
|
||||
|
||||
#VSCode
|
||||
.vscode/
|
||||
|
||||
#gitlab configs
|
||||
.gitlab/
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
|
||||
51
.gitlab-ci.yml
Normal file
51
.gitlab-ci.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
stages:
|
||||
- "test"
|
||||
- deploy
|
||||
|
||||
before_script:
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
|
||||
test-3.6-core:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
- tox -e py36-core
|
||||
|
||||
test-3.7-core:
|
||||
image: python:3.7-buster
|
||||
script:
|
||||
- tox -e py37-core
|
||||
|
||||
test-3.8-core:
|
||||
image: python:3.8-buster
|
||||
script:
|
||||
- tox -e py38-core
|
||||
|
||||
test-3.6-all:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
- tox -e py36-all
|
||||
|
||||
test-3.7-all:
|
||||
image: python:3.7-buster
|
||||
script:
|
||||
- tox -e py37-all
|
||||
|
||||
test-3.8-all:
|
||||
image: python:3.8-buster
|
||||
script:
|
||||
- tox -e py38-all
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.6-stretch
|
||||
|
||||
before_script:
|
||||
- pip install twine
|
||||
|
||||
script:
|
||||
- python setup.py sdist
|
||||
- twine upload dist/*
|
||||
|
||||
only:
|
||||
- tags
|
||||
14
.gitlab/issue_templates/Bug.md
Normal file
14
.gitlab/issue_templates/Bug.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Bug
|
||||
|
||||
- I have searched [issues](https://gitlab.com/allianceauth/allianceauth/issues?scope=all&utf8=%E2%9C%93&state=all) (Y/N):
|
||||
- What Version of Alliance Auth:
|
||||
- What Operating System:
|
||||
- Version of other components relevant to issue eg. Service, Database:
|
||||
|
||||
Please include a brief description of your issue here.
|
||||
|
||||
Please include steps to reproduce the issue
|
||||
|
||||
Please include any tracebacks or logs
|
||||
|
||||
Please include the results of the command `pip list`
|
||||
7
.gitlab/issue_templates/Feature Request.md
Normal file
7
.gitlab/issue_templates/Feature Request.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Feature Request
|
||||
|
||||
- Describe the feature are you requesting.
|
||||
|
||||
- Is this a Service (external integration), a Module (Alliance Auth extension) or an enhancement to an existing service/module.
|
||||
|
||||
- Describe why its useful to you or others.
|
||||
22
.idea/allianceauth.iml
generated
22
.idea/allianceauth.iml
generated
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="django" name="Django">
|
||||
<configuration>
|
||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
||||
<option name="settingsModule" value="alliance_auth/settings.py.example" />
|
||||
<option name="manageScript" value="manage.py" />
|
||||
<option name="environment" value="<map/>" />
|
||||
<option name="commandsToSkip" value="" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 2.7.11 virtualenv at ~/1.6" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="TemplatesService">
|
||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||
</component>
|
||||
</module>
|
||||
12
.idea/dataSources.ids
generated
12
.idea/dataSources.ids
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component name="dataSourceStorage">
|
||||
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
|
||||
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
|
||||
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
|
||||
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
|
||||
</data-source>
|
||||
</component>
|
||||
20
.idea/dataSources.xml
generated
20
.idea/dataSources.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
|
||||
<driver-ref>mysql</driver-ref>
|
||||
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_auth</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
|
||||
<driver-ref>mysql</driver-ref>
|
||||
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_forum</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
|
||||
<driver-ref>mysql</driver-ref>
|
||||
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_mumble</jdbc-url>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/encodings.xml
generated
5
.idea/encodings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
|
||||
</project>
|
||||
|
||||
11
.idea/inspectionProfiles/Project_Default.xml
generated
11
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,11 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0" is_locked="false">
|
||||
<option name="myName" value="Project Default" />
|
||||
<option name="myLocal" value="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
7
.idea/inspectionProfiles/profiles_settings.xml
generated
7
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,7 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="PROJECT_PROFILE" />
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7.11 virtualenv at ~/1.6" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="1" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/modules.xml
generated
9
.idea/modules.xml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/allianceauth.iml" filepath="$PROJECT_DIR$/.idea/allianceauth.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
||||
5
.idea/scopes/scope_settings.xml
generated
5
.idea/scopes/scope_settings.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<state>
|
||||
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/vcs.xml
generated
7
.idea/vcs.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
||||
27
.readthedocs.yml
Normal file
27
.readthedocs.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Build documentation with MkDocs
|
||||
#mkdocs:
|
||||
# configuration: mkdocs.yml
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- testing
|
||||
system_packages: true
|
||||
14
.travis.yml
14
.travis.yml
@@ -1,14 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install requests
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r testing-requirements.txt
|
||||
# command to run tests
|
||||
script: coverage run runtests.py
|
||||
cache: pip
|
||||
after_success:
|
||||
coveralls
|
||||
7
MANIFEST.in
Normal file
7
MANIFEST.in
Normal file
@@ -0,0 +1,7 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include MANIFEST.in
|
||||
graft allianceauth
|
||||
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[co]
|
||||
109
README.md
109
README.md
@@ -1,55 +1,86 @@
|
||||
Alliance Auth
|
||||
============
|
||||
# Alliance Auth
|
||||
|
||||
[](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://travis-ci.org/allianceauth/allianceauth)
|
||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](https://discord.gg/fjnHAmk)
|
||||
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
EVE service auth to help corps, alliances, and coalitions manage services.
|
||||
Built for "The 99 Percent" open for anyone to use.
|
||||
## Content
|
||||
|
||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||
- [Overview](#overview)
|
||||
- [Documentation](http://allianceauth.rtfd.io)
|
||||
- [Support](#support)
|
||||
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
|
||||
- [Developer Team](#developer-team)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
Special Permissions In Admin:
|
||||
## Overview
|
||||
|
||||
auth | user | group_management ( Access to add members to groups within the alliance )
|
||||
auth | user | jabber_broadcast ( Access to broadcast a message over jabber to own groups )
|
||||
auth | user | jabber_broadcast_all ( Can choose from all groups and the 'all' option when broadcasting )
|
||||
auth | user | corp_apis ( View APIs, and jackKnife, of all members in user's corp. )
|
||||
auth | user | alliance_apis ( View APIs, and jackKnife, of all member in user's alliance member corps. )
|
||||
auth | user | timer_management ( Access to create and remove timers )
|
||||
auth | user | timer_view ( Access to timerboard to view timers )
|
||||
auth | user | srp_management ( Allows for an individual to create and remove srp fleets and fleet data )
|
||||
auth | user | sigtracker_management ( Allows for an individual to create and remove signitures )
|
||||
auth | user | sigtracker_view ( Allows for an individual view signitures )
|
||||
auth | user | optimer_management ( Allows for an individual to create and remove fleet operations )
|
||||
auth | user | optimer_view ( Allows for an individual view fleet operations )
|
||||
auth | user | logging_notifications ( Generate notifications from logging )
|
||||
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
|
||||
auth | user | human_resources ( View applications to user's corp )
|
||||
hrapplications | application | delete_application ( Can delete applications )
|
||||
hrapplications | application | accept_application ( Can accept applications )
|
||||
hrapplications | application | reject_application ( Can reject applications )
|
||||
hrapplications | application | view_apis ( Can see applicant's API keys )
|
||||
hrapplications | applicationcomment | add_applicationcomment ( Can comment on applications )
|
||||
Main features:
|
||||
|
||||
Vagrant Instructions:
|
||||
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
|
||||
Copy the scripts to the root directory before running
|
||||
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
|
||||
|
||||
Active Developers:
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
|
||||
Adarnof
|
||||
basraah
|
||||
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
|
||||
Beta Testers/ Bug Fixers:
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
TrentBartlem ( Testing and Bug Fixes )
|
||||
IskFiend ( Bug Fixes and Server Configuration )
|
||||
Mr McClain (Bug Fixes and server configuration )
|
||||
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
|
||||
|
||||
Special Thanks:
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.
|
||||
## Screenshot
|
||||
|
||||
Here is an example of the Alliance Auth web site with some plug-ins apps and services enabled:
|
||||
|
||||

|
||||
|
||||
## Support
|
||||
|
||||
[Get help on Discord](https://discord.gg/fjnHAmk) or submit an [issue](https://gitlab.com/allianceauth/allianceauth/issues).
|
||||
|
||||
## Development Team
|
||||
|
||||
### Active Developers
|
||||
|
||||
- [Aaron Kable](https://gitlab.com/aaronkable/)
|
||||
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||
- [Erik Kalkoken](https://gitlab.com/ErikKalkoken/)
|
||||
|
||||
### Former Developers
|
||||
|
||||
- [Adarnof](https://gitlab.com/adarnof/)
|
||||
|
||||
### Beta Testers / Bug Fixers
|
||||
|
||||
- [ghoti](https://gitlab.com/ChainsawMcGinny/)
|
||||
- [kaezon](https://github.com/kaezon/)
|
||||
- [mmolitor87](https://gitlab.com/mmolitor87/)
|
||||
- [orbitroom](https://github.com/orbitroom/)
|
||||
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||
- [tehfiend](https://github.com/tehfiend/)
|
||||
|
||||
Special thanks to [Nikdoof](https://github.com/nikdoof/), as his [auth](https://github.com/nikdoof/test-auth) was the foundation for the original work on this project.
|
||||
|
||||
## Contributing
|
||||
|
||||
Alliance Auth is maintained and developed by the community and we welcome every contribution!
|
||||
|
||||
To see what needs to be worked on please review our issue list or chat with our active developers on Discord.
|
||||
|
||||
Also, please make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
||||
|
||||
In addition to the core AA system we also very much welcome contributions to our growing list of 3rd party services and plugin apps. Please see [AA Community Creations](https://gitlab.com/allianceauth/community-creations) for details.
|
||||
|
||||
2
alliance_auth/.gitignore
vendored
2
alliance_auth/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
/settings.py
|
||||
!/*.example
|
||||
@@ -1,17 +0,0 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import os
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'alliance_auth.settings')
|
||||
|
||||
from django.conf import settings # noqa
|
||||
|
||||
app = Celery('alliance_auth')
|
||||
|
||||
# Using a string here means the worker don't have to serialize
|
||||
# the configuration object to child processes.
|
||||
app.config_from_object('django.conf:settings')
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
@@ -1,707 +0,0 @@
|
||||
"""
|
||||
Django settings for alliance_auth project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.10.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.contrib import messages
|
||||
from celery.schedules import crontab
|
||||
|
||||
# Celery configuration
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = ''
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = 'True' == os.environ.get('AA_DEBUG','True')
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'django_celery_beat',
|
||||
'bootstrapform',
|
||||
'authentication',
|
||||
'services',
|
||||
'eveonline',
|
||||
'groupmanagement',
|
||||
'hrapplications',
|
||||
'timerboard',
|
||||
'srp',
|
||||
'optimer',
|
||||
'corputils',
|
||||
'fleetactivitytracking',
|
||||
'fleetup',
|
||||
'notifications',
|
||||
'esi',
|
||||
'permissions_tool',
|
||||
'geelweb.django.navhelper',
|
||||
'bootstrap_pagination',
|
||||
'captcha',
|
||||
|
||||
# Services
|
||||
'services.modules.mumble',
|
||||
'services.modules.discord',
|
||||
'services.modules.discourse',
|
||||
'services.modules.ipboard',
|
||||
'services.modules.ips4',
|
||||
'services.modules.market',
|
||||
'services.modules.openfire',
|
||||
'services.modules.seat',
|
||||
'services.modules.smf',
|
||||
'services.modules.phpbb3',
|
||||
'services.modules.xenforo',
|
||||
'services.modules.teamspeak3',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'alliance_auth.urls'
|
||||
|
||||
LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'customization/templates'),
|
||||
os.path.join(BASE_DIR, 'stock/templates'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'services.context_processors.auth_settings',
|
||||
'notifications.context_processors.user_notification_count',
|
||||
'authentication.context_processors.states',
|
||||
'authentication.context_processors.membership_state',
|
||||
'groupmanagement.context_processors.can_manage_groups',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'alliance_auth.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_auth',
|
||||
'USER': os.environ.get('AA_DB_DEFAULT_USER', 'allianceserver'),
|
||||
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_DEFAULT_PORT', '3306'),
|
||||
},
|
||||
}
|
||||
|
||||
# If you have run the authentication.0013_service_modules migration
|
||||
# you will need to set this to True in order to install service modules
|
||||
# which were involved in that migration after it has been run.
|
||||
# If you are on a fresh install with no existing database you can safely
|
||||
# set this to True
|
||||
# If you have not run the authentication.0013_service_modules migration
|
||||
# leave this set to False.
|
||||
SERVICES_MIGRATED = False
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
LOGIN_URL = 'auth_login_user'
|
||||
|
||||
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en-us')
|
||||
|
||||
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.10/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, "customization/static"),
|
||||
os.path.join(BASE_DIR, "stock/static"),
|
||||
)
|
||||
|
||||
# Bootstrap messaging css workaround
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger'
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": "redis://127.0.0.1:6379/1",
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Google Recaptcha
|
||||
|
||||
CAPTCHA_ENABLED = False
|
||||
|
||||
RECAPTCHA_PUBLIC_KEY = 'MyRecaptchaKey'
|
||||
RECAPTCHA_PRIVATE_KEY = 'MyRecaptchaPrivateKey'
|
||||
|
||||
NOCAPTCHA = True
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Auth configuration starts here
|
||||
##
|
||||
#####################################################
|
||||
|
||||
|
||||
#########################
|
||||
# CELERY SCHEDULED TASKS
|
||||
#########################
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'run_api_refresh': {
|
||||
'task': 'eveonline.tasks.run_api_refresh',
|
||||
'schedule': crontab(minute=0, hour="*/3"),
|
||||
},
|
||||
'run_corp_update': {
|
||||
'task': 'eveonline.tasks.run_corp_update',
|
||||
'schedule': crontab(minute=0, hour="*/2"),
|
||||
},
|
||||
'update_all_corpstats': {
|
||||
'task': 'corputils.tasks.update_all_corpstats',
|
||||
'schedule': crontab(minute=0, hour="*/6"),
|
||||
},
|
||||
}
|
||||
|
||||
#################
|
||||
# EMAIL SETTINGS
|
||||
#################
|
||||
# DOMAIN - The alliance auth domain_url
|
||||
# EMAIL_HOST - SMTP Server URL
|
||||
# EMAIL_PORT - SMTP Server PORT
|
||||
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
|
||||
# EMAIL_HOST_PASSWORD - Email Password
|
||||
# EMAIL_USE_TLS - Set to use TLS encryption
|
||||
#################
|
||||
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
|
||||
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.gmail.com')
|
||||
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
|
||||
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
|
||||
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
|
||||
|
||||
####################
|
||||
# Front Page Links
|
||||
####################
|
||||
# KILLBOARD_URL - URL for your killboard. Blank to hide link
|
||||
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
|
||||
# FORUM_URL - URL for your forums. Blank to hide link
|
||||
# SITE_NAME - Name of the auth site.
|
||||
####################
|
||||
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
|
||||
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
|
||||
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
|
||||
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Alliance Auth')
|
||||
|
||||
###################
|
||||
# SSO Settings
|
||||
###################
|
||||
# Get client ID and client secret from registering an app at
|
||||
# https://developers.eveonline.com/
|
||||
# Callback URL should be https://example.com/sso/callback
|
||||
###################
|
||||
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
|
||||
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
|
||||
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
|
||||
|
||||
#########################
|
||||
# Default Group Settings
|
||||
#########################
|
||||
# DEFAULT_AUTH_GROUP - Default group members are put in
|
||||
# DEFAULT_BLUE_GROUP - Default group for blue members
|
||||
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
|
||||
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
|
||||
#########################
|
||||
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
|
||||
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
|
||||
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
|
||||
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
|
||||
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
|
||||
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
|
||||
|
||||
#########################
|
||||
# Tenant Configuration
|
||||
#########################
|
||||
# CORP_IDS - A list of corporation IDs to treat as members.
|
||||
# ALLIANCE_IDS - A list of alliance IDs to treat as members.
|
||||
# Any corps in a specified alliance will be treated as members, so do not include them in CORP_IDS
|
||||
#########################
|
||||
CORP_IDS = []
|
||||
ALLIANCE_IDS = []
|
||||
|
||||
#########################
|
||||
# Standings Configuration
|
||||
#########################
|
||||
# Add a corp API key to add blue standings to grant access.
|
||||
# CORP_API_ID - Set this to the api id for the corp API key
|
||||
# CORP_API_VCODE - Set this to the api vcode for the corp API key
|
||||
# BLUE_STANDING - The lowest standings value to consider blue
|
||||
# STANDING_LEVEL - The level of standings to query. Accepted values are 'corp' and 'alliance'.
|
||||
# BLUE_CORP_IDS - A list of corps to remain blue regardless of in-game standings
|
||||
# BLUE_ALLIANCE_IDS - A list of alliances to remain blue regardless of in-game standings
|
||||
########################
|
||||
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
|
||||
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
|
||||
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
|
||||
STANDING_LEVEL = os.environ.get('AA_STANDING_LEVEL', 'corp')
|
||||
BLUE_CORP_IDS = []
|
||||
BLUE_ALLIANCE_IDS = []
|
||||
|
||||
########################
|
||||
# API Configuration
|
||||
########################
|
||||
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
|
||||
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
|
||||
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
|
||||
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
|
||||
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
|
||||
# Requires SSO to be configured.
|
||||
#######################
|
||||
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
|
||||
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
|
||||
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
|
||||
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'False')
|
||||
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
|
||||
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
|
||||
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
|
||||
|
||||
#######################
|
||||
# EVE Provider Settings
|
||||
#######################
|
||||
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
|
||||
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
|
||||
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
|
||||
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
|
||||
#
|
||||
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
|
||||
#######################
|
||||
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', '')
|
||||
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', '')
|
||||
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', '')
|
||||
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', '')
|
||||
|
||||
#####################
|
||||
# Alliance Market
|
||||
#####################
|
||||
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://example.com/market')
|
||||
MARKET_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_market',
|
||||
'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver-market'),
|
||||
'PASSWORD': os.environ.get('AA_DB_MARKET_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_MARKET_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_MARKET_PORT', '3306'),
|
||||
}
|
||||
|
||||
#####################
|
||||
# HR Configuration
|
||||
#####################
|
||||
# API_KEY_AUDIT_URL - URL for viewing API keys.
|
||||
# Other URLs are supported. Use string formatting notation {} with parameter names api_id, vcode, pk
|
||||
# Example URL formats are shown below:
|
||||
# Old Jacknife: 'https://example.com/jacknife/eveapi/audit.php?usid={api_id}&apik={vcode}'
|
||||
# New Jacknife: 'https://example.com/jacknife/?usid={api_id}&apik={vcode}'
|
||||
# SeAT: 'https://seat.example.com/api-key/detail/{api_id}'
|
||||
# Django Admin: '/admin/eveonline/eveapikeypair/{pk}/change'
|
||||
# Leave blank for the verification code to be shown in a popup on click.
|
||||
#####################
|
||||
API_KEY_AUDIT_URL = os.environ.get('AA_API_KEY_AUDIT_URL', '')
|
||||
|
||||
#####################
|
||||
# Forum Configuration
|
||||
#####################
|
||||
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
|
||||
# IPBOARD_APIKEY - Api key to interact with ipboard
|
||||
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
|
||||
#####################
|
||||
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
|
||||
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
|
||||
IPBOARD_APIMODULE = 'aa'
|
||||
|
||||
########################
|
||||
# XenForo Configuration
|
||||
########################
|
||||
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
|
||||
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
|
||||
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
|
||||
#####################
|
||||
|
||||
######################
|
||||
# Jabber Configuration
|
||||
######################
|
||||
# JABBER_URL - Jabber address url
|
||||
# JABBER_PORT - Jabber service portal
|
||||
# JABBER_SERVER - Jabber server url
|
||||
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
|
||||
# Please use http with 9090 or https with 9091
|
||||
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
|
||||
# BROADCAST_USER - Broadcast user JID
|
||||
# BROADCAST_USER_PASSWORD - Broadcast user password
|
||||
######################
|
||||
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
|
||||
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
|
||||
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
|
||||
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
|
||||
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
|
||||
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
|
||||
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
|
||||
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
|
||||
|
||||
######################################
|
||||
# Mumble Configuration
|
||||
######################################
|
||||
# MUMBLE_URL - Mumble server host
|
||||
# Do not include leading http:// or mumble://
|
||||
######################################
|
||||
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
|
||||
|
||||
######################################
|
||||
# PHPBB3 Configuration
|
||||
######################################
|
||||
PHPBB3_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_forum',
|
||||
'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver-phpbb3'),
|
||||
'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'),
|
||||
}
|
||||
|
||||
######################################
|
||||
# Teamspeak3 Configuration
|
||||
######################################
|
||||
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
|
||||
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
|
||||
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
|
||||
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
|
||||
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
|
||||
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
|
||||
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
|
||||
######################################
|
||||
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
|
||||
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
|
||||
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
|
||||
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
|
||||
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
|
||||
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
|
||||
|
||||
######################################
|
||||
# Discord Configuration
|
||||
######################################
|
||||
# DISCORD_GUILD_ID - ID of the guild to manage
|
||||
# DISCORD_BOT_TOKEN - oauth token of the app bot user
|
||||
# DISCORD_INVITE_CODE - invite code to the server
|
||||
# DISCORD_APP_ID - oauth app client ID
|
||||
# DISCORD_APP_SECRET - oauth app secret
|
||||
# DISCORD_CALLBACK_URL - oauth callback url
|
||||
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
|
||||
######################################
|
||||
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '')
|
||||
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '')
|
||||
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', '')
|
||||
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '')
|
||||
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
|
||||
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
|
||||
|
||||
######################################
|
||||
# Discourse Configuration
|
||||
######################################
|
||||
# DISCOURSE_URL - Web address of the forums (no trailing slash)
|
||||
# DISCOURSE_API_USERNAME - API account username
|
||||
# DISCOURSE_API_KEY - API Key
|
||||
# DISCOURSE_SSO_SECRET - SSO secret key
|
||||
######################################
|
||||
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', '')
|
||||
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
|
||||
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
|
||||
DISCOURSE_SSO_SECRET = os.environ.get('AA_DISCOURSE_SSO_SECRET', '')
|
||||
|
||||
#####################################
|
||||
# IPS4 Configuration
|
||||
#####################################
|
||||
# IPS4_URL - base url of the IPS4 install (no trailing slash)
|
||||
# IPS4_API_KEY - API key provided by IPS4
|
||||
#####################################
|
||||
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
|
||||
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
|
||||
IPS4_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_ips4',
|
||||
'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver-ips4'),
|
||||
'PASSWORD': os.environ.get('AA_DB_IPS4_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_IPS4_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_IPS4_PORT', '3306'),
|
||||
}
|
||||
|
||||
#####################################
|
||||
# SEAT Configuration
|
||||
#####################################
|
||||
# SEAT_URL - base url of the seat install (no trailing slash)
|
||||
# SEAT_XTOKEN - API key X-Token provided by SeAT
|
||||
#####################################
|
||||
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
|
||||
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', '')
|
||||
|
||||
######################################
|
||||
# SMF Configuration
|
||||
######################################
|
||||
SMF_URL = os.environ.get('AA_SMF_URL', 'https://example.com')
|
||||
SMF_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_smf',
|
||||
'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver-smf'),
|
||||
'PASSWORD': os.environ.get('AA_DB_SMF_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_SMF_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_SMF_PORT', '3306'),
|
||||
}
|
||||
|
||||
######################################
|
||||
# Fleet-Up Configuration
|
||||
######################################
|
||||
# FLEETUP_APP_KEY - The app key from http://fleet-up.com/Api/MyApps
|
||||
# FLEETUP_USER_ID - The user id from http://fleet-up.com/Api/MyKeys
|
||||
# FLEETUP_API_ID - The API id from http://fleet-up.com/Api/MyKeys
|
||||
# FLEETUP_GROUP_ID - The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
|
||||
######################################
|
||||
FLEETUP_APP_KEY = os.environ.get('AA_FLEETUP_APP_KEY', '')
|
||||
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
|
||||
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
|
||||
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
|
||||
|
||||
#####################################
|
||||
# Logging Configuration
|
||||
#####################################
|
||||
# Set log_file and console level to desired state:
|
||||
# DEBUG - basically stack trace, explains every step
|
||||
# INFO - model creation, deletion, updates, etc
|
||||
# WARN - unexpected function outcomes that do not impact user
|
||||
# ERROR - unexcpeted function outcomes which prevent user from achieving desired outcome
|
||||
# EXCEPTION - something critical went wrong, unhandled
|
||||
#####################################
|
||||
# Recommended level for log_file is INFO, console is DEBUG
|
||||
# Change log level of individual apps below to narrow your debugging
|
||||
#####################################
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
|
||||
'datefmt' : "%d/%b/%Y %H:%M:%S"
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'log_file': {
|
||||
'level': 'INFO', # edit this line to change logging level to file
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': os.path.join(BASE_DIR,'log/allianceauth.log'),
|
||||
'formatter': 'verbose',
|
||||
'maxBytes': 1024*1024*5, # edit this line to change max log file size
|
||||
'backupCount': 5, # edit this line to change number of log backups
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG', # edit this line to change logging level to console
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'notifications': { # creates notifications for users with logging_notifications permission
|
||||
'level': 'ERROR', # edit this line to change logging level to notifications
|
||||
'class': 'notifications.handlers.NotificationHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'authentication': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'celerytask': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'eveonline': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'groupmanagement': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'hrapplications': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'portal': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'registration': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'services': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'srp': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'timerboard': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'sigtracker': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'optimer': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'corputils': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'fleetactivitytracking': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'fleetup': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'util': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# Conditionally add databases only if configured
|
||||
if 'services.modules.phpbb3' in INSTALLED_APPS:
|
||||
DATABASES['phpbb3'] = PHPBB3_DB
|
||||
if 'services.modules.smf' in INSTALLED_APPS:
|
||||
DATABASES['smf'] = SMF_DB
|
||||
if 'services.modules.market' in INSTALLED_APPS:
|
||||
DATABASES['market'] = MARKET_DB
|
||||
if 'services.modules.ips4' in INSTALLED_APPS:
|
||||
DATABASES['ips4'] = IPS4_DB
|
||||
|
||||
# Ensure corp/alliance IDs are expected types
|
||||
STR_CORP_IDS = [str(id) for id in CORP_IDS]
|
||||
STR_ALLIANCE_IDS = [str(id) for id in ALLIANCE_IDS]
|
||||
STR_BLUE_CORP_IDS = [str(id) for id in BLUE_CORP_IDS]
|
||||
STR_BLUE_ALLIANCE_IDS = [str(id) for id in BLUE_ALLIANCE_IDS]
|
||||
|
||||
# Conditionally add periodic tasks for services if installed
|
||||
if 'services.modules.teamspeak3' in INSTALLED_APPS:
|
||||
CELERYBEAT_SCHEDULE['run_ts3_group_update'] = {
|
||||
'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
|
||||
if 'services.modules.seat' in INSTALLED_APPS:
|
||||
CELERYBEAT_SCHEDULE['run_seat_api_sync'] = {
|
||||
'task': 'services.modules.seat.tasks.run_api_sync',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db.models.signals import m2m_changed, pre_save
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
|
||||
from services.signals import m2m_changed_user_groups, pre_save_user
|
||||
from services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions
|
||||
from authentication.signals import pre_save_auth_state
|
||||
|
||||
from authentication.tasks import make_member, make_blue
|
||||
from authentication.models import AuthServicesInfo
|
||||
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
|
||||
|
||||
from eveonline.models import EveCharacter
|
||||
|
||||
|
||||
class AuthUtils:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _create_user(username):
|
||||
return User.objects.create(username=username)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, disconnect_signals=False):
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = NONE_STATE
|
||||
user.authservicesinfo.save()
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def create_member(cls, username):
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = MEMBER_STATE
|
||||
user.authservicesinfo.save()
|
||||
make_member(user.authservicesinfo)
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def create_blue(cls, username):
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = BLUE_STATE
|
||||
user.authservicesinfo.save()
|
||||
make_blue(user.authservicesinfo)
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def disconnect_signals(cls):
|
||||
m2m_changed.disconnect(m2m_changed_user_groups, sender=User.groups.through)
|
||||
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||
pre_save.disconnect(pre_save_user, sender=User)
|
||||
pre_save.disconnect(pre_save_auth_state, sender=AuthServicesInfo)
|
||||
|
||||
@classmethod
|
||||
def connect_signals(cls):
|
||||
m2m_changed.connect(m2m_changed_user_groups, sender=User.groups.through)
|
||||
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||
pre_save.connect(pre_save_user, sender=User)
|
||||
pre_save.connect(pre_save_auth_state, sender=AuthServicesInfo)
|
||||
|
||||
@classmethod
|
||||
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
|
||||
alliance_name=''):
|
||||
EveCharacter.objects.create(
|
||||
character_id=character_id,
|
||||
character_name=name,
|
||||
corporation_id=corp_id,
|
||||
corporation_name=corp_name,
|
||||
corporation_ticker=corp_ticker,
|
||||
alliance_id=alliance_id,
|
||||
alliance_name=alliance_name,
|
||||
api_id='1234',
|
||||
user=user
|
||||
)
|
||||
AuthServicesInfo.objects.update_or_create(user=user, defaults={'main_char_id': character_id})
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
for group in groups:
|
||||
for perm in perms:
|
||||
group.permissions.add(perm)
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
@@ -1,556 +0,0 @@
|
||||
"""
|
||||
Alliance Auth Test Suite Django settings.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
import alliance_auth
|
||||
|
||||
# Use nose to run all tests
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
NOSE_ARGS = [
|
||||
#'--with-coverage',
|
||||
#'--cover-package=',
|
||||
#'--exe', # If your tests need this to be found/run, check they py files are not chmodded +x
|
||||
]
|
||||
|
||||
# Celery configuration
|
||||
CELERY_ALWAYS_EAGER = True # Forces celery to run locally for testing
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(alliance_auth.__file__)))
|
||||
|
||||
SECRET_KEY = 'testing only'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'django_celery_beat',
|
||||
'bootstrapform',
|
||||
'authentication',
|
||||
'services',
|
||||
'eveonline',
|
||||
'groupmanagement',
|
||||
'hrapplications',
|
||||
'timerboard',
|
||||
'srp',
|
||||
'optimer',
|
||||
'corputils',
|
||||
'fleetactivitytracking',
|
||||
'fleetup',
|
||||
'notifications',
|
||||
'esi',
|
||||
'permissions_tool',
|
||||
'geelweb.django.navhelper',
|
||||
'bootstrap_pagination',
|
||||
'services.modules.mumble',
|
||||
'services.modules.discord',
|
||||
'services.modules.discourse',
|
||||
'services.modules.ipboard',
|
||||
'services.modules.ips4',
|
||||
'services.modules.market',
|
||||
'services.modules.openfire',
|
||||
'services.modules.seat',
|
||||
'services.modules.smf',
|
||||
'services.modules.phpbb3',
|
||||
'services.modules.xenforo',
|
||||
'services.modules.teamspeak3',
|
||||
'django_nose',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'alliance_auth.urls'
|
||||
|
||||
LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'customization/templates'),
|
||||
os.path.join(BASE_DIR, 'stock/templates'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'services.context_processors.auth_settings',
|
||||
'notifications.context_processors.user_notification_count',
|
||||
'authentication.context_processors.states',
|
||||
'authentication.context_processors.membership_state',
|
||||
'groupmanagement.context_processors.can_manage_groups',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'alliance_auth',
|
||||
'USER': os.environ.get('AA_DB_DEFAULT_USER', None),
|
||||
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', None),
|
||||
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', None)
|
||||
},
|
||||
}
|
||||
|
||||
LOGIN_URL = 'auth_login_user'
|
||||
|
||||
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en')
|
||||
|
||||
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, "customization/static"),
|
||||
os.path.join(BASE_DIR, "stock/static"),
|
||||
)
|
||||
|
||||
# Bootstrap messaging css workaround
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger'
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Auth configuration starts here
|
||||
##
|
||||
#####################################################
|
||||
|
||||
###########################
|
||||
# ALLIANCE / CORP TOGGLE
|
||||
###########################
|
||||
# Specifies to run membership checks against corp or alliance
|
||||
# Set to FALSE for alliance
|
||||
# Set to TRUE for corp
|
||||
###########################
|
||||
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
|
||||
|
||||
#################
|
||||
# EMAIL SETTINGS
|
||||
#################
|
||||
# DOMAIN - The alliance auth domain_url
|
||||
# EMAIL_HOST - SMTP Server URL
|
||||
# EMAIL_PORT - SMTP Server PORT
|
||||
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
|
||||
# EMAIL_HOST_PASSWORD - Email Password
|
||||
# EMAIL_USE_TLS - Set to use TLS encryption
|
||||
#################
|
||||
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
|
||||
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.example.com')
|
||||
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
|
||||
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
|
||||
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
|
||||
|
||||
####################
|
||||
# Front Page Links
|
||||
####################
|
||||
# KILLBOARD_URL - URL for your killboard. Blank to hide link
|
||||
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
|
||||
# FORUM_URL - URL for your forums. Blank to hide link
|
||||
# SITE_NAME - Name of the auth site.
|
||||
####################
|
||||
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
|
||||
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
|
||||
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
|
||||
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Test Alliance Auth')
|
||||
|
||||
###################
|
||||
# SSO Settings
|
||||
###################
|
||||
# Optional SSO.
|
||||
# Get client ID and client secret from registering an app at
|
||||
# https://developers.eveonline.com/
|
||||
# Callback URL should be http://mydomain.com/sso/callback
|
||||
# Leave callback blank to hide SSO button on login page
|
||||
###################
|
||||
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
|
||||
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
|
||||
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
|
||||
|
||||
#########################
|
||||
# Default Group Settings
|
||||
#########################
|
||||
# DEFAULT_AUTH_GROUP - Default group members are put in
|
||||
# DEFAULT_BLUE_GROUP - Default group for blue members
|
||||
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
|
||||
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
|
||||
#########################
|
||||
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
|
||||
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
|
||||
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
|
||||
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
|
||||
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
|
||||
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
|
||||
|
||||
#########################
|
||||
# Corp Configuration
|
||||
#########################
|
||||
# If running in alliance mode, the following should be for the executor corp#
|
||||
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
|
||||
# CORP_NAME - Set this to your Corporation Name
|
||||
# CORP_API_ID - Set this to the api id for the corp API key
|
||||
# CORP_API_VCODE - Set this to the api vcode for the corp API key
|
||||
########################
|
||||
CORP_ID = os.environ.get('AA_CORP_ID', '1234')
|
||||
CORP_NAME = os.environ.get('AA_CORP_NAME', 'Alliance Auth Test Corp')
|
||||
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
|
||||
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
|
||||
|
||||
#########################
|
||||
# Alliance Configuration
|
||||
#########################
|
||||
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
|
||||
# ALLIANCE_NAME - Set this to your Alliance Name
|
||||
########################
|
||||
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '12345')
|
||||
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', 'Alliance Auth Test Alliance')
|
||||
|
||||
########################
|
||||
# API Configuration
|
||||
########################
|
||||
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
|
||||
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
|
||||
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
|
||||
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
|
||||
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
|
||||
# Requires SSO to be configured.
|
||||
#######################
|
||||
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
|
||||
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
|
||||
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
|
||||
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'True')
|
||||
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
|
||||
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
|
||||
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
|
||||
|
||||
#######################
|
||||
# EVE Provider Settings
|
||||
#######################
|
||||
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
|
||||
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
|
||||
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
|
||||
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
|
||||
#
|
||||
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
|
||||
#######################
|
||||
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'xml')
|
||||
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'xml')
|
||||
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'xml')
|
||||
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', 'xml')
|
||||
|
||||
#####################
|
||||
# Alliance Market
|
||||
#####################
|
||||
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market')
|
||||
|
||||
#####################
|
||||
# HR Configuration
|
||||
#####################
|
||||
# JACK_KNIFE_URL - Url for the audit page of API Jack knife
|
||||
# Should seriously replace with your own.
|
||||
#####################
|
||||
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://example.com/eveapi/audit.php')
|
||||
|
||||
#####################
|
||||
# Forum Configuration
|
||||
#####################
|
||||
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
|
||||
# IPBOARD_APIKEY - Api key to interact with ipboard
|
||||
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
|
||||
#####################
|
||||
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
|
||||
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
|
||||
IPBOARD_APIMODULE = 'aa'
|
||||
|
||||
########################
|
||||
# XenForo Configuration
|
||||
########################
|
||||
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
|
||||
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
|
||||
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
|
||||
#####################
|
||||
|
||||
######################
|
||||
# Jabber Configuration
|
||||
######################
|
||||
# JABBER_URL - Jabber address url
|
||||
# JABBER_PORT - Jabber service portal
|
||||
# JABBER_SERVER - Jabber server url
|
||||
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
|
||||
# Please use http with 9090 or https with 9091
|
||||
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
|
||||
# BROADCAST_USER - Broadcast user JID
|
||||
# BROADCAST_USER_PASSWORD - Broadcast user password
|
||||
######################
|
||||
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
|
||||
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
|
||||
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
|
||||
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
|
||||
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
|
||||
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
|
||||
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
|
||||
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
|
||||
|
||||
######################################
|
||||
# Mumble Configuration
|
||||
######################################
|
||||
# MUMBLE_URL - Mumble server url
|
||||
# MUMBLE_SERVER_ID - Mumble server id
|
||||
######################################
|
||||
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
|
||||
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
|
||||
|
||||
######################################
|
||||
# PHPBB3 Configuration
|
||||
######################################
|
||||
|
||||
######################################
|
||||
# Teamspeak3 Configuration
|
||||
######################################
|
||||
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
|
||||
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
|
||||
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
|
||||
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
|
||||
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
|
||||
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
|
||||
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
|
||||
######################################
|
||||
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
|
||||
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
|
||||
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
|
||||
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
|
||||
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
|
||||
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
|
||||
|
||||
######################################
|
||||
# Discord Configuration
|
||||
######################################
|
||||
# DISCORD_GUILD_ID - ID of the guild to manage
|
||||
# DISCORD_BOT_TOKEN - oauth token of the app bot user
|
||||
# DISCORD_INVITE_CODE - invite code to the server
|
||||
# DISCORD_APP_ID - oauth app client ID
|
||||
# DISCORD_APP_SECRET - oauth app secret
|
||||
# DISCORD_CALLBACK_URL - oauth callback url
|
||||
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
|
||||
######################################
|
||||
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '0118999')
|
||||
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', 'bottoken')
|
||||
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', 'invitecode')
|
||||
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', 'appid')
|
||||
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', 'secret')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
|
||||
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
|
||||
|
||||
######################################
|
||||
# Discourse Configuration
|
||||
######################################
|
||||
# DISCOURSE_URL - Web address of the forums (no trailing slash)
|
||||
# DISCOURSE_API_USERNAME - API account username
|
||||
# DISCOURSE_API_KEY - API Key
|
||||
# DISCOURSE_SSO_SECRET - SSO secret key
|
||||
######################################
|
||||
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', 'https://example.com')
|
||||
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
|
||||
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
|
||||
DISCOURSE_SSO_SECRET = 'd836444a9e4084d5b224a60c208dce14'
|
||||
# Example secret from https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
|
||||
|
||||
#####################################
|
||||
# IPS4 Configuration
|
||||
#####################################
|
||||
# IPS4_URL - base url of the IPS4 install (no trailing slash)
|
||||
# IPS4_API_KEY - API key provided by IPS4
|
||||
#####################################
|
||||
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
|
||||
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
|
||||
|
||||
#####################################
|
||||
# SEAT Configuration
|
||||
#####################################
|
||||
# SEAT_URL - base url of the seat install (no trailing slash)
|
||||
# SEAT_XTOKEN - API key X-Token provided by SeAT
|
||||
#####################################
|
||||
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
|
||||
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', 'tokentokentoken')
|
||||
|
||||
######################################
|
||||
# SMF Configuration
|
||||
######################################
|
||||
SMF_URL = os.environ.get('AA_SMF_URL', '')
|
||||
|
||||
######################################
|
||||
# Fleet-Up Configuration
|
||||
######################################
|
||||
# FLEETUP_APP_KEY - The app key from http://fleet-up.com/Api/MyApps
|
||||
# FLEETUP_USER_ID - The user id from http://fleet-up.com/Api/MyKeys
|
||||
# FLEETUP_API_ID - The API id from http://fleet-up.com/Api/MyKeys
|
||||
# FLEETUP_GROUP_ID - The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
|
||||
######################################
|
||||
FLEETUP_APP_KEY = os.environ.get('AA_FLEETUP_APP_KEY', '')
|
||||
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
|
||||
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
|
||||
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
|
||||
|
||||
PASSWORD_HASHERS = [
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
]
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
|
||||
'datefmt' : "%d/%b/%Y %H:%M:%S"
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG', # edit this line to change logging level to console
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'notifications': { # creates notifications for users with logging_notifications permission
|
||||
'level': 'ERROR', # edit this line to change logging level to notifications
|
||||
'class': 'notifications.handlers.NotificationHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'authentication': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'celerytask': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'eveonline': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'groupmanagement': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'hrapplications': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'portal': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'registration': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'services': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'srp': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'timerboard': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'sigtracker': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'optimer': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'corputils': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'fleetactivitytracking': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'util': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
LOGGING = None # Comment out to enable logging for debugging
|
||||
@@ -1,222 +0,0 @@
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import admin
|
||||
import django.contrib.auth.views
|
||||
import authentication.views
|
||||
import eveonline.views
|
||||
import services.views
|
||||
import groupmanagement.views
|
||||
import optimer.views
|
||||
import timerboard.views
|
||||
import fleetactivitytracking.views
|
||||
import fleetup.urls
|
||||
import srp.views
|
||||
import notifications.views
|
||||
import hrapplications.views
|
||||
import corputils.urls
|
||||
import esi.urls
|
||||
import permissions_tool.urls
|
||||
from alliance_auth import NAME
|
||||
|
||||
admin.site.site_header = NAME
|
||||
|
||||
from alliance_auth.hooks import get_hooks
|
||||
|
||||
# Functional/Untranslated URL's
|
||||
urlpatterns = [
|
||||
# Locale
|
||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||
|
||||
# Admin urls
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
|
||||
# SSO
|
||||
url(r'^sso/', include(esi.urls, namespace='esi')),
|
||||
url(r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
|
||||
|
||||
# Index
|
||||
url(_(r'^$'), authentication.views.index_view, name='auth_index'),
|
||||
|
||||
# Authentication
|
||||
url(r'^logout_user/', authentication.views.logout_user, name='auth_logout_user'),
|
||||
|
||||
# Eve Online
|
||||
url(r'^main_character_change/(\w+)/$', eveonline.views.main_character_change,
|
||||
name='auth_main_character_change'),
|
||||
url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'),
|
||||
|
||||
# SRP URLS
|
||||
url(r'^srp_fleet_remove/(\w+)$', srp.views.srp_fleet_remove, name='auth_srp_fleet_remove'),
|
||||
url(r'^srp_fleet_disable/(\w+)$', srp.views.srp_fleet_disable, name='auth_srp_fleet_disable'),
|
||||
url(r'^srp_fleet_enable/(\w+)$', srp.views.srp_fleet_enable, name='auth_srp_fleet_enable'),
|
||||
url(r'^srp_fleet_mark_completed/(\w+)', srp.views.srp_fleet_mark_completed,
|
||||
name='auth_srp_fleet_mark_completed'),
|
||||
url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted,
|
||||
name='auth_srp_fleet_mark_uncompleted'),
|
||||
url(r'^srp_request_remove/', srp.views.srp_request_remove,
|
||||
name="auth_srp_request_remove"),
|
||||
url(r'srp_request_approve/', srp.views.srp_request_approve,
|
||||
name='auth_srp_request_approve'),
|
||||
url(r'srp_request_reject/', srp.views.srp_request_reject,
|
||||
name='auth_srp_request_reject'),
|
||||
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount,
|
||||
name="auth_srp_request_update_amount"),
|
||||
|
||||
# Notifications
|
||||
url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'),
|
||||
url(r'^notifications/mark_all_read/$', notifications.views.mark_all_read, name='auth_mark_all_notifications_read'),
|
||||
url(r'^notifications/delete_all_read/$', notifications.views.delete_all_read,
|
||||
name='auth_delete_all_read_notifications'),
|
||||
]
|
||||
|
||||
# User viewed/translated URLS
|
||||
urlpatterns += i18n_patterns(
|
||||
|
||||
# Fleetup
|
||||
url(r'^fleetup/', include(fleetup.urls.urlpatterns)),
|
||||
|
||||
# Authentication
|
||||
url(_(r'^login_user/'), authentication.views.login_user, name='auth_login_user'),
|
||||
url(_(r'^register_user/'), authentication.views.register_user_view, name='auth_register_user'),
|
||||
|
||||
url(_(r'^user/password/$'), django.contrib.auth.views.password_change, name='password_change'),
|
||||
url(_(r'^user/password/done/$'), django.contrib.auth.views.password_change_done,
|
||||
name='password_change_done'),
|
||||
url(_(r'^user/password/reset/$'), django.contrib.auth.views.password_reset,
|
||||
name='password_reset'),
|
||||
url(_(r'^user/password/password/reset/done/$'), django.contrib.auth.views.password_reset_done,
|
||||
name='password_reset_done'),
|
||||
url(_(r'^user/password/reset/complete/$'), django.contrib.auth.views.password_reset_complete,
|
||||
name='password_reset_complete'),
|
||||
url(_(r'^user/password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$'),
|
||||
django.contrib.auth.views.password_reset_confirm, name='password_reset_confirm'),
|
||||
|
||||
# Portal Urls
|
||||
url(_(r'^dashboard/$'), eveonline.views.dashboard_view, name='auth_dashboard'),
|
||||
url(_(r'^help/$'), authentication.views.help_view, name='auth_help'),
|
||||
|
||||
# Eveonline Urls
|
||||
url(_(r'^add_api_key/'), eveonline.views.add_api_key, name='auth_add_api_key'),
|
||||
url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'),
|
||||
url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'),
|
||||
url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'),
|
||||
|
||||
# Corputils
|
||||
url(_(r'^corpstats/'), include(corputils.urls, namespace='corputils')),
|
||||
|
||||
# Group management
|
||||
url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'),
|
||||
url(_(r'^group/management/'), groupmanagement.views.group_management,
|
||||
name='auth_group_management'),
|
||||
url(_(r'^group/membership/$'), groupmanagement.views.group_membership,
|
||||
name='auth_group_membership'),
|
||||
url(_(r'^group/membership/(\w+)/$'), groupmanagement.views.group_membership_list,
|
||||
name='auth_group_membership_list'),
|
||||
url(_(r'^group/membership/(\w+)/remove/(\w+)/$'), groupmanagement.views.group_membership_remove,
|
||||
name='auth_group_membership_remove'),
|
||||
url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add,
|
||||
name='auth_group_request_add'),
|
||||
url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request,
|
||||
name='auth_group_accept_request'),
|
||||
url(_(r'^group/request/reject/(\w+)'), groupmanagement.views.group_reject_request,
|
||||
name='auth_group_reject_request'),
|
||||
|
||||
url(_(r'^group/request_leave/(\w+)'), groupmanagement.views.group_request_leave,
|
||||
name='auth_group_request_leave'),
|
||||
url(_(r'group/leave_request/accept/(\w+)'), groupmanagement.views.group_leave_accept_request,
|
||||
name='auth_group_leave_accept_request'),
|
||||
url(_(r'^group/leave_request/reject/(\w+)'), groupmanagement.views.group_leave_reject_request,
|
||||
name='auth_group_leave_reject_request'),
|
||||
|
||||
# HR Application Management
|
||||
url(_(r'^hr_application_management/'), hrapplications.views.hr_application_management_view,
|
||||
name="auth_hrapplications_view"),
|
||||
url(_(r'^hr_application_create/$'), hrapplications.views.hr_application_create_view,
|
||||
name="auth_hrapplication_create_view"),
|
||||
url(_(r'^hr_application_create/(\d+)'), hrapplications.views.hr_application_create_view,
|
||||
name="auth_hrapplication_create_view"),
|
||||
url(_(r'^hr_application_remove/(\w+)'), hrapplications.views.hr_application_remove,
|
||||
name="auth_hrapplication_remove"),
|
||||
url(_(r'hr_application_view/(\w+)'), hrapplications.views.hr_application_view,
|
||||
name="auth_hrapplication_view"),
|
||||
url(_(r'hr_application_personal_view/(\w+)'), hrapplications.views.hr_application_personal_view,
|
||||
name="auth_hrapplication_personal_view"),
|
||||
url(_(r'hr_application_personal_removal/(\w+)'),
|
||||
hrapplications.views.hr_application_personal_removal,
|
||||
name="auth_hrapplication_personal_removal"),
|
||||
url(_(r'hr_application_approve/(\w+)'), hrapplications.views.hr_application_approve,
|
||||
name="auth_hrapplication_approve"),
|
||||
url(_(r'hr_application_reject/(\w+)'), hrapplications.views.hr_application_reject,
|
||||
name="auth_hrapplication_reject"),
|
||||
url(_(r'hr_application_search/'), hrapplications.views.hr_application_search,
|
||||
name="auth_hrapplication_search"),
|
||||
url(_(r'hr_mark_in_progress/(\w+)'), hrapplications.views.hr_application_mark_in_progress,
|
||||
name="auth_hrapplication_mark_in_progress"),
|
||||
|
||||
# Fleet Operations Timers
|
||||
url(_(r'^optimer/$'), optimer.views.optimer_view, name='auth_optimer_view'),
|
||||
url(_(r'^add_optimer/$'), optimer.views.add_optimer_view, name='auth_add_optimer_view'),
|
||||
url(_(r'^remove_optimer/(\w+)'), optimer.views.remove_optimer, name='auth_remove_optimer'),
|
||||
url(_(r'^edit_optimer/(\w+)$'), optimer.views.edit_optimer, name='auth_edit_optimer'),
|
||||
|
||||
# Service Urls
|
||||
url(_(r'^services/$'), services.views.services_view, name='auth_services'),
|
||||
|
||||
# Timer URLS
|
||||
url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'),
|
||||
url(_(r'^add_timer/$'), timerboard.views.add_timer_view, name='auth_add_timer_view'),
|
||||
url(_(r'^remove_timer/(\w+)'), timerboard.views.remove_timer, name='auth_remove_timer'),
|
||||
url(_(r'^edit_timer/(\w+)$'), timerboard.views.edit_timer, name='auth_edit_timer'),
|
||||
|
||||
# SRP URLS
|
||||
url(_(r'^srp/$'), srp.views.srp_management, name='auth_srp_management_view'),
|
||||
url(_(r'^srp_all/$'), srp.views.srp_management_all, name='auth_srp_management_all_view'),
|
||||
url(_(r'^srp_fleet_view/(\w+)$'), srp.views.srp_fleet_view, name='auth_srp_fleet_view'),
|
||||
url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_view'),
|
||||
url(_(r'^srp_fleet_edit/(\w+)$'), srp.views.srp_fleet_edit_view, name='auth_srp_fleet_edit_view'),
|
||||
url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'),
|
||||
|
||||
# Tools
|
||||
url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view,
|
||||
name='auth_fleet_format_tool_view'),
|
||||
|
||||
# Notifications
|
||||
url(_(r'^notifications/$'), notifications.views.notification_list, name='auth_notification_list'),
|
||||
url(_(r'^notifications/(\w+)/$'), notifications.views.notification_view, name='auth_notification_view'),
|
||||
|
||||
# FleetActivityTracking (FAT)
|
||||
url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'),
|
||||
url(r'^fat/statistics/$', fleetactivitytracking.views.fatlink_statistics_view, name='auth_fatlink_view_statistics'),
|
||||
url(r'^fat/statistics/corp/(\w+)$', fleetactivitytracking.views.fatlink_statistics_corp_view, name='auth_fatlink_view_statistics_corp'),
|
||||
url(r'^fat/statistics/corp/(?P<corpid>\w+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/', fleetactivitytracking.views.fatlink_statistics_corp_view,
|
||||
name='auth_fatlink_view_statistics_corp_month'),
|
||||
url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view,
|
||||
name='auth_fatlink_view_statistics_month'),
|
||||
url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
|
||||
name='auth_fatlink_view_personal_statistics'),
|
||||
url(r'^fat/user/statistics/(?P<year>[0-9]+)/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
|
||||
name='auth_fatlink_view_personal_statistics_year'),
|
||||
url(r'^fat/user/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
|
||||
fleetactivitytracking.views.fatlink_monthly_personal_statistics_view,
|
||||
name='auth_fatlink_view_personal_statistics_month'),
|
||||
url(r'^fat/user/(?P<char_id>[0-9]+)/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
|
||||
fleetactivitytracking.views.fatlink_monthly_personal_statistics_view,
|
||||
name='auth_fatlink_view_user_statistics_month'),
|
||||
url(r'^fat/create/$', fleetactivitytracking.views.create_fatlink_view, name='auth_create_fatlink_view'),
|
||||
url(r'^fat/modify/$', fleetactivitytracking.views.modify_fatlink_view, name='auth_modify_fatlink_view'),
|
||||
url(r'^fat/modify/(?P<hash>[a-zA-Z0-9_-]+)/([a-z0-9_-]+)$',
|
||||
fleetactivitytracking.views.modify_fatlink_view),
|
||||
url(r'^fat/link/$', fleetactivitytracking.views.fatlink_view, name='auth_click_fatlink_view'),
|
||||
url(r'^fat/link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
|
||||
fleetactivitytracking.views.click_fatlink_view),
|
||||
|
||||
url(r'^permissions/', include(permissions_tool.urls))
|
||||
)
|
||||
|
||||
# Append hooked service urls
|
||||
services = get_hooks('services_hook')
|
||||
for svc in services:
|
||||
urlpatterns += svc().urlpatterns
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
"""
|
||||
WSGI config for alliance_auth project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alliance_auth.settings")
|
||||
|
||||
# virtualenv wrapper, uncomment below to activate
|
||||
# activate_env=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'env/bin/activate_this.py')
|
||||
# execfile(activate_env, dict(__file__=activate_env))
|
||||
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -1,8 +1,6 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
from .celeryapp import app as celery_app # noqa
|
||||
|
||||
__version__ = '1.15.2'
|
||||
__version__ = '2.6.4'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
5
allianceauth/apps.py
Normal file
5
allianceauth/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AllianceAuthConfig(AppConfig):
|
||||
name = 'allianceauth'
|
||||
1
allianceauth/authentication/__init__.py
Normal file
1
allianceauth/authentication/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'allianceauth.authentication.apps.AuthenticationConfig'
|
||||
609
allianceauth/authentication/admin.py
Normal file
609
allianceauth/authentication/admin.py
Normal file
@@ -0,0 +1,609 @@
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import User as BaseUser, \
|
||||
Permission as BasePermission, Group
|
||||
from django.db.models import Q, F
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, \
|
||||
post_delete, m2m_changed
|
||||
from django.db.models.functions import Lower
|
||||
from django.dispatch import receiver
|
||||
from django.forms import ModelForm
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from allianceauth.authentication.models import State, get_guest_state,\
|
||||
CharacterOwnership, UserProfile, OwnershipRecord
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.eveonline.tasks import update_character
|
||||
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
def make_service_hooks_update_groups_action(service):
|
||||
"""
|
||||
Make a admin action for the given service
|
||||
:param service: services.hooks.ServicesHook
|
||||
:return: fn to update services groups for the selected users
|
||||
"""
|
||||
def update_service_groups(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.update_groups(user)
|
||||
|
||||
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
|
||||
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
|
||||
return update_service_groups
|
||||
|
||||
|
||||
def make_service_hooks_sync_nickname_action(service):
|
||||
"""
|
||||
Make a sync_nickname admin action for the given service
|
||||
:param service: services.hooks.ServicesHook
|
||||
:return: fn to sync nickname for the selected users
|
||||
"""
|
||||
def sync_nickname(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.sync_nickname(user)
|
||||
|
||||
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
|
||||
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
|
||||
return sync_nickname
|
||||
|
||||
|
||||
class QuerysetModelForm(ModelForm):
|
||||
# allows specifying FK querysets through kwarg
|
||||
def __init__(self, querysets=None, *args, **kwargs):
|
||||
querysets = querysets or {}
|
||||
super().__init__(*args, **kwargs)
|
||||
for field, qs in querysets.items():
|
||||
self.fields[field].queryset = qs
|
||||
|
||||
|
||||
class UserProfileInline(admin.StackedInline):
|
||||
model = UserProfile
|
||||
readonly_fields = ('state',)
|
||||
form = QuerysetModelForm
|
||||
verbose_name = ''
|
||||
verbose_name_plural = 'Profile'
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
# main_character field can only show current value or unclaimed alts
|
||||
# if superuser, allow selecting from any unclaimed main
|
||||
query = Q()
|
||||
if obj and obj.profile.main_character:
|
||||
query |= Q(pk=obj.profile.main_character_id)
|
||||
if request.user.is_superuser:
|
||||
query |= Q(userprofile__isnull=True)
|
||||
else:
|
||||
query |= Q(character_ownership__user=obj)
|
||||
qs = EveCharacter.objects.filter(query)
|
||||
formset = super().get_formset(request, obj=obj, **kwargs)
|
||||
|
||||
def get_kwargs(self, index):
|
||||
return {'querysets': {'main_character': EveCharacter.objects.filter(query)}}
|
||||
formset.get_form_kwargs = get_kwargs
|
||||
return formset
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
def user_profile_pic(obj):
|
||||
"""profile pic column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists (requires CSS)
|
||||
"""
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if user_obj.profile.main_character:
|
||||
return format_html(
|
||||
'<img src="{}" class="img-circle">',
|
||||
user_obj.profile.main_character.portrait_url(size=32)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
user_profile_pic.short_description = ''
|
||||
|
||||
|
||||
def user_username(obj):
|
||||
"""user column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
link = reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label,
|
||||
type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if user_obj.profile.main_character:
|
||||
return format_html(
|
||||
'<strong><a href="{}">{}</a></strong><br>{}',
|
||||
link,
|
||||
user_obj.username,
|
||||
user_obj.profile.main_character.character_name
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<strong><a href="{}">{}</a></strong>',
|
||||
link,
|
||||
user_obj.username,
|
||||
)
|
||||
|
||||
user_username.short_description = 'user / main'
|
||||
user_username.admin_order_field = 'username'
|
||||
|
||||
|
||||
def user_main_organization(obj):
|
||||
"""main organization column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if not user_obj.profile.main_character:
|
||||
result = None
|
||||
else:
|
||||
corporation = user_obj.profile.main_character.corporation_name
|
||||
if user_obj.profile.main_character.alliance_id:
|
||||
result = format_html('{}<br>{}',
|
||||
corporation,
|
||||
user_obj.profile.main_character.alliance_name
|
||||
)
|
||||
else:
|
||||
result = corporation
|
||||
return result
|
||||
|
||||
user_main_organization.short_description = 'Corporation / Alliance (Main)'
|
||||
user_main_organization.admin_order_field = \
|
||||
'profile__main_character__corporation_name'
|
||||
|
||||
|
||||
class MainCorporationsFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to filter on corporations from mains only
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
title = 'corporation'
|
||||
parameter_name = 'main_corporation_id__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = EveCharacter.objects\
|
||||
.exclude(userprofile=None)\
|
||||
.values('corporation_id', 'corporation_name')\
|
||||
.distinct()\
|
||||
.order_by(Lower('corporation_name'))
|
||||
return tuple(
|
||||
[(x['corporation_id'], x['corporation_name']) for x in qs]
|
||||
)
|
||||
|
||||
def queryset(self, request, qs):
|
||||
if self.value() is None:
|
||||
return qs.all()
|
||||
else:
|
||||
if qs.model == User:
|
||||
return qs\
|
||||
.filter(profile__main_character__corporation_id=\
|
||||
self.value())
|
||||
else:
|
||||
return qs\
|
||||
.filter(user__profile__main_character__corporation_id=\
|
||||
self.value())
|
||||
|
||||
|
||||
class MainAllianceFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to filter on alliances from mains only
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
title = 'alliance'
|
||||
parameter_name = 'main_alliance_id__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = EveCharacter.objects\
|
||||
.exclude(alliance_id=None)\
|
||||
.exclude(userprofile=None)\
|
||||
.values('alliance_id', 'alliance_name')\
|
||||
.distinct()\
|
||||
.order_by(Lower('alliance_name'))
|
||||
return tuple(
|
||||
[(x['alliance_id'], x['alliance_name']) for x in qs]
|
||||
)
|
||||
|
||||
def queryset(self, request, qs):
|
||||
if self.value() is None:
|
||||
return qs.all()
|
||||
else:
|
||||
if qs.model == User:
|
||||
return qs\
|
||||
.filter(profile__main_character__alliance_id=self.value())
|
||||
else:
|
||||
return qs\
|
||||
.filter(user__profile__main_character__alliance_id=\
|
||||
self.value())
|
||||
|
||||
|
||||
def update_main_character_model(modeladmin, request, queryset):
|
||||
tasks_count = 0
|
||||
for obj in queryset:
|
||||
if obj.profile.main_character:
|
||||
update_character.delay(obj.profile.main_character.character_id)
|
||||
tasks_count += 1
|
||||
|
||||
modeladmin.message_user(
|
||||
request,
|
||||
'Update from ESI started for {} characters'.format(tasks_count)
|
||||
)
|
||||
|
||||
update_main_character_model.short_description = \
|
||||
'Update main character model from ESI'
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""Extending Django's UserAdmin model
|
||||
|
||||
Behavior of groups and characters columns can be configured via settings
|
||||
|
||||
"""
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
class RealGroupsFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to get groups w/o Autogroups"""
|
||||
title = 'group'
|
||||
parameter_name = 'group_id__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = Group.objects.all().order_by(Lower('name'))
|
||||
if _has_auto_groups:
|
||||
qs = qs\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)
|
||||
return tuple([(x.pk, x.name) for x in qs])
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() is None:
|
||||
return queryset.all()
|
||||
else:
|
||||
return queryset.filter(groups__pk=self.value())
|
||||
|
||||
def get_actions(self, request):
|
||||
actions = super(BaseUserAdmin, self).get_actions(request)
|
||||
|
||||
actions[update_main_character_model.__name__] = (
|
||||
update_main_character_model,
|
||||
update_main_character_model.__name__,
|
||||
update_main_character_model.short_description
|
||||
)
|
||||
|
||||
for hook in get_hooks('services_hook'):
|
||||
svc = hook()
|
||||
# Check update_groups is redefined/overloaded
|
||||
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
|
||||
action = make_service_hooks_update_groups_action(svc)
|
||||
actions[action.__name__] = (
|
||||
action,
|
||||
action.__name__,
|
||||
action.short_description
|
||||
)
|
||||
|
||||
# Create sync nickname action if service implements it
|
||||
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
|
||||
action = make_service_hooks_sync_nickname_action(svc)
|
||||
actions[action.__name__] = (
|
||||
action, action.__name__,
|
||||
action.short_description
|
||||
)
|
||||
return actions
|
||||
|
||||
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
|
||||
"""converts list of strings into HTML with cutoff and tooltip"""
|
||||
items_truncated_str = ', '.join(my_items[:max_items])
|
||||
if not my_items:
|
||||
result = None
|
||||
elif len(my_items) <= max_items:
|
||||
result = items_truncated_str
|
||||
else:
|
||||
items_truncated_str += ', (...)'
|
||||
items_all_str = ', '.join(my_items)
|
||||
result = format_html(
|
||||
'<span data-tooltip="{}" class="tooltip">{}</span>',
|
||||
items_all_str,
|
||||
items_truncated_str
|
||||
)
|
||||
return result
|
||||
|
||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
||||
|
||||
ordering = ('username', )
|
||||
list_select_related = True
|
||||
show_full_result_count = True
|
||||
|
||||
list_display = (
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
'_state',
|
||||
'_groups',
|
||||
user_main_organization,
|
||||
'_characters',
|
||||
'is_active',
|
||||
'date_joined',
|
||||
'_role'
|
||||
)
|
||||
list_display_links = None
|
||||
|
||||
list_filter = (
|
||||
'profile__state',
|
||||
RealGroupsFilter,
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
'is_active',
|
||||
'date_joined',
|
||||
'is_staff',
|
||||
'is_superuser'
|
||||
)
|
||||
search_fields = (
|
||||
'username',
|
||||
'character_ownerships__character__character_name'
|
||||
)
|
||||
|
||||
def _characters(self, obj):
|
||||
my_characters = [
|
||||
x.character.character_name
|
||||
for x in CharacterOwnership.objects\
|
||||
.filter(user=obj)\
|
||||
.order_by('character__character_name')\
|
||||
.select_related()
|
||||
]
|
||||
return self._list_2_html_w_tooltips(
|
||||
my_characters,
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
|
||||
)
|
||||
|
||||
_characters.short_description = 'characters'
|
||||
|
||||
|
||||
def _state(self, obj):
|
||||
return obj.profile.state.name
|
||||
|
||||
_state.short_description = 'state'
|
||||
_state.admin_order_field = 'profile__state'
|
||||
|
||||
def _groups(self, obj):
|
||||
if not _has_auto_groups:
|
||||
my_groups = [x.name for x in obj.groups.order_by('name')]
|
||||
else:
|
||||
my_groups = [
|
||||
x.name for x in obj.groups\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)\
|
||||
.order_by('name')
|
||||
]
|
||||
|
||||
return self._list_2_html_w_tooltips(
|
||||
my_groups,
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
|
||||
)
|
||||
|
||||
_groups.short_description = 'groups'
|
||||
|
||||
def _role(self, obj):
|
||||
if obj.is_superuser:
|
||||
role = 'Superuser'
|
||||
elif obj.is_staff:
|
||||
role = 'Staff'
|
||||
else:
|
||||
role = 'User'
|
||||
return role
|
||||
|
||||
_role.short_description = 'role'
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_user')
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.add_user')
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.delete_user')
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
"""overriding this formfield to have sorted lists in the form"""
|
||||
if db_field.name == "groups":
|
||||
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
|
||||
@admin.register(State)
|
||||
class StateAdmin(admin.ModelAdmin):
|
||||
list_select_related = True
|
||||
list_display = ('name', 'priority', '_user_count')
|
||||
|
||||
def _user_count(self, obj):
|
||||
return obj.userprofile_set.all().count()
|
||||
_user_count.short_description = 'Users'
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('name', 'permissions', 'priority'),
|
||||
}),
|
||||
('Membership', {
|
||||
'fields': (
|
||||
'public',
|
||||
'member_characters',
|
||||
'member_corporations',
|
||||
'member_alliances'
|
||||
),
|
||||
})
|
||||
)
|
||||
filter_horizontal = [
|
||||
'member_characters',
|
||||
'member_corporations',
|
||||
'member_alliances',
|
||||
'permissions'
|
||||
]
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
"""overriding this formfield to have sorted lists in the form"""
|
||||
if db_field.name == "member_characters":
|
||||
kwargs["queryset"] = EveCharacter.objects.all()\
|
||||
.order_by(Lower('character_name'))
|
||||
elif db_field.name == "member_corporations":
|
||||
kwargs["queryset"] = EveCorporationInfo.objects.all()\
|
||||
.order_by(Lower('corporation_name'))
|
||||
elif db_field.name == "member_alliances":
|
||||
kwargs["queryset"] = EveAllianceInfo.objects.all()\
|
||||
.order_by(Lower('alliance_name'))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if obj == get_guest_state():
|
||||
return False
|
||||
return super(StateAdmin, self).has_delete_permission(request, obj=obj)
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
if obj == get_guest_state():
|
||||
return (
|
||||
(None, {
|
||||
'fields': ('permissions', 'priority'),
|
||||
}),
|
||||
)
|
||||
return super(StateAdmin, self).get_fieldsets(request, obj=obj)
|
||||
|
||||
|
||||
class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
list_select_related = True
|
||||
list_display = (
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
user_main_organization,
|
||||
'character',
|
||||
)
|
||||
search_fields = (
|
||||
'user__username',
|
||||
'character__character_name',
|
||||
'character__corporation_name',
|
||||
'character__alliance_name'
|
||||
)
|
||||
list_filter = (
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.pk:
|
||||
return 'owner_hash', 'character'
|
||||
return tuple()
|
||||
|
||||
|
||||
@admin.register(OwnershipRecord)
|
||||
class OwnershipRecordAdmin(BaseOwnershipAdmin):
|
||||
list_display = BaseOwnershipAdmin.list_display + ('created',)
|
||||
|
||||
|
||||
@admin.register(CharacterOwnership)
|
||||
class CharacterOwnershipAdmin(BaseOwnershipAdmin):
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class PermissionAdmin(admin.ModelAdmin):
|
||||
actions = None
|
||||
readonly_fields = [field.name for field in BasePermission._meta.fields]
|
||||
list_display = ('admin_name', 'name', 'codename', 'content_type')
|
||||
list_filter = ('content_type__app_label',)
|
||||
|
||||
@staticmethod
|
||||
def admin_name(obj):
|
||||
return str(obj)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_module_permission(self, request):
|
||||
return True
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
# can see list but not edit it
|
||||
return not obj
|
||||
|
||||
|
||||
# Hack to allow registration of django.contrib.auth models in our authentication app
|
||||
class User(BaseUser):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = BaseUser._meta.verbose_name
|
||||
verbose_name_plural = BaseUser._meta.verbose_name_plural
|
||||
|
||||
|
||||
class Permission(BasePermission):
|
||||
class Meta:
|
||||
proxy = True
|
||||
verbose_name = BasePermission._meta.verbose_name
|
||||
verbose_name_plural = BasePermission._meta.verbose_name_plural
|
||||
|
||||
|
||||
try:
|
||||
admin.site.unregister(BaseUser)
|
||||
finally:
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(Permission, PermissionAdmin)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=User)
|
||||
def redirect_pre_save(sender, signal=None, *args, **kwargs):
|
||||
pre_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def redirect_post_save(sender, signal=None, *args, **kwargs):
|
||||
post_save.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=User)
|
||||
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
|
||||
pre_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=User)
|
||||
def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
post_delete.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.groups.through)
|
||||
def redirect_m2m_changed_groups(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=User.user_permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseUser, *args, **kwargs)
|
||||
46
allianceauth/authentication/app_settings.py
Normal file
46
allianceauth/authentication/app_settings.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def _clean_setting(
|
||||
name: str,
|
||||
default_value: object,
|
||||
min_value: int = None,
|
||||
max_value: int = None,
|
||||
required_type: type = None
|
||||
):
|
||||
"""cleans the input for a custom setting
|
||||
|
||||
Will use `default_value` if settings does not exit or has the wrong type
|
||||
or is outside define boundaries (for int only)
|
||||
|
||||
Need to define `required_type` if `default_value` is `None`
|
||||
|
||||
Will assume `min_value` of 0 for int (can be overriden)
|
||||
|
||||
Returns cleaned value for setting
|
||||
"""
|
||||
if default_value is None and not required_type:
|
||||
raise ValueError('You must specify a required_type for None defaults')
|
||||
|
||||
if not required_type:
|
||||
required_type = type(default_value)
|
||||
|
||||
if min_value is None and required_type == int:
|
||||
min_value = 0
|
||||
|
||||
if (hasattr(settings, name)
|
||||
and isinstance(getattr(settings, name), required_type)
|
||||
and (min_value is None or getattr(settings, name) >= min_value)
|
||||
and (max_value is None or getattr(settings, name) <= max_value)
|
||||
):
|
||||
return getattr(settings, name)
|
||||
else:
|
||||
return default_value
|
||||
|
||||
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
|
||||
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_GROUPS', 10)
|
||||
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
|
||||
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)
|
||||
|
||||
12
allianceauth/authentication/apps.py
Normal file
12
allianceauth/authentication/apps.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.apps import AppConfig
|
||||
from django.core.checks import register, Tags
|
||||
|
||||
|
||||
class AuthenticationConfig(AppConfig):
|
||||
name = 'allianceauth.authentication'
|
||||
label = 'authentication'
|
||||
|
||||
def ready(self):
|
||||
super(AuthenticationConfig, self).ready()
|
||||
from allianceauth.authentication import checks, signals
|
||||
register(Tags.security)(checks.check_login_scopes_setting)
|
||||
93
allianceauth/authentication/backends.py
Normal file
93
allianceauth/authentication/backends.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
import logging
|
||||
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StateBackend(ModelBackend):
|
||||
@staticmethod
|
||||
def _get_state_permissions(user_obj):
|
||||
profile_state_field = UserProfile._meta.get_field('state')
|
||||
user_state_query = 'state__%s__user' % profile_state_field.related_query_name()
|
||||
return Permission.objects.filter(**{user_state_query: user_obj})
|
||||
|
||||
def get_state_permissions(self, user_obj, obj=None):
|
||||
return self._get_permissions(user_obj, obj, 'state')
|
||||
|
||||
def get_all_permissions(self, user_obj, obj=None):
|
||||
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
|
||||
return set()
|
||||
if not hasattr(user_obj, '_perm_cache'):
|
||||
user_obj._perm_cache = self.get_user_permissions(user_obj)
|
||||
user_obj._perm_cache.update(self.get_group_permissions(user_obj))
|
||||
user_obj._perm_cache.update(self.get_state_permissions(user_obj))
|
||||
return user_obj._perm_cache
|
||||
|
||||
def authenticate(self, request=None, token=None, **credentials):
|
||||
if not token:
|
||||
return None
|
||||
try:
|
||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||
if ownership.owner_hash == token.character_owner_hash:
|
||||
logger.debug('Authenticating {0} by ownership of character {1}'.format(ownership.user, token.character_name))
|
||||
return ownership.user
|
||||
else:
|
||||
logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name))
|
||||
ownership.delete()
|
||||
return self.create_user(token)
|
||||
except CharacterOwnership.DoesNotExist:
|
||||
try:
|
||||
# insecure legacy main check for pre-sso registration auth installs
|
||||
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
|
||||
logger.debug('Authenticating {0} by their main character {1} without active ownership.'.format(profile.user, profile.main_character))
|
||||
# attach an ownership
|
||||
token.user = profile.user
|
||||
CharacterOwnership.objects.create_by_token(token)
|
||||
return profile.user
|
||||
except UserProfile.DoesNotExist:
|
||||
# now we check historical records to see if this is a returning user
|
||||
records = OwnershipRecord.objects.filter(owner_hash=token.character_owner_hash).filter(character__character_id=token.character_id)
|
||||
if records.exists():
|
||||
# we've seen this character owner before. Re-attach to their old user account
|
||||
user = records[0].user
|
||||
token.user = user
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
logger.debug('Authenticating {0} by matching owner hash record of character {1}'.format(user, co.character))
|
||||
if not user.profile.main_character:
|
||||
# set this as their main by default if they have none
|
||||
user.profile.main_character = co.character
|
||||
user.profile.save()
|
||||
return user
|
||||
logger.debug('Unable to authenticate character {0}. Creating new user.'.format(token.character_name))
|
||||
return self.create_user(token)
|
||||
|
||||
def create_user(self, token):
|
||||
username = self.iterate_username(token.character_name) # build unique username off character name
|
||||
user = User.objects.create_user(username, is_active=False) # prevent login until email set
|
||||
user.set_unusable_password() # prevent login via password
|
||||
user.save()
|
||||
token.user = user
|
||||
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
|
||||
user.profile.main_character = co.character # assign main character as token character
|
||||
user.profile.save()
|
||||
logger.debug('Created new user {0}'.format(user))
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def iterate_username(name):
|
||||
name = str.replace(name, "'", "")
|
||||
name = str.replace(name, ' ', '_')
|
||||
if User.objects.filter(username__startswith=name).exists():
|
||||
u = User.objects.filter(username__startswith=name)
|
||||
num = len(u)
|
||||
username = "%s_%s" % (name, num)
|
||||
while u.filter(username=username).exists():
|
||||
num += 1
|
||||
username = "%s_%s" % (name, num)
|
||||
else:
|
||||
username = name
|
||||
return username
|
||||
12
allianceauth/authentication/checks.py
Normal file
12
allianceauth/authentication/checks.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.core.checks import Error
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def check_login_scopes_setting(*args, **kwargs):
|
||||
errors = []
|
||||
try:
|
||||
assert settings.LOGIN_TOKEN_SCOPES
|
||||
except (AssertionError, AttributeError):
|
||||
errors.append(Error('LOGIN_TOKEN_SCOPES setting cannot be blank.',
|
||||
hint='SSO tokens used for logging in must require scopes to be refreshable.'))
|
||||
return errors
|
||||
68
allianceauth/authentication/decorators.py
Normal file
68
allianceauth/authentication/decorators.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from django.conf.urls import include
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from functools import wraps
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
|
||||
def user_has_main_character(user):
|
||||
return bool(user.profile.main_character)
|
||||
|
||||
|
||||
def decorate_url_patterns(urls, decorator):
|
||||
url_list, app_name, namespace = include(urls)
|
||||
|
||||
def process_patterns(url_patterns):
|
||||
for pattern in url_patterns:
|
||||
if hasattr(pattern, 'url_patterns'):
|
||||
# this is an include - apply to all nested patterns
|
||||
process_patterns(pattern.url_patterns)
|
||||
else:
|
||||
# this is a pattern
|
||||
pattern.callback = decorator(pattern.callback)
|
||||
|
||||
process_patterns(url_list)
|
||||
return url_list, app_name, namespace
|
||||
|
||||
|
||||
def main_character_required(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if user_has_main_character(request.user):
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
messages.error(request, _('A main character is required to perform that action. Add one below.'))
|
||||
return redirect('authentication:dashboard')
|
||||
return login_required(_wrapped_view)
|
||||
|
||||
|
||||
def permissions_required(perm, login_url=None, raise_exception=False):
|
||||
"""
|
||||
Decorator for views that checks whether a user has a particular permission
|
||||
enabled, redirecting to the log-in page if necessary.
|
||||
If the raise_exception parameter is given the PermissionDenied exception
|
||||
is raised.
|
||||
|
||||
This decorator is identical to the django permission_required except it
|
||||
allows for passing a tuple/list of perms that will return true if any one
|
||||
of them is present.
|
||||
"""
|
||||
def check_perms(user):
|
||||
if isinstance(perm, str):
|
||||
perms = (perm,)
|
||||
else:
|
||||
perms = perm
|
||||
# First check if the user has the permission (even anon users)
|
||||
for perm_ in perms:
|
||||
perm_ = (perm_,)
|
||||
if user.has_perms(perm_):
|
||||
return True
|
||||
# In case the 403 handler should be called raise the exception
|
||||
if raise_exception:
|
||||
raise PermissionDenied
|
||||
# As the last resort, show the login form
|
||||
return False
|
||||
return user_passes_test(check_perms, login_url=login_url)
|
||||
6
allianceauth/authentication/forms.py
Normal file
6
allianceauth/authentication/forms.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
email = forms.EmailField(label=_('Email'), max_length=254, required=True)
|
||||
14
allianceauth/authentication/hmac_urls.py
Normal file
14
allianceauth/authentication/hmac_urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.conf.urls import url, include
|
||||
|
||||
from allianceauth.authentication import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^activate/complete/$', views.activation_complete, name='registration_activation_complete'),
|
||||
# The activation key can make use of any character from the
|
||||
# URL-safe base64 alphabet, plus the colon as a separator.
|
||||
url(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
|
||||
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
|
||||
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
|
||||
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
|
||||
url(r'', include('registration.auth_urls')),
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from allianceauth.authentication.models import UserProfile
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Ensures all main characters have an active ownership'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
profiles = UserProfile.objects.filter(main_character__isnull=False).filter(
|
||||
main_character__character_ownership__isnull=True)
|
||||
if profiles.exists():
|
||||
for profile in profiles:
|
||||
self.stdout.write(self.style.ERROR(
|
||||
'{0} does not have an ownership. Resetting user {1} main character.'.format(profile.main_character,
|
||||
profile.user)))
|
||||
profile.main_character = None
|
||||
profile.save()
|
||||
self.stdout.write(self.style.WARNING('Reset {0} main characters.'.format(profiles.count())))
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS('All main characters have active ownership.'))
|
||||
75
allianceauth/authentication/managers.py
Executable file
75
allianceauth/authentication/managers.py
Executable file
@@ -0,0 +1,75 @@
|
||||
import logging
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Manager, QuerySet, Q
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def available_states_query(character):
|
||||
query = Q(public=True)
|
||||
if character.character_id:
|
||||
query |= Q(member_characters__character_id=character.character_id)
|
||||
if character.corporation_id:
|
||||
query |= Q(member_corporations__corporation_id=character.corporation_id)
|
||||
if character.alliance_id:
|
||||
query |= Q(member_alliances__alliance_id=character.alliance_id)
|
||||
return query
|
||||
|
||||
|
||||
class CharacterOwnershipManager(Manager):
|
||||
def create_by_token(self, token):
|
||||
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
|
||||
EveCharacter.objects.create_character(token.character_id)
|
||||
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user,
|
||||
owner_hash=token.character_owner_hash)
|
||||
|
||||
|
||||
class StateQuerySet(QuerySet):
|
||||
def available_to_character(self, character):
|
||||
return self.filter(available_states_query(character))
|
||||
|
||||
def available_to_user(self, user):
|
||||
if user.profile.main_character:
|
||||
return self.available_to_character(user.profile.main_character)
|
||||
else:
|
||||
return self.none()
|
||||
|
||||
def get_for_user(self, user):
|
||||
states = self.available_to_user(user)
|
||||
if states.exists():
|
||||
return states[0]
|
||||
else:
|
||||
from allianceauth.authentication.models import get_guest_state
|
||||
return get_guest_state()
|
||||
|
||||
def delete(self):
|
||||
with transaction.atomic():
|
||||
for state in self:
|
||||
for profile in state.userprofile_set.all():
|
||||
profile.assign_state(state=self.model.objects.exclude(pk=state.pk).get_for_user(profile.user))
|
||||
super(StateQuerySet, self).delete()
|
||||
|
||||
|
||||
class StateManager(Manager):
|
||||
def get_queryset(self):
|
||||
return StateQuerySet(self.model, using=self._db)
|
||||
|
||||
def available_to_character(self, character):
|
||||
return self.get_queryset().available_to_character(character)
|
||||
|
||||
def available_to_user(self, user):
|
||||
return self.get_queryset().available_to_user(user)
|
||||
|
||||
def get_for_character(self, character):
|
||||
states = self.get_queryset().available_to_character(character)
|
||||
if states.exists():
|
||||
return states[0]
|
||||
else:
|
||||
from allianceauth.authentication.models import get_guest_state
|
||||
return get_guest_state()
|
||||
|
||||
def get_for_user(self, user):
|
||||
return self.get_queryset().get_for_user(user)
|
||||
17
allianceauth/authentication/migrations/0008_set_state.py
Normal file
17
allianceauth/authentication/migrations/0008_set_state.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-12 13:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0007_remove_authservicesinfo_is_blue'),
|
||||
('eveonline', '0001_initial'),
|
||||
('auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-12-11 23:14
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0012_remove_add_delete_authservicesinfo_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove fields
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='discord_uid',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='discourse_enabled',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='forum_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ipboard_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ips4_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ips4_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='jabber_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='market_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='mumble_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='smf_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='teamspeak3_perm_key',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='teamspeak3_uid',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='xenforo_username',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.1 on 2016-09-09 23:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def create_permission(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(User)
|
||||
Permission.objects.get_or_create(codename="view_fleetup", content_type=ct, name="view_fleetup")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0013_service_modules'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_permission, migrations.RunPython.noop)
|
||||
]
|
||||
285
allianceauth/authentication/migrations/0015_user_profiles.py
Normal file
285
allianceauth/authentication/migrations/0015_user_profiles.py
Normal file
@@ -0,0 +1,285 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-22 23:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import allianceauth.authentication.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def create_guest_state(apps, schema_editor):
|
||||
State = apps.get_model('authentication', 'State')
|
||||
State.objects.update_or_create(name='Guest', defaults={'priority': 0, 'public': True})
|
||||
|
||||
|
||||
def create_member_state(apps, schema_editor):
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
State = apps.get_model('authentication', 'State')
|
||||
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
|
||||
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
|
||||
|
||||
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||
s = State.objects.update_or_create(name=member_state_name, defaults={'priority': 100, 'public': False})[0]
|
||||
try:
|
||||
# move group permissions to state
|
||||
g = Group.objects.get(name=member_state_name)
|
||||
[s.permissions.add(p.pk) for p in g.permissions.all()]
|
||||
g.delete()
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
|
||||
# auto-populate member IDs
|
||||
CORP_IDS = getattr(settings, 'CORP_IDS', [])
|
||||
ALLIANCE_IDS = getattr(settings, 'ALLIANCE_IDS', [])
|
||||
[s.member_corporations.add(c.pk) for c in EveCorporationInfo.objects.filter(corporation_id__in=CORP_IDS)]
|
||||
[s.member_alliances.add(a.pk) for a in EveAllianceInfo.objects.filter(alliance_id__in=ALLIANCE_IDS)]
|
||||
|
||||
|
||||
def create_member_group(apps, schema_editor):
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
State = apps.get_model('authentication', 'State')
|
||||
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||
|
||||
try:
|
||||
g, _ = Group.objects.get_or_create(name=member_state_name)
|
||||
# move permissions back
|
||||
state = State.objects.get(name=member_state_name)
|
||||
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
||||
|
||||
# move users back
|
||||
for profile in state.userprofile_set.all().select_related('user'):
|
||||
profile.user.groups.add(g.pk)
|
||||
except State.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def create_blue_state(apps, schema_editor):
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
State = apps.get_model('authentication', 'State')
|
||||
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
|
||||
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
|
||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||
|
||||
s = State.objects.update_or_create(name=blue_state_name, defaults={'priority': 50, 'public': False})[0]
|
||||
try:
|
||||
# move group permissions to state
|
||||
g = Group.objects.get(name=blue_state_name)
|
||||
[s.permissions.add(p.pk) for p in g.permissions.all()]
|
||||
g.delete()
|
||||
except Group.DoesNotExist:
|
||||
pass
|
||||
|
||||
# auto-populate blue member IDs
|
||||
BLUE_CORP_IDS = getattr(settings, 'BLUE_CORP_IDS', [])
|
||||
BLUE_ALLIANCE_IDS = getattr(settings, 'BLUE_ALLIANCE_IDS', [])
|
||||
[s.member_corporations.add(c.pk) for c in EveCorporationInfo.objects.filter(corporation_id__in=BLUE_CORP_IDS)]
|
||||
[s.member_alliances.add(a.pk) for a in EveAllianceInfo.objects.filter(alliance_id__in=BLUE_ALLIANCE_IDS)]
|
||||
|
||||
|
||||
def create_blue_group(apps, schema_editor):
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
State = apps.get_model('authentication', 'State')
|
||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||
|
||||
try:
|
||||
g, _ = Group.objects.get_or_create(name=blue_state_name)
|
||||
# move permissions back
|
||||
state = State.objects.get(name=blue_state_name)
|
||||
[g.permissions.add(p.pk) for p in state.permissions.all()]
|
||||
|
||||
# move users back
|
||||
for profile in state.userprofile_set.all().select_related('user'):
|
||||
profile.user.groups.add(g.pk)
|
||||
except State.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
def purge_tokens(apps, schema_editor):
|
||||
Token = apps.get_model('esi', 'Token')
|
||||
Token.objects.filter(refresh_token__isnull=True).delete()
|
||||
|
||||
|
||||
def populate_ownerships(apps, schema_editor):
|
||||
Token = apps.get_model('esi', 'Token')
|
||||
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
|
||||
|
||||
unique_character_owners = [t['character_id'] for t in
|
||||
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
|
||||
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id']).exists()]
|
||||
|
||||
tokens = Token.objects.filter(character_id__in=unique_character_owners)
|
||||
for c_id in unique_character_owners:
|
||||
# find newest refreshable token and use it as basis for CharacterOwnership
|
||||
ts = tokens.filter(character_id=c_id).exclude(refresh_token__isnull=True).order_by('created')
|
||||
if ts.exists():
|
||||
token = ts[0]
|
||||
char = EveCharacter.objects.get(character_id=token.character_id)
|
||||
CharacterOwnership.objects.create(user_id=token.user_id, character_id=char.id, owner_hash=token.character_owner_hash)
|
||||
|
||||
|
||||
def create_profiles(apps, schema_editor):
|
||||
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
|
||||
State = apps.get_model('authentication', 'State')
|
||||
UserProfile = apps.get_model('authentication', 'UserProfile')
|
||||
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
|
||||
|
||||
# grab AuthServicesInfo if they have a unique main_char_id and the EveCharacter exists
|
||||
unique_mains = [auth['main_char_id'] for auth in
|
||||
AuthServicesInfo.objects.exclude(main_char_id='').values('main_char_id').annotate(
|
||||
n=models.Count('main_char_id')) if
|
||||
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id']).exists()]
|
||||
|
||||
auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
|
||||
|
||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||
|
||||
states = {
|
||||
'Member': State.objects.get(name=member_state_name),
|
||||
'Blue': State.objects.get(name=blue_state_name),
|
||||
}
|
||||
guest_state = State.objects.get(name='Guest')
|
||||
|
||||
for auth in auths:
|
||||
# carry states and mains forward
|
||||
state = states.get(auth.state, guest_state)
|
||||
char = EveCharacter.objects.get(character_id=auth.main_char_id)
|
||||
UserProfile.objects.create(user=auth.user, state=state, main_character=char)
|
||||
for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'):
|
||||
# prepare empty profiles
|
||||
UserProfile.objects.create(user=auth.user, state=guest_state)
|
||||
|
||||
|
||||
def recreate_authservicesinfo(apps, schema_editor):
|
||||
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
|
||||
UserProfile = apps.get_model('authentication', 'UserProfile')
|
||||
User = apps.get_model('auth', 'User')
|
||||
|
||||
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
|
||||
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
|
||||
|
||||
states = {
|
||||
member_state_name: 'Member',
|
||||
blue_state_name: 'Blue',
|
||||
}
|
||||
|
||||
# recreate all missing AuthServicesInfo models
|
||||
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
|
||||
|
||||
# repopulate main characters
|
||||
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
|
||||
AuthServicesInfo.objects.update_or_create(user=profile.user,
|
||||
defaults={'main_char_id': profile.main_character.character_id})
|
||||
|
||||
# repopulate states we understand
|
||||
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
|
||||
state__name__in=[member_state_name, blue_state_name]).select_related('user', 'state'):
|
||||
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': states[profile.state.name]})
|
||||
|
||||
|
||||
def disable_passwords(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
for u in User.objects.exclude(is_staff=True):
|
||||
# remove passwords for non-staff users to prevent password-based authentication
|
||||
# set_unusable_password is unavailable in migrations because :reasons:
|
||||
u.password = make_password(None)
|
||||
u.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('eveonline', '0008_remove_apikeys'),
|
||||
('authentication', '0014_fleetup_permission'),
|
||||
('esi', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CharacterOwnership',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('owner_hash', models.CharField(max_length=28, unique=True)),
|
||||
('character', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownership', to='eveonline.EveCharacter')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownerships', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'default_permissions': ('change', 'delete'),
|
||||
'ordering': ['user', 'character__character_name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='State',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, unique=True)),
|
||||
('priority', models.IntegerField(help_text='Users get assigned the state with the highest priority available to them.', unique=True)),
|
||||
('public', models.BooleanField(default=False, help_text='Make this state available to any character.')),
|
||||
('member_alliances', models.ManyToManyField(blank=True, help_text='Alliances to whose members this state is available.', to='eveonline.EveAllianceInfo')),
|
||||
('member_characters', models.ManyToManyField(blank=True, help_text='Characters to which this state is available.', to='eveonline.EveCharacter')),
|
||||
('member_corporations', models.ManyToManyField(blank=True, help_text='Corporations to whose members this state is available.', to='eveonline.EveCorporationInfo')),
|
||||
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-priority'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('main_character', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eveonline.EveCharacter')),
|
||||
('state', models.ForeignKey(default=allianceauth.authentication.models.get_guest_state_pk, on_delete=django.db.models.deletion.SET_DEFAULT, to='authentication.State')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'default_permissions': ('change',),
|
||||
},
|
||||
),
|
||||
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
|
||||
migrations.RunPython(create_member_state, create_member_group),
|
||||
migrations.RunPython(create_blue_state, create_blue_group),
|
||||
migrations.RunPython(purge_tokens, migrations.RunPython.noop),
|
||||
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
|
||||
migrations.RunPython(create_profiles, recreate_authservicesinfo),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='user',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AuthServicesInfo',
|
||||
),
|
||||
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
|
||||
migrations.CreateModel(
|
||||
name='Permission',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'verbose_name': 'permission',
|
||||
'verbose_name_plural': 'permissions',
|
||||
},
|
||||
bases=('auth.permission',),
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.PermissionManager()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'proxy': True,
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
},
|
||||
bases=('auth.user',),
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 2.0.4 on 2018-04-14 18:28
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def create_initial_records(apps, schema_editor):
|
||||
OwnershipRecord = apps.get_model('authentication', 'OwnershipRecord')
|
||||
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
|
||||
|
||||
OwnershipRecord.objects.bulk_create([
|
||||
OwnershipRecord(user=o.user, character=o.character, owner_hash=o.owner_hash) for o in CharacterOwnership.objects.all()
|
||||
])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('eveonline', '0009_on_delete'),
|
||||
('authentication', '0015_user_profiles'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OwnershipRecord',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('owner_hash', models.CharField(db_index=True, max_length=28)),
|
||||
('created', models.DateTimeField(auto_now=True)),
|
||||
('character', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to='eveonline.EveCharacter')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
),
|
||||
migrations.RunPython(create_initial_records, migrations.RunPython.noop)
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def remove_permission(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(User)
|
||||
Permission.objects.filter(codename="view_fleetup", content_type=ct, name="view_fleetup").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0016_ownershiprecord'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_permission, migrations.RunPython.noop)
|
||||
]
|
||||
111
allianceauth/authentication/models.py
Executable file
111
allianceauth/authentication/models.py
Executable file
@@ -0,0 +1,111 @@
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.db import models, transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from .managers import CharacterOwnershipManager, StateManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class State(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True)
|
||||
permissions = models.ManyToManyField(Permission, blank=True)
|
||||
priority = models.IntegerField(unique=True,
|
||||
help_text="Users get assigned the state with the highest priority available to them.")
|
||||
|
||||
member_characters = models.ManyToManyField(EveCharacter, blank=True,
|
||||
help_text="Characters to which this state is available.")
|
||||
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
|
||||
help_text="Corporations to whose members this state is available.")
|
||||
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
|
||||
help_text="Alliances to whose members this state is available.")
|
||||
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
|
||||
|
||||
objects = StateManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['-priority']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def available_to_character(self, character):
|
||||
return self in State.objects.available_to_character(character)
|
||||
|
||||
def available_to_user(self, user):
|
||||
return self in State.objects.available_to_user(user)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
with transaction.atomic():
|
||||
for profile in self.userprofile_set.all():
|
||||
profile.assign_state(state=State.objects.exclude(pk=self.pk).get_for_user(profile.user))
|
||||
super(State, self).delete(**kwargs)
|
||||
|
||||
|
||||
def get_guest_state():
|
||||
try:
|
||||
return State.objects.get(name='Guest')
|
||||
except State.DoesNotExist:
|
||||
return State.objects.create(name='Guest', priority=0, public=True)
|
||||
|
||||
|
||||
def get_guest_state_pk():
|
||||
return get_guest_state().pk
|
||||
|
||||
|
||||
class UserProfile(models.Model):
|
||||
class Meta:
|
||||
default_permissions = ('change',)
|
||||
|
||||
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
|
||||
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
state = models.ForeignKey(State, on_delete=models.SET_DEFAULT, default=get_guest_state_pk)
|
||||
|
||||
def assign_state(self, state=None, commit=True):
|
||||
if not state:
|
||||
state = State.objects.get_for_user(self.user)
|
||||
if self.state != state:
|
||||
self.state = state
|
||||
if commit:
|
||||
logger.info('Updating {} state to {}'.format(self.user, self.state))
|
||||
self.save(update_fields=['state'])
|
||||
notify(self.user, _('State Changed'),
|
||||
_('Your user state has been changed to %(state)s') % ({'state': state}),
|
||||
'info')
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
state_changed.send(sender=self.__class__, user=self.user, state=self.state)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
|
||||
class CharacterOwnership(models.Model):
|
||||
class Meta:
|
||||
default_permissions = ('change', 'delete')
|
||||
ordering = ['user', 'character__character_name']
|
||||
|
||||
character = models.OneToOneField(EveCharacter, on_delete=models.CASCADE, related_name='character_ownership')
|
||||
owner_hash = models.CharField(max_length=28, unique=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
|
||||
|
||||
objects = CharacterOwnershipManager()
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s" % (self.user, self.character)
|
||||
|
||||
|
||||
class OwnershipRecord(models.Model):
|
||||
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records')
|
||||
owner_hash = models.CharField(max_length=28, db_index=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records')
|
||||
created = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created']
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s on %s" % (self.user, self.character, self.created)
|
||||
161
allianceauth/authentication/signals.py
Normal file
161
allianceauth/authentication/signals.py
Normal file
@@ -0,0 +1,161 @@
|
||||
import logging
|
||||
|
||||
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.dispatch import receiver, Signal
|
||||
from esi.models import Token
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
state_changed = Signal(providing_args=['user', 'state'])
|
||||
|
||||
|
||||
def trigger_state_check(state):
|
||||
# evaluate all current members to ensure they still have access
|
||||
for profile in state.userprofile_set.all():
|
||||
profile.assign_state()
|
||||
|
||||
# we may now be available to others with lower states
|
||||
check_states = State.objects.filter(priority__lt=state.priority)
|
||||
for profile in UserProfile.objects.filter(state__in=check_states):
|
||||
if state.available_to_user(profile.user):
|
||||
profile.state = state
|
||||
profile.save(update_fields=['state'])
|
||||
state_changed.send(sender=state.__class__, user=profile.user, state=state)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=State.member_characters.through)
|
||||
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member characters changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=State.member_corporations.through)
|
||||
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member corporations changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=State.member_alliances.through)
|
||||
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
|
||||
if action.startswith('post_'):
|
||||
logger.debug('State {} member alliances changed. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=State)
|
||||
def state_saved(sender, instance, *args, **kwargs):
|
||||
logger.debug('State {} saved. Re-evaluating membership.'.format(instance))
|
||||
trigger_state_check(instance)
|
||||
|
||||
|
||||
# Is there a smarter way to intercept pre_save with a diff main_character or state?
|
||||
@receiver(post_save, sender=UserProfile)
|
||||
def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
|
||||
# catches post_save from profiles to trigger necessary service and state checks
|
||||
if not created:
|
||||
update_fields = kwargs.pop('update_fields', []) or []
|
||||
if 'state' not in update_fields:
|
||||
logger.debug('Profile for {} saved without state change. Re-evaluating state.'.format(instance.user))
|
||||
instance.assign_state()
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_required_models(sender, instance, created, *args, **kwargs):
|
||||
# ensure all users have a model
|
||||
if created:
|
||||
logger.debug('User {} created. Creating default UserProfile.'.format(instance))
|
||||
UserProfile.objects.get_or_create(user=instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Token)
|
||||
def record_character_ownership(sender, instance, created, *args, **kwargs):
|
||||
if created:
|
||||
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
|
||||
instance.character_name))
|
||||
if instance.user:
|
||||
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
|
||||
else:
|
||||
query = Q(owner_hash=instance.character_owner_hash)
|
||||
# purge ownership records if the hash or auth user account has changed
|
||||
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
|
||||
# create character if needed
|
||||
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
|
||||
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name,
|
||||
instance.character_id))
|
||||
EveCharacter.objects.create_character(instance.character_id)
|
||||
char = EveCharacter.objects.get(character_id=instance.character_id)
|
||||
# check if we need to create ownership
|
||||
if instance.user and not CharacterOwnership.objects.filter(
|
||||
character__character_id=instance.character_id).exists():
|
||||
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
|
||||
instance.user))
|
||||
CharacterOwnership.objects.update_or_create(character=char,
|
||||
defaults={'owner_hash': instance.character_owner_hash,
|
||||
'user': instance.user})
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=CharacterOwnership)
|
||||
def validate_main_character(sender, instance, *args, **kwargs):
|
||||
try:
|
||||
if instance.user.profile.main_character == instance.character:
|
||||
logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
|
||||
instance.character, instance.user))
|
||||
# clear main character as user no longer owns them
|
||||
instance.user.profile.main_character = None
|
||||
instance.user.profile.save()
|
||||
except UserProfile.DoesNotExist:
|
||||
# a user is being deleted
|
||||
pass
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Token)
|
||||
def validate_ownership(sender, instance, *args, **kwargs):
|
||||
if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists():
|
||||
logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name))
|
||||
CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete()
|
||||
|
||||
|
||||
@receiver(pre_save, sender=User)
|
||||
def assign_state_on_active_change(sender, instance, *args, **kwargs):
|
||||
# set to guest state if inactive, assign proper state if reactivated
|
||||
if instance.pk:
|
||||
old_instance = User.objects.get(pk=instance.pk)
|
||||
if old_instance.is_active != instance.is_active:
|
||||
if instance.is_active:
|
||||
logger.debug("User {0} has been activated. Assigning state.".format(instance))
|
||||
instance.profile.assign_state()
|
||||
else:
|
||||
logger.debug(
|
||||
"User {0} has been deactivated. Revoking state and assigning to guest state.".format(instance))
|
||||
instance.profile.state = get_guest_state()
|
||||
instance.profile.save(update_fields=['state'])
|
||||
|
||||
|
||||
@receiver(post_save, sender=EveCharacter)
|
||||
def check_state_on_character_update(sender, instance, *args, **kwargs):
|
||||
# if this is a main character updating, check that user's state
|
||||
try:
|
||||
logger.debug("Character {0} has been saved. Assessing owner's state for changes.".format(instance))
|
||||
instance.userprofile.assign_state()
|
||||
except UserProfile.DoesNotExist:
|
||||
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
|
||||
pass
|
||||
|
||||
|
||||
@receiver(post_save, sender=CharacterOwnership)
|
||||
def ownership_record_creation(sender, instance, created, *args, **kwargs):
|
||||
if created:
|
||||
records = OwnershipRecord.objects.filter(owner_hash=instance.owner_hash).filter(character=instance.character)
|
||||
if records.exists():
|
||||
if records[0].user == instance.user: # most recent record is sorted first
|
||||
logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user))
|
||||
return
|
||||
logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user))
|
||||
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
CSS for allianceauth admin site
|
||||
*/
|
||||
|
||||
/* styling for profile pic */
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.column-user_profile_pic {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* tooltip */
|
||||
.tooltip {
|
||||
position: relative ;
|
||||
}
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip) ;
|
||||
position: absolute ;
|
||||
top: 1.1em ;
|
||||
left: 1em ;
|
||||
min-width: 200px ;
|
||||
border: 1px #808080 solid ;
|
||||
padding: 8px ;
|
||||
color: black ;
|
||||
background-color: rgb(255, 255, 204) ;
|
||||
z-index: 1 ;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
43
allianceauth/authentication/tasks.py
Normal file
43
allianceauth/authentication/tasks.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError
|
||||
from esi.models import Token
|
||||
from celery import shared_task
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task
|
||||
def check_character_ownership(owner_hash):
|
||||
tokens = Token.objects.filter(character_owner_hash=owner_hash)
|
||||
if tokens:
|
||||
for t in tokens:
|
||||
old_hash = t.character_owner_hash
|
||||
try:
|
||||
t.update_token_data(commit=False)
|
||||
except (TokenExpiredError, TokenInvalidError):
|
||||
t.delete()
|
||||
continue
|
||||
except (KeyError, IncompleteResponseError):
|
||||
# We can't validate the hash hasn't changed but also can't assume it has. Abort for now.
|
||||
logger.warning("Failed to validate owner hash of {0} due to problems contacting SSO servers.".format(
|
||||
tokens[0].character_name))
|
||||
break
|
||||
|
||||
if not t.character_owner_hash == old_hash:
|
||||
logger.info(
|
||||
'Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
|
||||
tokens.delete()
|
||||
break
|
||||
|
||||
if not Token.objects.filter(character_owner_hash=owner_hash).exists():
|
||||
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
|
||||
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
|
||||
|
||||
|
||||
@shared_task
|
||||
def check_all_character_ownership():
|
||||
for c in CharacterOwnership.objects.all().only('owner_hash'):
|
||||
check_character_ownership.delay(c.owner_hash)
|
||||
@@ -0,0 +1,163 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% trans "Dashboard" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% trans "Dashboard" %}</h1>
|
||||
{% if user.is_staff %}
|
||||
{% include 'allianceauth/admin-status/include.html' %}
|
||||
{% endif %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row vertical-flexbox-row2">
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-primary" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Main Character" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if request.user.profile.main_character %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<div class="hidden-xs">
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.character_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.corporation_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
{% if main.alliance_id %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.alliance_name }}</td>
|
||||
<tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table visible-xs-block">
|
||||
<p>
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ main.character_name }}</strong><br>
|
||||
{{ main.corporation_name }}<br>
|
||||
{{ main.alliance_name }}
|
||||
</p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% trans "No main character set." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% trans 'Add Character' %}</a>
|
||||
</div>
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% trans "Change Main" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-success" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Group Memberships" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
|
||||
<table class="table table-aa">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title text-center" style="text-align: center">
|
||||
{% trans 'Characters' %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-aa hidden-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Corp' %}</th>
|
||||
<th class="text-center">{% trans 'Alliance' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-aa visible-xs-block" style="width: 100%">
|
||||
<tbody>
|
||||
{% for ownership in request.user.character_ownerships.all %}
|
||||
{% with ownership.character as char %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align: middle">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align: middle; width: 100%">
|
||||
<strong>{{ char.character_name }}</strong><br>
|
||||
{{ char.corporation_name }}<br>
|
||||
{{ char.alliance_name|default:"" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
51
allianceauth/authentication/templates/public/base.html
Normal file
51
allianceauth/authentication/templates/public/base.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% load static %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
|
||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||
|
||||
{% include 'bundles/bootstrap-css.html' %}
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
{% block extra_include %}
|
||||
{% endblock %}
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.panel-transparent {
|
||||
background: rgba(48, 48, 48, 0.7);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
|
||||
}
|
||||
|
||||
#lang-select {
|
||||
width: 40%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
{% block extra_style %}
|
||||
{% endblock %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" style="margin-top:150px">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% load i18n %}
|
||||
<div class="dropdown">
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
|
||||
{{ language.name_local }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
8
allianceauth/authentication/templates/public/login.html
Normal file
8
allianceauth/authentication/templates/public/login.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% extends 'public/middle_box.html' %}
|
||||
{% load static %}
|
||||
{% block page_title %}Login{% endblock %}
|
||||
{% block middle_box_content %}
|
||||
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
|
||||
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
|
||||
</a>
|
||||
{% endblock %}
|
||||
24
allianceauth/authentication/templates/public/middle_box.html
Normal file
24
allianceauth/authentication/templates/public/middle_box.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends 'public/base.html' %}
|
||||
{% load static %}
|
||||
{% block title %}Login{% endblock %}
|
||||
{% block content %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div class="panel panel-default panel-transparent">
|
||||
<div class="panel-body">
|
||||
<div class="col-md-12">
|
||||
{% block middle_box_content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% include 'public/lang_select.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extra_include %}
|
||||
{% include 'bundles/bootstrap-js.html' %}
|
||||
{% endblock %}
|
||||
24
allianceauth/authentication/templates/public/register.html
Normal file
24
allianceauth/authentication/templates/public/register.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% load staticfiles %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
{% extends 'public/base.html' %}
|
||||
{% block page_title %}Registration{% endblock %}
|
||||
{% block extra_include %}
|
||||
{% include 'bundles/bootstrap-css.html' %}
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
{% include 'bundles/bootstrap-js.html' %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="panel panel-default panel-transparent">
|
||||
<div class="panel-body">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Register" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'public/lang_select.html' %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'public/middle_box.html' %}
|
||||
{% load i18n %}
|
||||
{% block middle_box_content %}
|
||||
<div class="alert alert-danger">{% trans 'Invalid or expired activation link.' %}</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,13 @@
|
||||
You're receiving this email because someone has entered this email address while registering for an account on {{ site.domain }}
|
||||
|
||||
If this was you, please click on the link below to confirm your email address:
|
||||
|
||||
<a href="{{ scheme }}://{{ url }}">Confirm email address</a>
|
||||
|
||||
Link not working? Try copy/pasting this URL into your browser:
|
||||
|
||||
{{ scheme }}://{{ url }}
|
||||
|
||||
This link will expire in {{ expiration_days }} day(s).
|
||||
|
||||
If this was not you, it is safe to ignore this email.
|
||||
@@ -0,0 +1 @@
|
||||
Confirm your Alliance Auth account email address
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this email because you requested a password reset for your
|
||||
{% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
|
||||
user account.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends 'public/middle_box.html' %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% block page_title %}Register{% endblock %}
|
||||
{% block middle_box_content %}
|
||||
<form class="form-signin" role="form" action="" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<br/>
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Submit" %}</button>
|
||||
<br/>
|
||||
</form>
|
||||
{% endblock %}
|
||||
18
allianceauth/authentication/tests/__init__.py
Normal file
18
allianceauth/authentication/tests/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def get_admin_change_view_url(obj: object) -> str:
|
||||
"""returns URL to admin change view for given object"""
|
||||
return reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label, type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
|
||||
def get_admin_search_url(ModelClass: type) -> str:
|
||||
"""returns URL to search URL for model of given object"""
|
||||
return '{}{}/'.format(
|
||||
reverse('admin:app_list', args=(ModelClass._meta.app_label,)),
|
||||
ModelClass.__name__.lower()
|
||||
)
|
||||
581
allianceauth/authentication/tests/test_admin.py
Normal file
581
allianceauth/authentication/tests/test_admin.py
Normal file
@@ -0,0 +1,581 @@
|
||||
from urllib.parse import quote
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.auth.models import User as BaseUser, Group
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
|
||||
from allianceauth.authentication.models import (
|
||||
CharacterOwnership, State, OwnershipRecord
|
||||
)
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..admin import (
|
||||
BaseUserAdmin,
|
||||
CharacterOwnershipAdmin,
|
||||
PermissionAdmin,
|
||||
StateAdmin,
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
OwnershipRecordAdmin,
|
||||
User,
|
||||
UserAdmin,
|
||||
user_main_organization,
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
update_main_character_model
|
||||
)
|
||||
from . import get_admin_change_view_url, get_admin_search_url
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication.admin'
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
def __init__(self, user=None):
|
||||
self.user = user
|
||||
|
||||
|
||||
def create_test_data():
|
||||
# groups
|
||||
group_1 = Group.objects.create(
|
||||
name='Group 1'
|
||||
)
|
||||
group_2 = Group.objects.create(
|
||||
name='Group 2'
|
||||
)
|
||||
|
||||
# user 1 - corp and alliance, normal user
|
||||
character_1 = EveCharacter.objects.create(
|
||||
character_id='1001',
|
||||
character_name='Bruce Wayne',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
character_1a = EveCharacter.objects.create(
|
||||
character_id='1002',
|
||||
character_name='Batman',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
executor_corp_id='2001'
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
member_count=42,
|
||||
alliance=alliance
|
||||
)
|
||||
user_1 = User.objects.create_user(
|
||||
character_1.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_1,
|
||||
owner_hash='x1' + character_1.character_name,
|
||||
user=user_1
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_1a,
|
||||
owner_hash='x1' + character_1a.character_name,
|
||||
user=user_1
|
||||
)
|
||||
user_1.profile.main_character = character_1
|
||||
user_1.profile.save()
|
||||
user_1.groups.add(group_1)
|
||||
|
||||
# user 2 - corp only, staff
|
||||
character_2 = EveCharacter.objects.create(
|
||||
character_id=1003,
|
||||
character_name='Clark Kent',
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Planet',
|
||||
corporation_ticker='DP',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Plane',
|
||||
corporation_ticker='DP',
|
||||
member_count=99,
|
||||
alliance=None
|
||||
)
|
||||
user_2 = User.objects.create_user(
|
||||
character_2.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_2,
|
||||
owner_hash='x1' + character_2.character_name,
|
||||
user=user_2
|
||||
)
|
||||
user_2.profile.main_character = character_2
|
||||
user_2.profile.save()
|
||||
user_2.groups.add(group_2)
|
||||
user_2.is_staff = True
|
||||
user_2.save()
|
||||
|
||||
# user 3 - no main, no group, superuser
|
||||
character_3 = EveCharacter.objects.create(
|
||||
character_id=1101,
|
||||
character_name='Lex Luthor',
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
member_count=666,
|
||||
alliance=None
|
||||
)
|
||||
EveAllianceInfo.objects.create(
|
||||
alliance_id='3101',
|
||||
alliance_name='Lex World Domination',
|
||||
alliance_ticker='LWD',
|
||||
executor_corp_id=''
|
||||
)
|
||||
user_3 = User.objects.create_user(
|
||||
character_3.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_3,
|
||||
owner_hash='x1' + character_3.character_name,
|
||||
user=user_3
|
||||
)
|
||||
user_3.is_superuser = True
|
||||
user_3.save()
|
||||
return user_1, user_2, user_3, group_1, group_2
|
||||
|
||||
|
||||
def make_generic_search_request(ModelClass: type, search_term: str):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
return c.get(
|
||||
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
|
||||
)
|
||||
|
||||
|
||||
class TestCharacterOwnershipAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user_1, _, _, _, _ = create_test_data()
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = CharacterOwnershipAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
ownership = self.user_1.character_ownerships.first()
|
||||
response = c.get(get_admin_change_view_url(ownership))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = CharacterOwnership.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestOwnershipRecordAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user_1, _, _, _, _ = create_test_data()
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = OwnershipRecordAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
ownership_record = OwnershipRecord.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = c.get(get_admin_change_view_url(ownership_record))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = OwnershipRecord.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestStateAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
create_test_data()
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = StateAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
response = c.get(get_admin_change_view_url(guest_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
member_state = AuthUtils.get_member_state()
|
||||
response = c.get(get_admin_change_view_url(member_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = State.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.name)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
class TestUserAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.user_1, cls.user_2, cls.user_3, cls.group_1, cls.group_2 = \
|
||||
create_test_data()
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.modeladmin = UserAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
self.character_1 = self.user_1.character_ownerships.first().character
|
||||
|
||||
def _create_autogroups(self):
|
||||
"""create autogroups for corps and alliances"""
|
||||
if _has_auto_groups:
|
||||
autogroups_config = AutogroupsConfig(
|
||||
corp_groups = True,
|
||||
alliance_groups = True
|
||||
)
|
||||
autogroups_config.save()
|
||||
for state in State.objects.all():
|
||||
autogroups_config.states.add(state)
|
||||
autogroups_config.update_corp_group_membership(self.user_1)
|
||||
|
||||
# column rendering
|
||||
|
||||
def test_user_profile_pic_u1(self):
|
||||
expected = ('<img src="https://images.evetech.net/characters/1001/'
|
||||
'portrait?size=32" class="img-circle">')
|
||||
self.assertEqual(user_profile_pic(self.user_1), expected)
|
||||
|
||||
def test_user_profile_pic_u3(self):
|
||||
self.assertIsNone(user_profile_pic(self.user_3))
|
||||
|
||||
def test_user_username_u1(self):
|
||||
expected = (
|
||||
'<strong><a href="/admin/authentication/user/{}/change/">'
|
||||
'Bruce_Wayne</a></strong><br>Bruce Wayne'.format(self.user_1.pk)
|
||||
)
|
||||
self.assertEqual(user_username(self.user_1), expected)
|
||||
|
||||
def test_user_username_u3(self):
|
||||
expected = (
|
||||
'<strong><a href="/admin/authentication/user/{}/change/">'
|
||||
'Lex_Luthor</a></strong>'.format(self.user_3.pk)
|
||||
)
|
||||
self.assertEqual(user_username(self.user_3), expected)
|
||||
|
||||
def test_user_main_organization_u1(self):
|
||||
expected = 'Wayne Technologies<br>Wayne Enterprises'
|
||||
self.assertEqual(user_main_organization(self.user_1), expected)
|
||||
|
||||
def test_user_main_organization_u2(self):
|
||||
expected = 'Daily Planet'
|
||||
self.assertEqual(user_main_organization(self.user_2), expected)
|
||||
|
||||
def test_user_main_organization_u3(self):
|
||||
expected = None
|
||||
self.assertEqual(user_main_organization(self.user_3), expected)
|
||||
|
||||
def test_characters_u1(self):
|
||||
expected = 'Batman, Bruce Wayne'
|
||||
result = self.modeladmin._characters(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_characters_u2(self):
|
||||
expected = 'Clark Kent'
|
||||
result = self.modeladmin._characters(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_characters_u3(self):
|
||||
expected = 'Lex Luthor'
|
||||
result = self.modeladmin._characters(self.user_3)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_groups_u1(self):
|
||||
self._create_autogroups()
|
||||
expected = 'Group 1'
|
||||
result = self.modeladmin._groups(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_groups_u2(self):
|
||||
self._create_autogroups()
|
||||
expected = 'Group 2'
|
||||
result = self.modeladmin._groups(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_groups_u3(self):
|
||||
self._create_autogroups()
|
||||
result = self.modeladmin._groups(self.user_3)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch(MODULE_PATH + '._has_auto_groups', False)
|
||||
def test_groups_u1_no_autogroups(self):
|
||||
expected = 'Group 1'
|
||||
result = self.modeladmin._groups(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@patch(MODULE_PATH + '._has_auto_groups', False)
|
||||
def test_groups_u2_no_autogroups(self):
|
||||
expected = 'Group 2'
|
||||
result = self.modeladmin._groups(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@patch(MODULE_PATH + '._has_auto_groups', False)
|
||||
def test_groups_u3_no_autogroups(self):
|
||||
result = self.modeladmin._groups(self.user_3)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_state(self):
|
||||
expected = 'Guest'
|
||||
result = self.modeladmin._state(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u1(self):
|
||||
expected = 'User'
|
||||
result = self.modeladmin._role(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u2(self):
|
||||
expected = 'Staff'
|
||||
result = self.modeladmin._role(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u3(self):
|
||||
expected = 'Superuser'
|
||||
result = self.modeladmin._role(self.user_3)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_list_2_html_w_tooltips_no_cutoff(self):
|
||||
items = ['one', 'two', 'three']
|
||||
expected = 'one, two, three'
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_list_2_html_w_tooltips_w_cutoff(self):
|
||||
items = ['one', 'two', 'three']
|
||||
expected = ('<span data-tooltip="one, two, three" '
|
||||
'class="tooltip">one, two, (...)</span>')
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 2)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_list_2_html_w_tooltips_empty_list(self):
|
||||
items = []
|
||||
expected = None
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# actions
|
||||
|
||||
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True)
|
||||
@patch(MODULE_PATH + '.update_character')
|
||||
def test_action_update_main_character_model(
|
||||
self, mock_task, mock_message_user
|
||||
):
|
||||
users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk])
|
||||
update_main_character_model(
|
||||
self.modeladmin, MockRequest(self.user_1), users_qs
|
||||
)
|
||||
self.assertEqual(mock_task.delay.call_count, 2)
|
||||
self.assertTrue(mock_message_user.called)
|
||||
|
||||
# filters
|
||||
|
||||
def test_filter_real_groups_with_autogroups(self):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (UserAdmin.RealGroupsFilter,)
|
||||
|
||||
self._create_autogroups()
|
||||
my_modeladmin = UserAdminTest(User, AdminSite())
|
||||
|
||||
# Make sure the lookups are correct
|
||||
request = self.factory.get('/')
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
filters = changelist.get_filters(request)
|
||||
filterspec = filters[0][0]
|
||||
expected = [
|
||||
(self.group_1.pk, self.group_1.name),
|
||||
(self.group_2.pk, self.group_2.name),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = User.objects.filter(groups__in=[self.group_1])
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
@patch(MODULE_PATH + '._has_auto_groups', False)
|
||||
def test_filter_real_groups_no_autogroups(self):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (UserAdmin.RealGroupsFilter,)
|
||||
|
||||
my_modeladmin = UserAdminTest(User, AdminSite())
|
||||
|
||||
# Make sure the lookups are correct
|
||||
request = self.factory.get('/')
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
filters = changelist.get_filters(request)
|
||||
filterspec = filters[0][0]
|
||||
expected = [
|
||||
(self.group_1.pk, self.group_1.name),
|
||||
(self.group_2.pk, self.group_2.name),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = User.objects.filter(groups__in=[self.group_1])
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_filter_main_corporations(self):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (MainCorporationsFilter,)
|
||||
|
||||
my_modeladmin = UserAdminTest(User, AdminSite())
|
||||
|
||||
# Make sure the lookups are correct
|
||||
request = self.factory.get('/')
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
filters = changelist.get_filters(request)
|
||||
filterspec = filters[0][0]
|
||||
expected = [
|
||||
('2002', 'Daily Planet'),
|
||||
('2001', 'Wayne Technologies'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get(
|
||||
'/',
|
||||
{'main_corporation_id__exact': self.character_1.corporation_id}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [self.user_1]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_filter_main_alliances(self):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (MainAllianceFilter,)
|
||||
|
||||
my_modeladmin = UserAdminTest(User, AdminSite())
|
||||
|
||||
# Make sure the lookups are correct
|
||||
request = self.factory.get('/')
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
filters = changelist.get_filters(request)
|
||||
filterspec = filters[0][0]
|
||||
expected = [
|
||||
('3001', 'Wayne Enterprises'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get(
|
||||
'/',
|
||||
{'main_alliance_id__exact': self.character_1.alliance_id}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [self.user_1]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
response = c.get(get_admin_change_view_url(self.user_1))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = User.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
407
allianceauth/authentication/tests/test_all.py
Normal file
407
allianceauth/authentication/tests/test_all.py
Normal file
@@ -0,0 +1,407 @@
|
||||
from unittest import mock
|
||||
from io import StringIO
|
||||
from urllib import parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.core.management import call_command
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
|
||||
from allianceauth.authentication.decorators import main_character_required
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from esi.errors import IncompleteResponseError
|
||||
from esi.models import Token
|
||||
|
||||
from ..backends import StateBackend
|
||||
from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\
|
||||
OwnershipRecord
|
||||
from ..tasks import check_character_ownership
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
class DecoratorTestCase(TestCase):
|
||||
@staticmethod
|
||||
@main_character_required
|
||||
def dummy_view(*args, **kwargs):
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
|
||||
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
|
||||
main_character = EveCharacter.objects.create(
|
||||
character_id=1,
|
||||
character_name='Main Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
|
||||
cls.main_user.profile.main_character = main_character
|
||||
|
||||
def setUp(self):
|
||||
self.request = RequestFactory().get('/test/')
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_login_redirect(self, m):
|
||||
setattr(self.request, 'user', AnonymousUser())
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_main_character_redirect(self, m):
|
||||
setattr(self.request, 'user', self.no_main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
url = getattr(response, 'url', None)
|
||||
self.assertEqual(url, reverse('authentication:dashboard'))
|
||||
|
||||
@mock.patch(MODULE_PATH + '.decorators.messages')
|
||||
def test_successful_request(self, m):
|
||||
setattr(self.request, 'user', self.main_user)
|
||||
response = self.dummy_view(self.request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class BackendTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.main_character = EveCharacter.objects.create(
|
||||
character_id=1,
|
||||
character_name='Main Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
cls.alt_character = EveCharacter.objects.create(
|
||||
character_id=2,
|
||||
character_name='Alt Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
cls.unclaimed_character = EveCharacter.objects.create(
|
||||
character_id=3,
|
||||
character_name='Unclaimed Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
|
||||
AuthUtils.disconnect_signals()
|
||||
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
|
||||
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
|
||||
UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character})
|
||||
AuthUtils.connect_signals()
|
||||
|
||||
def test_authenticate_main_character(self):
|
||||
t = Token(character_id=self.main_character.character_id, character_owner_hash='1')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEquals(user, self.user)
|
||||
|
||||
def test_authenticate_alt_character(self):
|
||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEquals(user, self.user)
|
||||
|
||||
def test_authenticate_unclaimed_character(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertNotEqual(user, self.user)
|
||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||
|
||||
def test_authenticate_character_record(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||
record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.old_user)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||
self.assertTrue(user.profile.main_character)
|
||||
|
||||
def test_iterate_username(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id,
|
||||
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||
username = StateBackend().authenticate(token=t).username
|
||||
t.character_owner_hash = '4'
|
||||
username_1 = StateBackend().authenticate(token=t).username
|
||||
t.character_owner_hash = '5'
|
||||
username_2 = StateBackend().authenticate(token=t).username
|
||||
self.assertNotEqual(username, username_1, username_2)
|
||||
self.assertTrue(username_1.endswith('_1'))
|
||||
self.assertTrue(username_2.endswith('_2'))
|
||||
|
||||
|
||||
class CharacterOwnershipTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('user', disconnect_signals=True)
|
||||
cls.alt_user = AuthUtils.create_user('alt_user', disconnect_signals=True)
|
||||
cls.character = EveCharacter.objects.create(
|
||||
character_id=1,
|
||||
character_name='Main Character',
|
||||
corporation_id=1,
|
||||
corporation_name='Corp',
|
||||
corporation_ticker='CORP',
|
||||
)
|
||||
|
||||
def test_create_ownership(self):
|
||||
Token.objects.create(
|
||||
user=self.user,
|
||||
character_id=self.character.character_id,
|
||||
character_name=self.character.character_name,
|
||||
character_owner_hash='1',
|
||||
)
|
||||
co = CharacterOwnership.objects.get(character=self.character)
|
||||
self.assertEquals(co.user, self.user)
|
||||
self.assertEquals(co.owner_hash, '1')
|
||||
|
||||
def test_transfer_ownership(self):
|
||||
Token.objects.create(
|
||||
user=self.user,
|
||||
character_id=self.character.character_id,
|
||||
character_name=self.character.character_name,
|
||||
character_owner_hash='1',
|
||||
)
|
||||
Token.objects.create(
|
||||
user=self.alt_user,
|
||||
character_id=self.character.character_id,
|
||||
character_name=self.character.character_name,
|
||||
character_owner_hash='2',
|
||||
)
|
||||
co = CharacterOwnership.objects.get(character=self.character)
|
||||
self.assertNotEqual(self.user, co.user)
|
||||
self.assertEquals(self.alt_user, co.user)
|
||||
|
||||
def test_clear_main_character(self):
|
||||
Token.objects.create(
|
||||
user=self.user,
|
||||
character_id=self.character.character_id,
|
||||
character_name=self.character.character_name,
|
||||
character_owner_hash='1',
|
||||
)
|
||||
self.user.profile.main_character = self.character
|
||||
self.user.profile.save()
|
||||
Token.objects.create(
|
||||
user=self.alt_user,
|
||||
character_id=self.character.character_id,
|
||||
character_name=self.character.character_name,
|
||||
character_owner_hash='2',
|
||||
)
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
self.assertIsNone(self.user.profile.main_character)
|
||||
|
||||
|
||||
class StateTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
||||
corp_name='Test Corp', alliance_name='Test Alliance')
|
||||
cls.guest_state = get_guest_state()
|
||||
cls.test_character = EveCharacter.objects.get(character_id='1')
|
||||
cls.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp',
|
||||
corporation_ticker='TEST', member_count=1)
|
||||
cls.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance',
|
||||
alliance_ticker='TEST', executor_corp_id='1')
|
||||
cls.member_state = State.objects.create(
|
||||
name='Test Member',
|
||||
priority=150,
|
||||
)
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def test_state_assignment_on_character_change(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.member_state)
|
||||
|
||||
self.member_state.member_characters.remove(self.test_character)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.guest_state)
|
||||
|
||||
def test_state_assignment_on_corporation_change(self):
|
||||
self.member_state.member_corporations.add(self.test_corporation)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.member_state)
|
||||
|
||||
self.member_state.member_corporations.remove(self.test_corporation)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.guest_state)
|
||||
|
||||
def test_state_assignment_on_alliance_addition(self):
|
||||
self.member_state.member_alliances.add(self.test_alliance)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.member_state)
|
||||
|
||||
self.member_state.member_alliances.remove(self.test_alliance)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.guest_state)
|
||||
|
||||
def test_state_assignment_on_higher_priority_state_creation(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
higher_state = State.objects.create(
|
||||
name='Higher State',
|
||||
priority=200,
|
||||
)
|
||||
higher_state.member_characters.add(self.test_character)
|
||||
self._refresh_user()
|
||||
self.assertEquals(higher_state, self.user.profile.state)
|
||||
higher_state.member_characters.clear()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
self.member_state.member_characters.clear()
|
||||
|
||||
def test_state_assignment_on_lower_priority_state_creation(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
lower_state = State.objects.create(
|
||||
name='Lower State',
|
||||
priority=125,
|
||||
)
|
||||
lower_state.member_characters.add(self.test_character)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
lower_state.member_characters.clear()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
self.member_state.member_characters.clear()
|
||||
|
||||
def test_state_assignment_on_priority_change(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
lower_state = State.objects.create(
|
||||
name='Lower State',
|
||||
priority=125,
|
||||
)
|
||||
lower_state.member_characters.add(self.test_character)
|
||||
self._refresh_user()
|
||||
lower_state.priority = 500
|
||||
lower_state.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(lower_state, self.user.profile.state)
|
||||
lower_state.priority = 125
|
||||
lower_state.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
|
||||
def test_state_assignment_on_state_deletion(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
higher_state = State.objects.create(
|
||||
name='Higher State',
|
||||
priority=200,
|
||||
)
|
||||
higher_state.member_characters.add(self.test_character)
|
||||
self._refresh_user()
|
||||
self.assertEquals(higher_state, self.user.profile.state)
|
||||
higher_state.delete()
|
||||
self.assertFalse(State.objects.filter(name='Higher State').count())
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
|
||||
def test_state_assignment_on_public_toggle(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
higher_state = State.objects.create(
|
||||
name='Higher State',
|
||||
priority=200,
|
||||
)
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
higher_state.public = True
|
||||
higher_state.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(higher_state, self.user.profile.state)
|
||||
higher_state.public = False
|
||||
higher_state.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.member_state, self.user.profile.state)
|
||||
|
||||
def test_state_assignment_on_active_changed(self):
|
||||
self.member_state.member_characters.add(self.test_character)
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.guest_state)
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
self._refresh_user()
|
||||
self.assertEquals(self.user.profile.state, self.member_state)
|
||||
|
||||
|
||||
class CharacterOwnershipCheckTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
|
||||
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
|
||||
corp_name='Test Corp', alliance_name='Test Alliance')
|
||||
cls.character = EveCharacter.objects.get(character_id='1')
|
||||
cls.token = Token.objects.create(
|
||||
user=cls.user,
|
||||
character_id='1',
|
||||
character_name='Test',
|
||||
character_owner_hash='1',
|
||||
)
|
||||
cls.ownership = CharacterOwnership.objects.get(character=cls.character)
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||
def test_no_change_owner_hash(self, update_token_data):
|
||||
# makes sure the ownership isn't delete if owner hash hasn't changed
|
||||
check_character_ownership(self.ownership)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||
def test_unable_to_update_token_data(self, update_token_data):
|
||||
# makes sure ownerships and tokens aren't hellpurged when there's problems with the SSO servers
|
||||
update_token_data.side_effect = IncompleteResponseError()
|
||||
check_character_ownership(self.ownership)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||
|
||||
update_token_data.side_effect = KeyError()
|
||||
check_character_ownership(self.ownership)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
|
||||
|
||||
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
|
||||
@mock.patch(MODULE_PATH + '.tasks.Token.delete')
|
||||
@mock.patch(MODULE_PATH + '.tasks.Token.objects.exists')
|
||||
@mock.patch(MODULE_PATH + '.tasks.CharacterOwnership.objects.filter')
|
||||
def test_owner_hash_changed(self, filter, exists, delete, update_token_data):
|
||||
# makes sure the ownership is revoked when owner hash changes
|
||||
filter.return_value.exists.return_value = False
|
||||
check_character_ownership(self.ownership)
|
||||
self.assertTrue(filter.return_value.delete.called)
|
||||
|
||||
|
||||
class ManagementCommandTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
|
||||
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
|
||||
character = UserProfile.objects.get(user=cls.user).main_character
|
||||
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
|
||||
|
||||
def setUp(self):
|
||||
self.stdout = StringIO()
|
||||
|
||||
def test_ownership(self):
|
||||
call_command('checkmains', stdout=self.stdout)
|
||||
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||
self.assertNotIn(self.user.username, self.stdout.getvalue())
|
||||
self.assertIn('All main characters', self.stdout.getvalue())
|
||||
|
||||
def test_no_ownership(self):
|
||||
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
|
||||
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
|
||||
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
|
||||
|
||||
call_command('checkmains', stdout=self.stdout)
|
||||
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
|
||||
self.assertIn(user.username, self.stdout.getvalue())
|
||||
102
allianceauth/authentication/tests/test_app_settings.py
Normal file
102
allianceauth/authentication/tests/test_app_settings.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from unittest.mock import Mock, patch
|
||||
from django.test import TestCase
|
||||
|
||||
from .. import app_settings
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
class TestSetAppSetting(TestCase):
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_not_set(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False,
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_not_set_for_none(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
None,
|
||||
required_type=int
|
||||
)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_true_stays_true(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = True
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False,
|
||||
)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_false_stays_false(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = False
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_bool(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_int(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
50
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_below_minimum_1(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = -5
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_below_minimum_2(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = -50
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50,
|
||||
min_value=-10
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_int(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 1000
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50,
|
||||
max_value=100
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_is_none_needs_required_type(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
with self.assertRaises(ValueError):
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=None
|
||||
)
|
||||
27
allianceauth/authentication/urls.py
Normal file
27
allianceauth/authentication/urls.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(
|
||||
r'^account/login/$',
|
||||
TemplateView.as_view(template_name='public/login.html'),
|
||||
name='login'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/main/$',
|
||||
views.main_character_change,
|
||||
name='change_main_character'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/add/$',
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
||||
]
|
||||
211
allianceauth/authentication/views.py
Normal file
211
allianceauth/authentication/views.py
Normal file
@@ -0,0 +1,211 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import login, authenticate
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from esi.decorators import token_required
|
||||
from esi.models import Token
|
||||
|
||||
from registration.backends.hmac.views import (
|
||||
RegistrationView as BaseRegistrationView,
|
||||
ActivationView as BaseActivationView,
|
||||
REGISTRATION_SALT
|
||||
)
|
||||
from registration.signals import user_registered
|
||||
|
||||
from .models import CharacterOwnership
|
||||
from .forms import RegistrationForm
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import *
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
groups = request.user.groups.all()
|
||||
if _has_auto_groups:
|
||||
groups = groups\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)
|
||||
groups = groups.order_by('name')
|
||||
characters = EveCharacter.objects\
|
||||
.filter(character_ownership__user=request.user)\
|
||||
.select_related()\
|
||||
.order_by('character_name')
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
'characters': characters
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def main_character_change(request, token):
|
||||
logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name))
|
||||
try:
|
||||
co = CharacterOwnership.objects.get(character__character_id=token.character_id, user=request.user)
|
||||
except CharacterOwnership.DoesNotExist:
|
||||
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
else:
|
||||
messages.error(
|
||||
request,
|
||||
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
|
||||
)
|
||||
co = None
|
||||
if co:
|
||||
request.user.profile.main_character = co.character
|
||||
request.user.profile.save(update_fields=['main_character'])
|
||||
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character})
|
||||
logger.info('Changed user %(user)s main character to %(char)s' % ({'user': request.user, 'char': co.character}))
|
||||
return redirect("authentication:dashboard")
|
||||
|
||||
|
||||
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def add_character(request, token):
|
||||
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
|
||||
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
|
||||
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
|
||||
else:
|
||||
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
"""
|
||||
Override the HMAC two-step registration view to accommodate the three-step registration required.
|
||||
Step 1: OAuth token to create user and profile.
|
||||
Step 2: Get email and send activation link (but do not save email).
|
||||
Step 3: Get link, save email and activate.
|
||||
|
||||
Step 1 is necessary to automatically assign character ownership and a main character, both of which require a saved User
|
||||
model - this means the ensuing registration form cannot create the user because it already exists.
|
||||
|
||||
Email is not saved to the user model in Step 2 as a way of differentiating users who have not yet completed registration
|
||||
(is_active=False) and users who have been disabled by an admin (is_active=False, email present).
|
||||
|
||||
Because of this, the email address needs to be assigned in Step 3 after clicking the link, which means the link must
|
||||
have the email address embedded much like the username. Key creation and decoding is overridden to support this action.
|
||||
"""
|
||||
|
||||
|
||||
# Step 1
|
||||
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def sso_login(request, token):
|
||||
user = authenticate(token=token)
|
||||
if user:
|
||||
token.user = user
|
||||
if Token.objects.exclude(pk=token.pk).equivalent_to(token).require_valid().exists():
|
||||
token.delete()
|
||||
else:
|
||||
token.save()
|
||||
if user.is_active:
|
||||
login(request, user)
|
||||
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
|
||||
elif not user.email:
|
||||
# Store the new user PK in the session to enable us to identify the registering user in Step 2
|
||||
request.session['registration_uid'] = user.pk
|
||||
# Go to Step 2
|
||||
return redirect('registration_register')
|
||||
messages.error(request, _('Unable to authenticate as the selected character.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
|
||||
|
||||
# Step 2
|
||||
class RegistrationView(BaseRegistrationView):
|
||||
form_class = RegistrationForm
|
||||
success_url = 'authentication:dashboard'
|
||||
|
||||
def get_success_url(self, user):
|
||||
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||
return 'authentication:dashboard', (), {}
|
||||
return super().get_success_url(user)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
|
||||
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
|
||||
pk=self.request.session.get('registration_uid')).exists():
|
||||
messages.error(self.request, _('Registration token has expired.'))
|
||||
return redirect(settings.LOGIN_URL)
|
||||
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||
# Keep the request so the user can be automagically logged in.
|
||||
setattr(self, 'request', request)
|
||||
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def register(self, form):
|
||||
user = User.objects.get(pk=self.request.session.get('registration_uid'))
|
||||
user.email = form.cleaned_data['email']
|
||||
user_registered.send(self.__class__, user=user, request=self.request)
|
||||
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
|
||||
# Go to Step 3
|
||||
self.send_activation_email(user)
|
||||
else:
|
||||
user.is_active = True
|
||||
user.save()
|
||||
login(self.request, user, 'allianceauth.authentication.backends.StateBackend')
|
||||
return user
|
||||
|
||||
def get_activation_key(self, user):
|
||||
return signing.dumps(obj=[getattr(user, User.USERNAME_FIELD), user.email], salt=REGISTRATION_SALT)
|
||||
|
||||
def get_email_context(self, activation_key):
|
||||
context = super(RegistrationView, self).get_email_context(activation_key)
|
||||
context['url'] = context['site'].domain + reverse('registration_activate', args=[activation_key])
|
||||
return context
|
||||
|
||||
|
||||
# Step 3
|
||||
class ActivationView(BaseActivationView):
|
||||
def validate_key(self, activation_key):
|
||||
try:
|
||||
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
|
||||
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
|
||||
return dump
|
||||
except signing.BadSignature:
|
||||
return None
|
||||
|
||||
def activate(self, *args, **kwargs):
|
||||
dump = self.validate_key(kwargs.get('activation_key'))
|
||||
if dump:
|
||||
user = self.get_user(dump[0])
|
||||
if user:
|
||||
user.email = dump[1]
|
||||
user.is_active = True
|
||||
user.save()
|
||||
return user
|
||||
return False
|
||||
|
||||
|
||||
def registration_complete(request):
|
||||
messages.success(request, _('Sent confirmation email. Please follow the link to confirm your email address.'))
|
||||
return redirect('authentication:login')
|
||||
|
||||
|
||||
def activation_complete(request):
|
||||
messages.success(request, _('Confirmed your email address. Please login to continue.'))
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
def registration_closed(request):
|
||||
messages.error(request, _('Registraion of new accounts it not allowed at this time.'))
|
||||
return redirect('authentication:login')
|
||||
103
allianceauth/bin/allianceauth.py
Normal file
103
allianceauth/bin/allianceauth.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python
|
||||
import os
|
||||
import shutil
|
||||
from optparse import OptionParser
|
||||
from django.core.management import call_command
|
||||
from django.core.management.commands.startproject import Command as BaseStartProject
|
||||
|
||||
|
||||
class StartProject(BaseStartProject):
|
||||
def add_arguments(self, parser):
|
||||
super().add_arguments(parser)
|
||||
parser.add_argument('--python', help='The path to the python executable.')
|
||||
parser.add_argument('--celery', help='The path to the celery executable.')
|
||||
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
|
||||
|
||||
|
||||
def create_project(parser, options, args):
|
||||
# Validate args
|
||||
if len(args) < 2:
|
||||
parser.error("Please specify a name for your Alliance Auth installation.")
|
||||
elif len(args) > 3:
|
||||
parser.error("Too many arguments.")
|
||||
|
||||
# First find the path to Alliance Auth
|
||||
import allianceauth
|
||||
allianceauth_path = os.path.dirname(allianceauth.__file__)
|
||||
template_path = os.path.join(allianceauth_path, 'project_template')
|
||||
|
||||
# Determine locations of commands to render supervisor cond
|
||||
command_options = {
|
||||
'template': template_path,
|
||||
'python': shutil.which('python'),
|
||||
'gunicorn': shutil.which('gunicorn'),
|
||||
'celery': shutil.which('celery'),
|
||||
'extensions': ['py', 'conf', 'json'],
|
||||
}
|
||||
|
||||
# Strip 'start' out of the arguments, leaving project name (and optionally destination dir)
|
||||
args = args[1:]
|
||||
|
||||
# Call the command with extra context
|
||||
call_command(StartProject(), *args, **command_options)
|
||||
|
||||
print("Success! %(project_name)s has been created." % {'project_name': args[0]}) # noqa
|
||||
|
||||
|
||||
def update_settings(parser, options, args):
|
||||
if len(args) < 2:
|
||||
parser.error("Please specify the path to your Alliance Auth installation.")
|
||||
elif len(args) > 2:
|
||||
parser.error("Too many arguments.")
|
||||
|
||||
project_path = args[1]
|
||||
project_name = os.path.split(project_path)[-1]
|
||||
|
||||
# find the target settings/base.py file, handing both the project and app as valid paths
|
||||
# first check if given path is to the app
|
||||
settings_path = os.path.join(project_path, 'settings/base.py')
|
||||
if not os.path.exists(settings_path):
|
||||
# next check if given path is to the project, so the app is within it
|
||||
settings_path = os.path.join(project_path, project_name, 'settings/base.py')
|
||||
if not os.path.exists(settings_path):
|
||||
parser.error("Unable to locate the Alliance Auth project at %s" % project_path)
|
||||
|
||||
# first find the path to the Alliance Auth template settings
|
||||
import allianceauth
|
||||
allianceauth_path = os.path.dirname(allianceauth.__file__)
|
||||
template_path = os.path.join(allianceauth_path, 'project_template')
|
||||
template_settings_path = os.path.join(template_path, 'project_name/settings/base.py')
|
||||
|
||||
# overwrite the local project's base settings
|
||||
with open(template_settings_path, 'r') as template, open(settings_path, 'w') as target:
|
||||
target.write(template.read())
|
||||
|
||||
print("Successfully updated %(project_name)s settings." % {'project_name': project_name})
|
||||
|
||||
|
||||
COMMANDS = {
|
||||
'start': create_project,
|
||||
'update': update_settings,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
# Parse options
|
||||
parser = OptionParser(usage="Usage: %prog [start|update] project_name [directory]")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# Find command
|
||||
try:
|
||||
command = args[0]
|
||||
except IndexError:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
if command in COMMANDS:
|
||||
COMMANDS[command](parser, options, args)
|
||||
else:
|
||||
parser.error("Unrecognised command: " + command)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
9
allianceauth/context_processors.py
Normal file
9
allianceauth/context_processors.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.conf import settings
|
||||
from .views import NightModeRedirectView
|
||||
|
||||
|
||||
def auth_settings(request):
|
||||
return {
|
||||
'SITE_NAME': settings.SITE_NAME,
|
||||
'NIGHT_MODE': NightModeRedirectView.night_mode_state(request),
|
||||
}
|
||||
1
allianceauth/corputils/__init__.py
Normal file
1
allianceauth/corputils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'allianceauth.corputils.apps.CorpUtilsConfig'
|
||||
6
allianceauth/corputils/admin.py
Normal file
6
allianceauth/corputils/admin.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import CorpStats, CorpMember
|
||||
|
||||
admin.site.register(CorpStats)
|
||||
admin.site.register(CorpMember)
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CorpUtilsConfig(AppConfig):
|
||||
name = 'corputils'
|
||||
name = 'allianceauth.corputils'
|
||||
label = 'corputils'
|
||||
30
allianceauth/corputils/auth_hooks.py
Normal file
30
allianceauth/corputils/auth_hooks.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from allianceauth import hooks
|
||||
from allianceauth.corputils import urls
|
||||
|
||||
|
||||
class CorpStats(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self,
|
||||
_('Corporation Stats'),
|
||||
'fa fa-share-alt fa-fw',
|
||||
'corputils:view',
|
||||
navactive=['corputils:'])
|
||||
|
||||
def render(self, request):
|
||||
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(
|
||||
'corputils.view_alliance_corpstats') or request.user.has_perm(
|
||||
'corputils.add_corpstats') or request.user.has_perm('corputils.view_state_corpstats'):
|
||||
return MenuItemHook.render(self, request)
|
||||
return ''
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
return CorpStats()
|
||||
|
||||
|
||||
@hooks.register('url_hook')
|
||||
def register_url():
|
||||
return UrlHook(urls, 'corputils', r'^corpstats/')
|
||||
48
allianceauth/corputils/managers.py
Normal file
48
allianceauth/corputils/managers.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from django.db import models
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CorpStatsQuerySet(models.QuerySet):
|
||||
def visible_to(self, user):
|
||||
# superusers get all visible
|
||||
if user.is_superuser:
|
||||
logger.debug('Returning all corpstats for superuser %s.' % user)
|
||||
return self
|
||||
|
||||
try:
|
||||
char = user.profile.main_character
|
||||
assert char
|
||||
# build all accepted queries
|
||||
queries = [models.Q(token__user=user)]
|
||||
if user.has_perm('corputils.view_alliance_corpstats'):
|
||||
if char.alliance_id is not None:
|
||||
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
|
||||
else:
|
||||
queries.append(models.Q(corp__corporation_id=char.corporation_id))
|
||||
if user.has_perm('corputils.view_corp_corpstats'):
|
||||
if user.has_perm('corputils.view_alliance_corpstats'):
|
||||
pass
|
||||
else:
|
||||
queries.append(models.Q(corp__corporation_id=char.corporation_id))
|
||||
if user.has_perm('corputils.view_state_corpstats'):
|
||||
queries.append(models.Q(corp__in=user.profile.state.member_corporations.all()))
|
||||
queries.append(models.Q(corp__alliance__in=user.profile.state.member_alliances.all()))
|
||||
logger.debug('%s queries for user %s visible corpstats.' % (len(queries), user))
|
||||
# filter based on queries
|
||||
query = queries.pop()
|
||||
for q in queries:
|
||||
query |= q
|
||||
return self.filter(query)
|
||||
except AssertionError:
|
||||
logger.debug('User %s has no main character. No corpstats visible.' % user)
|
||||
return self.none()
|
||||
|
||||
|
||||
class CorpStatsManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return CorpStatsQuerySet(self.model, using=self._db)
|
||||
|
||||
def visible_to(self, user):
|
||||
return self.get_queryset().visible_to(user)
|
||||
@@ -19,6 +19,7 @@ PERMISSIONS = {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def user_permissions_dict(apps):
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
@@ -33,14 +34,17 @@ def user_permissions_dict(apps):
|
||||
'corpstats': {x: Permission.objects.get_or_create(codename=x, name=y, content_type=corpstats_ct)[0] for x, y in PERMISSIONS['corpstats'].items()},
|
||||
}
|
||||
|
||||
|
||||
def users_with_permission(apps, perm):
|
||||
User = apps.get_model('auth', 'User')
|
||||
return User.objects.filter(user_permissions=perm.pk)
|
||||
|
||||
|
||||
def groups_with_permission(apps, perm):
|
||||
Group = apps.get_model('auth', 'Group')
|
||||
return Group.objects.filter(permissions=perm.pk)
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
perm_dict = user_permissions_dict(apps)
|
||||
|
||||
@@ -66,8 +70,9 @@ def forward(apps, schema_editor):
|
||||
|
||||
for name, perm in perm_dict['user'].items():
|
||||
perm.delete()
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
perm_dict = user_permissions_dict(apps)
|
||||
|
||||
corp_users = users_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
|
||||
@@ -79,7 +84,6 @@ def reverse(apps, schema_editor):
|
||||
u.user_permissions.remove(perm_dict['corpstats']['view_corp_corpstats'].pk)
|
||||
for u in corp_api_users:
|
||||
u.user_permissions.remove(perm_dict['corpstats']['corp_apis'].pk)
|
||||
|
||||
|
||||
alliance_users = users_with_permission(apps, perm_dict['corpstats']['view_alliance_corpstats'])
|
||||
alliance_api_users = users_with_permission(apps, perm_dict['corpstats']['alliance_apis'])
|
||||
@@ -93,7 +97,6 @@ def reverse(apps, schema_editor):
|
||||
|
||||
corp_groups = groups_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
|
||||
corp_api_groups = groups_with_permission(apps, perm_dict['corpstats']['corp_apis'])
|
||||
corp_gs = corp_groups | corp_api_groups
|
||||
for g in corp_groups.distinct():
|
||||
g.permissions.add(perm_dict['user']['corp_apis'].pk)
|
||||
for g in corp_groups:
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-22 23:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corputils', '0002_migrate_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='corpstats',
|
||||
options={'permissions': (('corp_apis', 'Can view API keys of members of their corporation.'), ('alliance_apis', 'Can view API keys of members of their alliance.'), ('blue_apis', 'Can view API keys of members of blue corporations.'), ('view_corp_corpstats', 'Can view corp stats of their corporation.'), ('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'), ('view_blue_corpstats', 'Can view corp stats of blue corporations.')), 'verbose_name': 'corp stats', 'verbose_name_plural': 'corp stats'},
|
||||
),
|
||||
]
|
||||
60
allianceauth/corputils/migrations/0004_member_models.py
Normal file
60
allianceauth/corputils/migrations/0004_member_models.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-03-26 20:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import json
|
||||
|
||||
|
||||
def convert_json_to_members(apps, schema_editor):
|
||||
CorpStats = apps.get_model('corputils', 'CorpStats')
|
||||
CorpMember = apps.get_model('corputils', 'CorpMember')
|
||||
for cs in CorpStats.objects.all():
|
||||
members = json.loads(cs._members)
|
||||
CorpMember.objects.bulk_create(
|
||||
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in
|
||||
members.items()]
|
||||
)
|
||||
|
||||
|
||||
def convert_members_to_json(apps, schema_editor):
|
||||
CorpStats = apps.get_model('corputils', 'CorpStats')
|
||||
for cs in CorpStats.objects.all():
|
||||
cs._members = json.dumps({m.character_id: m.character_name for m in cs.members.all()})
|
||||
cs.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('corputils', '0003_granular_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CorpMember',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('character_id', models.PositiveIntegerField()),
|
||||
('character_name', models.CharField(max_length=37)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['character_name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='corpmember',
|
||||
name='corpstats',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members',
|
||||
to='corputils.CorpStats'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='corpmember',
|
||||
unique_together=set([('corpstats', 'character_id')]),
|
||||
),
|
||||
migrations.RunPython(convert_json_to_members, convert_members_to_json),
|
||||
migrations.RemoveField(
|
||||
model_name='corpstats',
|
||||
name='_members',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.2 on 2017-06-10 15:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def delete_permissions(apps, schema_editor):
|
||||
CorpStats = apps.get_model('corputils', 'CorpStats')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(CorpStats)
|
||||
perms = Permission.objects.filter(content_type=ct)
|
||||
perms.filter(codename__contains='api').delete()
|
||||
perms.filter(codename='view_corpstats').delete()
|
||||
perms.filter(codename__contains='blue').delete()
|
||||
perms.filter(codename__contains='remove').delete()
|
||||
|
||||
g = perms.get(codename='view_corp_corpstats')
|
||||
g.name = 'Can view corp stats of their corporation.'
|
||||
g.save()
|
||||
|
||||
g = perms.get(codename='view_alliance_corpstats')
|
||||
g.name = 'Can view corp stats of members of their alliance.'
|
||||
g.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corputils', '0004_member_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='corpstats',
|
||||
options={'permissions': (('view_corp_corpstats', 'Can view corp stats of their corporation.'), ('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'), ('view_state_corpstats', 'Can view corp stats of members of their auth state.')), 'verbose_name': 'corp stats', 'verbose_name_plural': 'corp stats'},
|
||||
),
|
||||
migrations.RunPython(delete_permissions, migrations.RunPython.noop),
|
||||
]
|
||||
201
allianceauth/corputils/models.py
Normal file
201
allianceauth/corputils/models.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, UserProfile
|
||||
from bravado.exception import HTTPForbidden
|
||||
from django.db import models
|
||||
from esi.errors import TokenError
|
||||
from esi.models import Token
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from allianceauth.corputils.managers import CorpStatsManager
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||
"""
|
||||
Swagger spec operations:
|
||||
|
||||
Character
|
||||
get_characters_character_id
|
||||
get_corporations_corporation_id_members
|
||||
Universe
|
||||
post_universe_names
|
||||
"""
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CorpStats(models.Model):
|
||||
token = models.ForeignKey(Token, on_delete=models.CASCADE)
|
||||
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
|
||||
last_update = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_corp_corpstats', 'Can view corp stats of their corporation.'),
|
||||
('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'),
|
||||
('view_state_corpstats', 'Can view corp stats of members of their auth state.'),
|
||||
)
|
||||
verbose_name = "corp stats"
|
||||
verbose_name_plural = "corp stats"
|
||||
|
||||
objects = CorpStatsManager()
|
||||
|
||||
def __str__(self):
|
||||
return "%s for %s" % (self.__class__.__name__, self.corp)
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
||||
'corporation_id'] == int(self.corp.corporation_id)
|
||||
member_ids = c.Corporation.get_corporations_corporation_id_members(
|
||||
corporation_id=self.corp.corporation_id).result()
|
||||
|
||||
# requesting too many ids per call results in a HTTP400
|
||||
# the swagger spec doesn't have a maxItems count
|
||||
# manual testing says we can do over 350, but let's not risk it
|
||||
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
||||
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in
|
||||
member_id_chunks]
|
||||
member_list = {}
|
||||
for name_chunk in member_name_chunks:
|
||||
member_list.update({m['id']: m['name'] for m in name_chunk})
|
||||
|
||||
# bulk create new member models
|
||||
missing_members = [m_id for m_id in member_ids if
|
||||
not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
|
||||
CorpMember.objects.bulk_create(
|
||||
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in
|
||||
missing_members])
|
||||
|
||||
# purge old members
|
||||
self.members.exclude(character_id__in=member_ids).delete()
|
||||
|
||||
# update the timer
|
||||
self.save()
|
||||
|
||||
except TokenError as e:
|
||||
logger.warning("%s failed to update: %s" % (self, e))
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
||||
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
|
||||
level="error")
|
||||
self.delete()
|
||||
except HTTPForbidden as e:
|
||||
logger.warning("%s failed to update: %s" % (self, e))
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
||||
message="%s: %s" % (e.status_code, e.message), level="error")
|
||||
self.delete()
|
||||
except AssertionError:
|
||||
logger.warning("%s token character no longer in corp." % self)
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s cannot update with your ESI token." % self,
|
||||
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
|
||||
self.delete()
|
||||
|
||||
@property
|
||||
def member_count(self):
|
||||
return self.members.count()
|
||||
|
||||
@property
|
||||
def user_count(self):
|
||||
return len(set([m.main_character for m in self.members.all() if m.main_character]))
|
||||
|
||||
@property
|
||||
def registered_member_count(self):
|
||||
return len(self.registered_members)
|
||||
|
||||
@property
|
||||
def registered_members(self):
|
||||
return self.members.filter(pk__in=[m.pk for m in self.members.all() if m.registered])
|
||||
|
||||
@property
|
||||
def unregistered_member_count(self):
|
||||
return self.member_count - self.registered_member_count
|
||||
|
||||
@property
|
||||
def unregistered_members(self):
|
||||
return self.members.filter(pk__in=[m.pk for m in self.members.all() if not m.registered])
|
||||
|
||||
@property
|
||||
def main_count(self):
|
||||
return len(self.mains)
|
||||
|
||||
@property
|
||||
def mains(self):
|
||||
return self.members.filter(pk__in=[m.pk for m in self.members.all() if
|
||||
m.main_character and int(m.main_character.character_id) == int(
|
||||
m.character_id)])
|
||||
|
||||
def visible_to(self, user):
|
||||
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
|
||||
|
||||
def can_update(self, user):
|
||||
return self.token.user == user or self.visible_to(user)
|
||||
|
||||
def corp_logo(self, size=128):
|
||||
return self.corp.logo_url(size)
|
||||
|
||||
def alliance_logo(self, size=128):
|
||||
if self.corp.alliance:
|
||||
return self.corp.alliance.logo_url(size)
|
||||
else:
|
||||
return EveAllianceInfo.generic_logo_url(1, size)
|
||||
|
||||
|
||||
class CorpMember(models.Model):
|
||||
character_id = models.PositiveIntegerField()
|
||||
character_name = models.CharField(max_length=37)
|
||||
corpstats = models.ForeignKey(CorpStats, on_delete=models.CASCADE, related_name='members')
|
||||
|
||||
class Meta:
|
||||
# not making character_id unique in case a character moves between two corps while only one updates
|
||||
unique_together = ('corpstats', 'character_id')
|
||||
ordering = ['character_name']
|
||||
|
||||
def __str__(self):
|
||||
return self.character_name
|
||||
|
||||
@property
|
||||
def character(self):
|
||||
try:
|
||||
return EveCharacter.objects.get(character_id=self.character_id)
|
||||
except EveCharacter.DoesNotExist:
|
||||
return None
|
||||
|
||||
@property
|
||||
def main_character(self):
|
||||
try:
|
||||
return self.character.character_ownership.user.profile.main_character
|
||||
except (CharacterOwnership.DoesNotExist, UserProfile.DoesNotExist, AttributeError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def alts(self):
|
||||
if self.main_character:
|
||||
return [co.character for co in self.main_character.character_ownership.user.character_ownerships.all()]
|
||||
else:
|
||||
return []
|
||||
|
||||
@property
|
||||
def registered(self):
|
||||
return CharacterOwnership.objects.filter(character__character_id=self.character_id).exists()
|
||||
|
||||
def portrait_url(self, size=32):
|
||||
return EveCharacter.generic_portrait_url(self.character_id, size)
|
||||
|
||||
@property
|
||||
def portrait_url_32(self):
|
||||
return self.portrait_url(32)
|
||||
|
||||
@property
|
||||
def portrait_url_64(self):
|
||||
return self.portrait_url(64)
|
||||
|
||||
@property
|
||||
def portrait_url_128(self):
|
||||
return self.portrait_url(128)
|
||||
1
allianceauth/corputils/swagger.json
Normal file
1
allianceauth/corputils/swagger.json
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user