Compare commits

...

217 Commits

Author SHA1 Message Date
Danhia f1b1214291 Merge pull request 'New permissions to allow a group of user to manage event-related objects' (#89) from Danhia/website:admin/events-permissions into main
Reviewed-on: 42CTF/website#89
2023-09-24 15:25:41 +02:00
Danhia 8b28f73bdb added special permissions to allow a group of user to manage event-related objects 2023-09-24 15:17:49 +02:00
Danhia c8adc6caf9 Merge pull request 'Added speed bonus points for events' (#88) from Danhia/website:events/bonus-points into main
Reviewed-on: 42CTF/website#88
2023-09-17 20:43:42 +02:00
Danhia 6ecb94eab5 added speed bonus points for events 2023-09-17 20:39:13 +02:00
Danhia 44897411f5 Merge pull request 'Fix invalid characters in team name that causes error 500' (#87) from Danhia/website:events/fix-teamname into main
Reviewed-on: 42CTF/website#87
2023-09-01 11:10:54 +02:00
Danhia 5a10e033a2 Merge pull request 'fix(api): Check if user has campus before add to response' (#85) from Starthur/website:main into main
Reviewed-on: 42CTF/website#85
Reviewed-by: Danhia <danhia@protonmail.com>
2023-09-01 11:09:53 +02:00
Danhia 3d86f21ba2 add forbidden characters in teamname to avoir error 500 2023-09-01 11:07:13 +02:00
Starthur 691c53e110
fix(api): Check if user has campus before add to response 2023-02-07 10:15:30 +01:00
Starthur 2d4816e78f Merge pull request '[API][BOT] New endpoint to retrieve campus associated to discord id' (#84) from Danhia/website:api-campus into main
Reviewed-on: 42CTF/website#84
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2023-02-07 09:43:57 +01:00
Danhia 5d84174db2 Added new endpoint to retrieve campus associated to discord id 2023-02-06 19:41:48 +01:00
Danhia 9ad64e72bc Merge pull request 'doc(tools): Broken link GEF' (#83) from Starthur/website:main into main
Reviewed-on: 42CTF/website#83
Reviewed-by: Danhia <danhia@protonmail.com>
2023-02-05 23:20:19 +01:00
Danhia 9d0a420cac Merge pull request 'Separate profile page for events' (#69) from Danhia/website:event/fix-chall-slug into main
Reviewed-on: 42CTF/website#69
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2023-02-05 23:13:44 +01:00
Starthur 7120a80c1d
doc(tools): Broken link GEF 2023-02-01 18:41:53 +01:00
Starthur fe71460537 Merge branch 'main' into event/fix-chall-slug 2022-10-13 20:23:21 +02:00
Danhia 32bbc392d6 Merge pull request 'Facilitate access to the user's profile' (#82) from UncleReaton/website:main into main
Reviewed-on: 42CTF/website#82
Reviewed-by: Danhia <danhia@protonmail.com>
2022-10-10 21:03:09 +02:00
UncleReaton acf306097a Change user.userprofileinfo.user to user 2022-10-10 21:01:31 +02:00
UncleReaton dc955fc189 Add a button to view the user's profile by clicking on their score on the navbar 2022-10-10 20:37:11 +02:00
UncleReaton e304f7cd15 Add a button to view the user's profile from their edit page 2022-10-10 20:27:31 +02:00
Danhia 73cf94515d Merge pull request 'Added command to prune users who never logged in' (#80) from Danhia/website:management/prune-users into main
Well maybe it will remove all users from the db but we can't know until we try it, can we ?
2022-09-30 16:24:18 +02:00
Danhia 94237bd9b6 Added command to prune users who never logged in 2022-09-30 16:20:17 +02:00
Starthur a983aafba2 Merge pull request 'Repare search' (#79) from Starthur/website:main into main
Reviewed-on: 42CTF/website#79
2022-08-29 14:02:22 +02:00
Starthur cce09ed254
Repare search 2022-08-29 14:01:05 +02:00
Danhia c2e58c4f92 Merge pull request 'Fix logo size and position with some highly advanced CSS' (#78) from Danhia/website:scoreboard/fix-network into main
Reviewed-on: 42CTF/website#78
2022-08-27 11:38:16 +02:00
Danhia eba672d067 Merge branch 'main' into scoreboard/fix-network 2022-08-27 11:37:40 +02:00
Danhia 1a6c31f5e8 fix logo size and position with some highly advanced CSS 2022-08-27 11:36:07 +02:00
Danhia bd17ad5f8f Merge pull request 'Fix missing url in top3 network scoreboard' (#77) from Danhia/website:scoreboard/fix-network into main
Reviewed-on: 42CTF/website#77
2022-08-27 11:18:28 +02:00
Danhia 40984a2a0c fix missing url in top3 network scoreboard 2022-08-27 11:10:20 +02:00
Danhia 9ac003ea3c Merge pull request 'Podium added and working, logo and website of campuses added in campus model' (#76) from ix/website:main into main
Reviewed-on: 42CTF/website#76
Reviewed-by: Danhia <danhia@protonmail.com>
2022-08-27 10:47:46 +02:00
Danhia 5cf86b9c36 Merge branch 'main' into main 2022-08-27 10:47:33 +02:00
zero 7c06c24d9f Fix hardcoded logo value in network scoreboard template 2022-08-21 20:37:05 +02:00
zero 9b2da1e2cc Someone forgot to remove prints in a file... 2022-08-19 15:51:10 +02:00
zero 891c2530e6 Podium added and working, logo and website of campuses added in campus model 2022-08-19 15:43:25 +02:00
Danhia 13895d7712 Merge pull request 'Fix member conversion page UI and SEO upgrades' (#75) from ix/website:main into main
Reviewed-on: 42CTF/website#75
Reviewed-by: Danhia <danhia@protonmail.com>
2022-08-18 14:21:13 +02:00
Danhia 0f64c7579c Merge branch 'main' into main 2022-08-18 14:20:43 +02:00
zero 02331beff4 fix domain in canonical url 2022-08-18 13:58:13 +02:00
zero 5b2abeac20 add canonical meta 2022-08-18 13:44:38 +02:00
Danhia 1771f3c2bb Merge pull request 'Removed the is_member attribute for campus name' (#74) from Danhia/website:scoreboard/fix-network into main
Reviewed-on: 42CTF/website#74
2022-08-18 10:40:32 +02:00
Danhia 09118cac7a removed the is_member attribute for campus name 2022-08-18 10:38:53 +02:00
Danhia 7cd43ca35f Merge pull request 'Scoreboard for 42 Network' (#73) from Danhia/website:scoreboard/42network into main
Reviewed-on: 42CTF/website#73
2022-08-17 22:59:40 +02:00
Danhia d21915405e removed pagination on network scoreboard 2022-08-17 22:58:36 +02:00
zero dd7a69aa50 42CTF/website#72 |42CTF/website#36 sitemap and titles fixed 2022-08-17 16:42:06 +02:00
Danhia 8a2cc6f3ae scoreboard for 42 Network 2022-08-17 12:59:29 +02:00
Danhia e36ca2b146 Merge pull request 'Rephrase a bit the become member page + improved french translation for resources' (#71) from Danhia/website:resources/fix-typos into main
Reviewed-on: 42CTF/website#71
2022-08-16 22:37:00 +02:00
Danhia 63876e91ef rephrase a bit the become member page + improved french translation for resources 2022-08-16 19:46:53 +02:00
Danhia eadede339b Merge pull request 'Rework of resources pages and a page to become member, added it in the navbar and fix global UI spacing issue.' (#70) from ix/website:main into main
Reviewed-on: 42CTF/website#70
Reviewed-by: Danhia <danhia@protonmail.com>
2022-08-16 19:09:33 +02:00
zero d1992e1476 Rework of resources pages and a page to become member, added it in the navbar and fix global UI spacing issue. 2022-08-16 18:07:13 +02:00
Danhia 00acec6fdb separate profile page for events 2022-08-16 18:03:39 +02:00
Danhia 1ee02fc964 Merge pull request 'fix-campus-in-scoreboard' (#65) from fix-campus-in-scoreboard into main
Reviewed-on: 42CTF/website#65
Reviewed-by: Danhia <danhia@protonmail.com>
2022-08-01 22:21:44 +02:00
Starthur ff23250275
Remove old field 2022-08-01 22:18:23 +02:00
Starthur c449f26ad7
Check FK to print value in scoreboard 2022-08-01 22:18:23 +02:00
Danhia 42858e4342 Hotfix for indentation error 2022-07-02 00:26:12 +02:00
Danhia ec61f06be0 Merge pull request 'hotfix for solo events' (#63) from Danhia/website:fix-event into main
Reviewed-on: 42CTF/website#63
2022-07-01 23:57:17 +02:00
Danhia 2233474c89 hotfix for solo events 2022-07-01 23:51:36 +02:00
Danhia 7b9dfad15d Merge pull request 'Limit events to certain campus' (#62) from events_campus into main
Reviewed-on: 42CTF/website#62
Reviewed-by: Danhia <danhia@protonmail.com>
2022-06-02 19:50:05 +02:00
Starthur 46ea80161d Chack campus before password 2022-05-31 22:14:11 +02:00
Starthur d222784bb0 Remove unsued import 2022-05-31 22:13:54 +02:00
Starthur 86fbf93dbf Remove a useless migration 2022-05-31 22:13:45 +02:00
Arthur-TRT ff657c070a Limit events to certain campus 2022-05-30 10:25:18 +02:00
Starthur a1ec3e5f20 Merge pull request 'Broken CSS register page' (#61) from html-refacto into main
Reviewed-on: 42CTF/website#61
2022-05-28 12:05:24 +02:00
Starthur 0d7f39d335 Broken CSS register page 2022-05-28 12:04:00 +02:00
Danhia f56322bd6e Merge pull request 'Fix duplicates in created challenges' (#59) from Danhia/website:fix-challenges into main
Reviewed-on: 42CTF/website#59
2022-05-28 11:00:23 +02:00
Danhia 506eb54d49 fixed duplicates in created challenges 2022-05-28 10:53:46 +02:00
Starthur 87efa03ed0 Merge pull request 'Esthetic change' (#57) from challenge_created into main
Reviewed-on: 42CTF/website#57
2022-05-27 21:04:07 +02:00
Starthur 5435ea3316 Esthetic change 2022-05-27 21:02:54 +02:00
Starthur 4245cfd19f Merge branch 'Yir-registration-failed-restore' 2022-05-27 20:57:06 +02:00
Starthur f391a5a5cb Merge branch 'registration-failed-restore' of https://gitea.42ctf.org/Yir/website into Yir-registration-failed-restore 2022-05-27 20:56:42 +02:00
Starthur ebdc3b9c9e Merge pull request 'challenges_created' (#56) from challenges_created into main
Reviewed-on: 42CTF/website#56
2022-05-27 16:56:57 +02:00
Starthur 9d3b9ebf5f remove debug 2022-05-27 16:55:23 +02:00
Starthur 04318e3b0a Add created challenges on profile 2022-05-27 16:53:53 +02:00
Starthur eeb752d605 Merge pull request 'Change because preprod can't acces tld' (#55) from mail into main
Reviewed-on: 42CTF/website#55
2022-05-27 16:12:54 +02:00
Starthur 2d466454f5 Change because preprod can't acces tld 2022-05-27 16:10:36 +02:00
Starthur 7ff972e623 Merge pull request 'Change mail service for local mail server' (#54) from mail into main
Reviewed-on: 42CTF/website#54
2022-05-27 15:54:24 +02:00
Starthur d8791353fb Change mail service for local mail server 2022-05-27 15:52:18 +02:00
Starthur dc3e7f4b27 Merge pull request 'Broken CSS Issue#45' (#53) from html_refacto into main
Reviewed-on: 42CTF/website#53
2022-05-27 15:36:29 +02:00
Starthur 763853b3c6 Broken CSS Issue#45 2022-05-27 15:35:11 +02:00
Starthur c1c82ff9e0 Merge pull request 'new_logo' (#52) from new_logo into main
Reviewed-on: 42CTF/website#52
2022-05-27 14:57:20 +02:00
Starthur fdc3796667 Resize image 2022-05-27 14:55:57 +02:00
Starthur b25bf03485 Change image for social card 2022-05-27 14:53:30 +02:00
Starthur 5a08764df7 Merge pull request 'Vectorized typography in logo' (#51) from UncleReaton/website:main into main
Reviewed-on: 42CTF/website#51
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2022-05-18 11:45:55 +02:00
UncleReaton 084fb1a62d Vectorized typography in logo 2022-05-18 11:42:03 +02:00
Starthur d8c35db454 Merge pull request 'Change old logo for the new one + Add favicon' (#50) from UncleReaton/website:main into main
Reviewed-on: 42CTF/website#50
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2022-05-18 11:25:15 +02:00
UncleReaton 6e860b2997 Change old logo for the new one + Add favicon 2022-05-18 00:34:02 +02:00
Starthur 4ee613d9af Merge pull request 'Reformulate to the context' (#46) from Aslan/website:translation into main
Reviewed-on: 42CTF/website#46
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2022-05-17 20:30:44 +02:00
Starthur 9c75b317eb Merge branch 'main' into translation 2022-05-17 20:29:42 +02:00
Starthur 2d2e97e794 Merge pull request 'Correcting typo' (#49) from refactor/42OAuth into main
Reviewed-on: 42CTF/website#49
2022-05-17 17:35:58 +02:00
Arthur-TRT 66ab43bf2b Correcting typo 2022-05-17 17:34:49 +02:00
Starthur 4213883774 Merge pull request 'Refactoring 42Oauth' (#48) from refactor/42OAuth into main
Reviewed-on: 42CTF/website#48
2022-05-17 15:48:05 +02:00
Arthur-TRT 9cb4b2b3a8 Refactoring 42Oauth 2022-05-17 15:29:58 +02:00
Starthur b90cc55b8d Merge pull request 'Add alt texts to base template and fr+en news' (#47) from UncleReaton/website:main into main
Reviewed-on: 42CTF/website#47
Reviewed-by: Starthur <contact+42ctf@arthur-trt.fr>
2022-05-17 14:28:19 +02:00
UncleReaton d19514f629 Add alt texts to base template and fr+en news 2022-05-11 20:43:20 +02:00
Aslan 6b2b0cef7d Reformulate to the context 2022-04-10 13:12:40 +02:00
Aslan d7bf9ae74e Update 'src/locale/ru/LC_MESSAGES/django.po' 2022-04-03 17:09:35 +02:00
Danhia dfa36b58a6 Merge pull request 'campus_scoreboard' (#42) from campus_scoreboard into main
Reviewed-on: 42CTF/website#42
Reviewed-by: Danhia <danhia@protonmail.com>
2022-03-29 21:09:30 +02:00
Starthur bf5678d371 Merge branch 'main' into campus_scoreboard 2022-03-29 21:06:31 +02:00
Danhia 3c013794d7 Merge pull request '42OAuth' (#41) from 42OAuth into main
Reviewed-on: 42CTF/website#41
2022-03-29 21:03:47 +02:00
Danhia 6110408cd0 Merge pull request 'home-refacto' (#40) from Starthur/website:home-refacto into campus_scoreboard
Reviewed-on: 42CTF/website#40
2022-03-29 20:35:03 +02:00
Starthur bb2617a3e4 Merge branch 'home-refacto' of https://gitea.42ctf.org/Starthur/website into home-refacto 2022-03-29 19:53:10 +02:00
Starthur e23d6d7f49 HTML typo 2022-03-29 19:53:00 +02:00
Starthur 978dd24a00 Add campuses scoreboard 2022-03-29 19:53:00 +02:00
Danhia 15f3a21c60 Merge pull request 'Add 42Oauth' (#37) from Starthur/website:main into 42OAuth
Reviewed-on: 42CTF/website#37
2022-03-29 19:51:57 +02:00
Starthur e480aec493 Remove debug 2022-03-29 19:49:52 +02:00
Starthur 3991dda7d3 HTML typo 2022-03-29 18:55:54 +02:00
Starthur e8575d559e Add campuses scoreboard 2022-03-29 18:46:19 +02:00
Starthur ea691204c4 Upgrad po file 2022-03-29 16:46:03 +02:00
Arthur TROUILLET 21f38906df Change OAuth secret to env 2022-03-29 14:19:06 +02:00
Arthur TROUILLET 39e3859836 Done 2022-03-29 11:52:40 +02:00
Arthur-TRT a26784cd98 First version of 42Oauth 2022-03-29 11:02:07 +02:00
Yir ba7992e7be Preserve registration form fields that passed error check when the registration failed. 2022-03-20 22:43:38 +01:00
Danhia 7dad96d79f Merge pull request 'API for events data' (#33) from Danhia/website:api-events into main
Reviewed-on: 42CTF/website#33
2022-03-19 20:42:35 +01:00
Danhia d554961dd1 added API for events data 2022-03-19 20:38:52 +01:00
Danhia 280ba0d1fc Merge pull request 'Fixed a typo in resources /locale/fr' (#32) from FdB/website:fdb-patch-typo-binaires into main
Reviewed-on: 42CTF/website#32
2022-03-19 19:54:39 +01:00
Bertrand C bdd51b6cf9 Merge branch 'main' into fdb-patch-typo-binaires 2022-03-19 19:50:25 +01:00
Bertrand C c004c18e75 Fixed a typo in resources /locale/fr
Binaies to binaires
2022-03-19 19:41:47 +01:00
Danhia e8d43586a6 merge main into translation 2022-03-19 19:33:09 +01:00
Danhia 2da57b7b9b Merge pull request 'Change text color of input for better readibility' (#31) from UncleReaton/website:main into main
Reviewed-on: 42CTF/website#31
2022-03-19 19:21:33 +01:00
Danhia a828ce2ece Merge pull request 'Add a partial translation into Russian language' (#30) from Aslan/website:translation into translation
Reviewed-on: 42CTF/website#30
2022-03-19 19:20:27 +01:00
UncleReaton 16030c14a4 Change text color of flag submission input for better readibility 2022-03-19 16:52:47 +01:00
Aslan ca86079148 Add translation of remaining part into Russian language 2022-03-18 00:38:42 +01:00
Aslan 93c9844efa Add translation of languages into Russian language 2022-03-18 00:28:58 +01:00
Aslan 4c06ed41bf Add translation of home templates into Russian language 2022-03-18 00:26:39 +01:00
Aslan fca3609c1b Add translation of events templates into Russian language 2022-03-18 00:24:31 +01:00
Aslan b0380d4fc3 Add translation of events templates into Russian language 2022-03-16 22:25:45 +01:00
Aslan 3ea2a41d2b Add translation of ctfs templates into Russian language 2022-03-16 22:24:53 +01:00
Aslan de87c70ae0 Add translation of accounts templates into Russian language 2022-03-16 22:23:33 +01:00
Aslan 22fe0d601a Fix typo in the error message 2022-03-16 22:13:16 +01:00
Danhia 5191e300df changed color for member so difference is more noticeable 2022-03-15 21:53:55 +01:00
Danhia 09377c509f updated git links in resources + donation section 2022-03-13 18:14:32 +01:00
Danhia 6a76f4b99f Merge pull request 'Add Japanese version of "gitea.html"' (#28) from ysaito/website:main into translation
Reviewed-on: 42CTF/website#28
2022-02-23 14:38:52 +01:00
ysaito 5908ce4778 docs(news/ja/): Add Japanese version of gitea.html 2022-02-23 14:36:42 +09:00
Danhia 4e5fe52984 added latest news to repository 2022-02-21 16:37:34 +01:00
Danhia 2f24fae611 Merge pull request 'merge translation into main' (#26) from translation into main
Reviewed-on: 42CTF/website#26
2022-02-18 16:19:51 +01:00
Danhia 02385dcd08 Merge pull request 'Add Japanese message string for django.po' (#25) from ysaito/website:main into translation
Reviewed-on: 42CTF/website#25
2022-02-18 16:17:47 +01:00
ysaito 7b2de06225 refactor(news/ja/): Corrected the translation of a word 2022-02-18 22:48:26 +09:00
ysaito ba1cb03dbf feat(*/django.po): Add Japanese message string 2022-02-18 22:45:09 +09:00
Danhia 11164bb84a replaced the bad request with a redirect to account edit 2022-02-18 11:32:08 +01:00
Danhia 67a4d180e0 corrected typo 2022-02-18 11:27:58 +01:00
Danhia 3b86441259 Merge branch 'main' into bot-api 2022-02-18 11:11:58 +01:00
Danhia 7b2552f0b5 added try except in case user cancels oauth 2022-02-18 11:11:34 +01:00
Danhia eac835bef5 removed merge artifact 2022-02-18 10:55:54 +01:00
Danhia 06593b26f9 fixed merge conflict 2022-02-17 23:59:21 +01:00
Danhia e8f5f33269 Merge branch 'challenges-description' into main 2022-02-17 23:50:28 +01:00
Danhia f0479f6577 Merge branch 'challenges-description' into main 2022-02-17 23:21:56 +01:00
Danhia 60a59b23e6 fixed error 500 when requesting german or russian translation 2022-02-17 23:21:41 +01:00
Danhia 8f32ef9a6e excluded the API from the i18n patterns 2022-02-17 13:09:11 +01:00
Danhia a0b76903a7 added endpoint API for bot 2022-02-17 12:40:40 +01:00
Danhia a7de7b8054 removed language code in redirect_uri 2022-02-17 12:21:17 +01:00
Danhia 0d734e98b1 removed language code in redirect_uri 2022-02-17 12:19:52 +01:00
Danhia 3d24fe9b3b tentative for fixing url redirect 2022-02-17 12:03:52 +01:00
Danhia ba0d75c250 restored discord OAuth, not working in local, let's test on preprod if the url problem is solved 2022-02-17 11:11:36 +01:00
Danhia 3ae80ca17f fixed end of sentence in french with quote badly escaped 2022-02-16 20:14:29 +01:00
Danhia a7e75b2a43 fixed quotes that where badly escaped in resources 2022-02-16 20:07:19 +01:00
Danhia 9ea67ae2a0 Merge branch 'main' into translation 2022-02-16 19:28:39 +01:00
Danhia 899a01e472 Merge branch 'main' into challenges-description 2022-02-16 15:41:52 +01:00
Danhia c40d49c326 Merge pull request 'Ressource page translation' (#22) from Starthur/website:main into translation
Reviewed-on: 42CTF/website#22
2022-02-16 15:37:18 +01:00
Starthur d6b1380552 Last push before pull request 2022-02-16 15:23:15 +01:00
Arthur-TRT d0c93f98a1 Correct some mistake 2022-02-16 15:19:07 +01:00
Arthur-TRT 97e120e5fc First translation of Ressources in fr 2022-02-16 15:03:40 +01:00
Danhia 494377399c Merge pull request 'Update README' (#21) from update-readme into main
Reviewed-on: 42CTF/website#21
2022-02-16 14:34:14 +01:00
Starthur 28d8874272 Update README
Better solution to create superuser admin
2022-02-16 14:33:21 +01:00
Starthur b9704d3cb3 Update README
Add information about migrate and admin right on local dev
2022-02-16 14:13:00 +01:00
Danhia 0ac395def8 Update 'requirements.txt' 2022-02-16 13:53:57 +01:00
Danhia 7ff556a986 Update 'README.md' 2022-02-16 13:50:19 +01:00
Danhia 5e02041f0c put challenges descriptions outside of the db 2022-02-15 18:13:43 +01:00
Danhia f75a034094 replaced jp with ja for news 2022-02-15 14:27:37 +01:00
Danhia 5f28bc5d2c Merge pull request '[translation] Added Japanese version' (#9) from ysaito/website:translation-jp into translation
Reviewed-on: 42CTF/website#9
2022-02-15 13:20:02 +01:00
ysaito 904ce1b748 feat(welcome_ctf_2021.html): Added Japanese version 2022-02-15 18:11:00 +09:00
ysaito 5b15b9cd6f feat(sql_challenges.html): Added Japanese version 2022-02-15 18:07:48 +09:00
ysaito 5d46bd6df0 feat(speed_dating_ctf_2022.html): Added Japanese version 2022-02-15 18:06:46 +09:00
ysaito 67de669459 feat(dynamic_scoring.html): Added Japanese version 2022-02-15 18:05:43 +09:00
ysaito 26aeb6d7bd feat(42ctf_vm.html): Added Japanese version 2022-02-15 18:04:30 +09:00
Danhia 1b89fa88d8 updated all .po with makemessages 2022-02-14 19:38:16 +01:00
Danhia 0bb3a8b805 Merge pull request #8 from ClemaX/translation/de
German news translation and typos fix
2022-02-14 18:34:08 +00:00
Danhia 5eec76dd00 removed start challenge button when event is over 2022-02-14 19:18:00 +01:00
Clément Hamada 6b6f264fe0 news: locale: de: Initial translation 2022-02-13 21:47:03 +01:00
Clément Hamada c4fd6d05c3 locale: de: Fix some typos 2022-02-13 21:30:40 +01:00
Danhia 901c73a4f2 Merge branch 'main' of https://github.com/Danhia/42CTF into main 2022-02-13 13:14:24 +01:00
Danhia 01c0f28b16 hotfix for 500 when joining team 2022-02-13 13:13:54 +01:00
Danhia 7174cf9edb Update README.md 2022-02-12 20:35:41 +01:00
Danhia ef0fcc5fea Merge pull request #7 from Miliviu/main
Set Spanish to done in the readme todo list
2022-02-12 19:28:07 +00:00
Danhia aabba3ea53 dynamic scoring for events, i'm pretty sure everything is gonna break 2022-02-12 19:56:46 +01:00
Miliviu 1abeabebfe Set Spanish to done in the readme todo list 2022-02-11 19:55:50 +01:00
Danhia 9f2d78ac33 challenges not yet published 2022-02-09 14:14:41 +01:00
Danhia 27ed107501 Merge pull request #6 from Miliviu/main
Add translation of news to Spanish
2022-02-09 13:36:45 +01:00
Miliviu 0dea7fb619 Add translation of news to Spanish 2022-02-09 13:08:02 +01:00
Danhia 23529b3b07 added english version of news contents 2022-02-09 12:39:12 +01:00
Danhia cb9bc7cc56 news content is now out of the db for languages other than english 2022-02-09 12:22:12 +01:00
Danhia af4912837f Merge pull request #5 from Miliviu/main
All Spanish strings translated
2022-02-09 11:12:32 +01:00
Miliviu 187579e61c Add trasnlator references and date of translation 2022-02-09 10:58:12 +01:00
Miliviu fabe417fe2 Add Spanish to availible languages in the Settings.py file 2022-02-09 10:53:11 +01:00
Miliviu a6a5e424df Added spanish translation to both django files 2022-02-09 10:44:40 +01:00
Danhia bcf0c51ba5 added option to open team to automatching 2022-02-08 15:36:06 +01:00
Danhia 241a3f2bc1 added japanese empty files 2022-02-06 23:39:46 +01:00
Danhia cb46708e5c fixed redirection when leaving team + few details in html 2022-02-04 19:28:09 +01:00
Danhia 3c166f3834 updated .po files for last modifications 2022-02-04 19:05:11 +01:00
Danhia 92a1dca27c Merge branch 'main' into events 2022-02-04 19:04:20 +01:00
Danhia 96121e401e fixed redirection in events 2022-02-04 19:04:17 +01:00
ix 97f6b99133 fix event html 2022-02-04 17:13:50 +01:00
Danhia c45cfed8d4 fixed centering flag vertically 2022-02-04 16:04:56 +01:00
ix 5dc04f888b fix flags 2022-02-04 15:43:56 +01:00
Danhia 993e1fa998 beginning of lang flag refacto 2022-02-04 15:28:06 +01:00
Danhia 9d33985fa4 Merge pull request #4 from ClemaX/translation/de
locale: de: Translate all existing messages
2022-02-04 13:05:05 +01:00
Clément Hamada 0cd862734e resources: locale: de: Translate all existing messages 2022-02-04 06:02:00 +01:00
Clément Hamada aed129b4b9 locale: de: Spellcheck 2022-02-04 05:58:07 +01:00
Clément Hamada d1ab64885e resources: locale: de: Update messages 2022-02-04 03:34:01 +01:00
Clément Hamada b2cf802ab5 locale: de: Update file references 2022-02-04 03:30:49 +01:00
Clément Hamada 266e018d83 locale: de: Translate all existing messages 2022-02-04 03:13:51 +01:00
Danhia 8fd25d8fa6 changed position of logos in footer for mobile devices + added helloasso button in resources 2022-02-04 00:27:29 +01:00
ix 53fe69aa09 Merge branch 'main' of github.com:Danhia/42CTF into main 2022-02-03 21:03:18 +01:00
Danhia aadb2ac858 hotfix for dynamic scoring 2022-02-03 21:02:55 +01:00
ix 34b8829797 Fix resources page 2022-02-03 21:00:04 +01:00
ix 619ddf3d07 fix spaces between nav elements 2022-02-03 20:49:24 +01:00
ix 5d00c5733f Fix resources page 2022-02-03 20:49:02 +01:00
ix 248cdc54d5 Fix unused html 2022-02-03 20:45:56 +01:00
Danhia f8eef0ef3d added dynamic scoring for permanent platform, let's hope nothing breaks 2022-02-03 20:27:55 +01:00
Danhia e806b23812 fixed latest flags when chall is disabled + fixed recompute_scoreboard 2022-02-03 18:46:37 +01:00
Danhia 0fbf7b77c3 added disabled attribute for challenge + fixed recomputescoreboard 2022-02-03 18:37:24 +01:00
Danhia 985f440ede hotfix for 0 division when there is an empty category in profile page 2022-02-02 19:34:22 +01:00
141 changed files with 7351 additions and 3924 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "src/ctfs/templates/challenges"]
path = src/ctfs/templates/challenges
url = https://gitea.42ctf.org/42CTF/challenges-descriptions.git

View File

@ -1,50 +1,34 @@
# 42ctf # 42CTF
CTF by 42 students
[42CTF](https://www.42ctf.org) is a CTF platform created by School 42 students and open to anyone.
### Todo ### Todo
- [x] Serveur SMTP & reset password TODO has been migrated to [issues](https://gitea.42ctf.org/42CTF/website/issues) !
- [x] Clean le repo And hopefully, it is not redirected anymore to `/dev/null`.
- [x] Accès au chall après validation
- [x] Section "Intro"
- [x] Section Treasure Hunt
- [x] Edition de profil
- [x] Ajouter de la Doc
- [x] Infrastructure de pwn
- [x] Organiser une session découverte
- [x] Compteur de flags
- [x] Graphiques statistiques
- [x] Création d'un discord linkable
- [ ] Refonte du linkage discord -> 42ctf
- [x] Traduction du site
- [x] Anglais
- [x] Français
- [ ] Russe
- [ ] Espagnol
- [ ] Italien
- [ ] OAuth 42
- [ ] Feature proposer une solution à un challenge
- [ ] Système de badge/succès
- [ ] Génération d'une page résumant le profil d'un utilisateur (ex: show resume sur intra.42.fr)
#### Event feature ### How to contribute ?
- [X] make relation between user and events First, you need to contact a 42CTF admin to get an account on the 42CTF gitea.
- [X] make scoreboard for events You can contact us on [discord](https://discord.gg/3KDvt6hbWW) or by [email](mailto:42ctf@protonmail.com).
- [X] make access mod for events : You can also fill this [form](https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC) and we'll contact you.
- [X] Sub button for public events Then, once you have a gitea account, you can fork this repository, do some stuff, and open a pull request.
- [X] Access by password
- [X] Begin date for display challenges If you want to translate the platform, then have a look at the [wiki](https://gitea.42ctf.org/42CTF/website/wiki).
- [X] Ending date for stop flag submission
- [ ] Access by invite link If you want to help with bot development, it has now its own [repository](https://gitea.42ctf.org/42CTF/bot)
- [X] Admin rights
- [X] Admin can access to events pages without password ### How to set up my dev environment ?
- [X] Admin can subscribe to event without password
- [X] process flag submission There is only one file missing on this repository for you to run the server: `local_settings.py`.
- [X] increment user score in Scores model You should create one in the `src` directory, with the following content:
- [X] add filters for admin dashboard ```
- [X] add search in fields in admin dashboard DEBUG = True
- [X] display more information in admin dashboard SECRET_KEY = 'what you want'
- [X] Smooth display of events listing ```
- [X] Event info page with background and noice display
- [ ] Create teams for events When you'll run `python manage.py migrate` then `python manage.py runserver`, an empty database will be automatically created.
The `local_settings.py` is in the `.gitignore` and should stay that way, so we don't accidentally overwrite the production file when we deploy.
To obtain administrator rights you can run `python manage.py createsuperuser`.

104
bot.py
View File

@ -1,104 +0,0 @@
import os
import discord
import discord.utils
import urllib.request, json
import asyncio
import json
import logging
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = '42ctf'
intents = discord.Intents.all()
client = discord.Client(intents=intents)
db_file = open('members.json', 'r')
users = json.load(db_file)
db_file.close()
logging.basicConfig(filename='bot.log', format='%(asctime)s %(message)s', level=logging.INFO)
guild = ''
roles = {}
def get_rank(token):
url = urllib.request.urlopen("https://www.42ctf.org/accounts/rank/" + token)
data = json.loads(url.read().decode())
rank = data['rank']
return rank
async def watch_roles():
global users
await client.wait_until_ready() # ensures cache is loaded
while not client.is_closed():
for member_id, token in users.items():
if (token == "0000"):
continue
member = discord.utils.get(guild.members, id=int(member_id))
rank = get_rank(token)
if rank == 1 and roles['top1'] not in member.roles:
await member.add_roles(roles['top1'])
await member.remove_roles(roles['top10'])
await member.remove_roles(roles['top50'])
elif rank > 1 and rank <= 10 and roles['top10'] not in member.roles:
await member.add_roles(roles['top10'])
await member.remove_roles(roles['top1'])
await member.remove_roles(roles['top50'])
elif rank > 10 and rank <= 50 and roles['top50'] not in member.roles:
await member.add_roles(roles['top50'])
await member.remove_roles(roles['top10'])
await member.remove_roles(roles['top1'])
elif rank > 50:
await member.remove_roles(roles['top1'])
await member.remove_roles(roles['top10'])
await member.remove_roles(roles['top50'])
await asyncio.sleep(60)
@client.event
async def on_ready():
global guild, roles
guild = discord.utils.get(client.guilds, name=GUILD)
roles['top10'] = discord.utils.get(guild.roles, id=801787467064672286)
roles['top1'] = discord.utils.get(guild.roles, id=798638767359524875)
roles['top50'] = discord.utils.get(guild.roles, id=803729539145924649)
logging.info('%s is connected to the following guild: %s(id: %d)', client.user, guild.name, guild.id)
client.loop.create_task(watch_roles())
@client.event
async def on_message(message):
global guild, roles
if message.author == client.user:
return
if '!connect' in message.content:
try:
user_token = message.content.split(' ')[1]
member = discord.utils.get(guild.members, name=message.author.name)
rank = get_rank(user_token)
users[str(member.id)] = user_token
logging.info("MESSAGE: from %s with token %s", message.author.name, user_token)
with open('members.json', 'w') as json_file:
json.dump(users, json_file)
if rank == 1:
await member.add_roles(roles['top1'])
response = "Congratulations, you're now Top 1. But for how long ?"
elif (rank <= 10):
await member.add_roles(roles['top10'])
response = "You've been granted the Top 10 role. Now, go away and flag !"
elif rank <= 50:
await member.add_roles(roles['top50'])
response = "You've been granted the Top 50 role. Now, go away and flag !"
else:
response = "No role for you now, but I'll keep watching you."
except IndexError:
response = 'usage: !connect 42ctf_token'
await message.author.create_dm()
await message.author.dm_channel.send(response)
client.run(TOKEN)

View File

@ -1,3 +1,3 @@
Django Django==3.2.11
requests requests==2.27.1
authlib authlib==0.15.5

View File

@ -1,5 +1,6 @@
from .models import UserProfileInfo from .models import UserProfileInfo
from django.contrib import admin from django.contrib import admin
from .models import Campus
#admin.site.register(UserProfileInfo) #admin.site.register(UserProfileInfo)
# Register your models here. # Register your models here.
@ -7,6 +8,10 @@ from django.contrib import admin
@admin.register(UserProfileInfo) @admin.register(UserProfileInfo)
class userprofile(admin.ModelAdmin): class userprofile(admin.ModelAdmin):
#list display #list display
list_display = ['user', 'score', 'last_submission_date'] list_display = ['user', 'score', 'last_submission_date', 'campus']
# search list # search list
search_fields = ['score', 'user__username'] search_fields = ['score', 'user__username', 'campus__name']
@admin.register(Campus)
class campus(admin.ModelAdmin):
list_display = ['name']

View File

@ -0,0 +1,24 @@
from collections import defaultdict
from django.core.management.base import BaseCommand, CommandError
from accounts import models as acc_models
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import timezone
from datetime import timedelta
class Command(BaseCommand):
help = 'Remove all users who never logged in'
def handle(self, *args, **options):
all_users = acc_models.UserProfileInfo.objects.filter(score=0).select_related()
to_remove = []
for elem in all_users:
user = elem.user
if user.last_login is None and user.date_joined < timezone.now() - timedelta(hours=72):
to_remove.append(user)
print("You are going to remove {} users.".format(len(to_remove)))
answer = input("Continue ? [y/N] ")
if answer.lower() in ["y","yes"]:
for elem in to_remove:
elem.delete()
print("Users have been successfully pruned.")

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.11 on 2022-03-29 08:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_auto_20220123_1704'),
]
operations = [
migrations.AddField(
model_name='userprofileinfo',
name='intra42_campus',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AddField(
model_name='userprofileinfo',
name='intra42_id',
field=models.CharField(blank=True, max_length=20, null=True, unique=True),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 3.2.11 on 2022-03-29 11:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_auto_20220329_1034'),
]
operations = [
migrations.AlterModelOptions(
name='userprofileinfo',
options={'ordering': ['-score', 'last_submission_date', 'user__username', 'intra42_campus'], 'permissions': (('view_info', 'View user info'),), 'verbose_name': 'profile', 'verbose_name_plural': 'profiles'},
),
migrations.AddField(
model_name='userprofileinfo',
name='intra42_campus_id',
field=models.CharField(blank=True, max_length=10, null=True),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 3.2.11 on 2022-05-17 12:52
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_auto_20220329_1339'),
]
operations = [
migrations.CreateModel(
name='Campus',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50)),
],
options={
'verbose_name': 'campus',
'verbose_name_plural': 'campuses',
'permissions': (('view_info', 'View user info'),),
},
),
migrations.AddField(
model_name='userprofileinfo',
name='campus',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='accounts.campus'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.11 on 2022-05-17 12:54
from django.db import migrations
from accounts.models import UserProfileInfo
from accounts.models import Campus
def migrate_campus(apps, schema_editor):
UserProfileInfo = apps.get_model('accounts', 'UserProfileInfo')
Campus = apps.get_model('accounts', 'Campus')
for user in UserProfileInfo.objects.all():
if user.intra42_campus_id:
user.campus, created = Campus.objects.get_or_create(id=user.intra42_campus_id, name=user.intra42_campus)
user.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0010_auto_20220517_1452'),
]
operations = [
migrations.RunPython(migrate_campus),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.11 on 2022-08-01 20:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_migration_campus'),
]
operations = [
migrations.AlterModelOptions(
name='userprofileinfo',
options={'ordering': ['-score', 'last_submission_date', 'user__username', 'campus'], 'permissions': (('view_info', 'View user info'),), 'verbose_name': 'profile', 'verbose_name_plural': 'profiles'},
),
migrations.RemoveField(
model_name='userprofileinfo',
name='intra42_campus',
),
migrations.RemoveField(
model_name='userprofileinfo',
name='intra42_campus_id',
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.11 on 2022-08-18 15:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0012_auto_20220801_2212'),
]
operations = [
migrations.AddField(
model_name='campus',
name='logo',
field=models.URLField(default='https://42.fr'),
preserve_default=False,
),
migrations.AddField(
model_name='campus',
name='url',
field=models.URLField(default='https://42.fr', max_length=100),
preserve_default=False,
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.11 on 2022-08-18 15:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0013_auto_20220818_1741'),
]
operations = [
migrations.AlterField(
model_name='campus',
name='logo',
field=models.URLField(blank=True),
),
migrations.AlterField(
model_name='campus',
name='url',
field=models.URLField(blank=True, max_length=100),
),
]

View File

@ -12,15 +12,29 @@ class UserProfileInfo(models.Model):
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now) last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
token = models.CharField(max_length=200, blank=True) token = models.CharField(max_length=200, blank=True)
discord_id = models.CharField(max_length=20, null=True, blank=True, unique=True) discord_id = models.CharField(max_length=20, null=True, blank=True, unique=True)
intra42_id = models.CharField(max_length=20, null=True, blank=True, unique=True)
campus = models.ForeignKey('Campus', on_delete=models.DO_NOTHING, null=True, blank=True)
member = models.BooleanField(default=False) member = models.BooleanField(default=False)
member_since = models.DateTimeField('Member since', default=timezone.now) member_since = models.DateTimeField('Member since', default=timezone.now)
member_until = models.DateTimeField('Member until', default=timezone.now) member_until = models.DateTimeField('Member until', default=timezone.now)
def __str__(self): def __str__(self):
return self.user.username return self.user.username
class Meta: class Meta:
ordering = ['-score', 'last_submission_date', 'user__username'] ordering = ['-score', 'last_submission_date', 'user__username', 'campus']
verbose_name = 'profile' verbose_name = 'profile'
verbose_name_plural = 'profiles' verbose_name_plural = 'profiles'
permissions = (("view_info", "View user info"),) permissions = (("view_info", "View user info"),)
class Campus(models.Model):
id = models.IntegerField(primary_key=True, unique=True)
name = models.CharField(max_length=50)
url = models.URLField(max_length=100,blank=True)
logo = models.URLField(max_length=200,blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'campus'
verbose_name_plural = 'campuses'
permissions = (("view_info", "View user info"),)
# Create your models here. # Create your models here.

View File

@ -12,7 +12,7 @@
{% trans "Deleted accounts cannot be recovered." %}<br><br> {% trans "Deleted accounts cannot be recovered." %}<br><br>
<div class="col-sm-8 col-md-6 mx-auto"> <div class="col-sm-8 col-md-6 mx-auto">
{% if bad_password %} {% if bad_password %}
<span class="message error-msg">{% trans "Password inccorect." %}</span> <span class="message error-msg">{% trans "Password incorrect." %}</span>
{% elif deleted %} {% elif deleted %}
<span class="message success-msg">{% trans "Your account has been deleted." %}</span> <span class="message success-msg">{% trans "Your account has been deleted." %}</span>
{% endif %} {% endif %}

View File

@ -33,15 +33,53 @@
</br>Token </br>Token
<input type='text' readonly value='{{token}}'> <input type='text' readonly value='{{token}}'>
</br> </br>
<input class="form-control" type="submit" value="{% trans "Apply" %}"> <input class="form-control" type="submit" value="{% trans " Apply" %}">
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div class="ctf-block">
<div class="ctf-head">
<h3>{% trans "Connected accounts" %}</h3>
</div> </div>
<div class="d-none d-md-block col-10 col-md-3 right-sidebar"> <div class="bloc-body">
<div class="d-flex">
{% if user.userprofileinfo.discord_id|length > 0 %}
<form action="{% url 'accounts:connections-disconnect-discord' %}" method='POST'
class="form-inline p-2">
{%csrf_token%}
<button class="btn btn-dark" type="submit">{% trans "Disconnect Discord" %}</button>
</form>
{% else %}
<form action="{% url 'accounts:connections-connect-discord' %}" method='POST'
class="form-inline p-2">
{%csrf_token%}
<button class="btn btn-dark" type="submit">{% trans "Connect Discord" %}</button>
</form>
{% endif %}
</div>
<div class="d-flex">
{% if user.userprofileinfo.intra42_id|length > 0 %}
<form action="{% url 'accounts:connections-disconnect-intra42' %}" method='POST'
class="form-inline p-2">
{%csrf_token%}
<button class="btn btn-dark" type="submit">{% trans "Disconnect 42" %}</button>
</form>
{% else %}
<form action="{% url 'accounts:connections-connect-intra42' %}" method='POST'
class="form-inline p-2">
{%csrf_token%}
<button class="btn btn-dark" type="submit">{% trans "Connect 42" %}</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{{ user.username }}</li> <li class="list-group-item">{{ user.username }}</li>
<li class="list-group-item">{% trans "Score" %} : {{ user.userprofileinfo.score }}</li> <li class="list-group-item">{% trans "Score" %} : {{ user.userprofileinfo.score }}</li>
@ -55,14 +93,20 @@
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"Y-m-d" }}</li> <li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"Y-m-d" }}</li>
</ul> </ul>
<ul class="list-group"> <ul class="list-group">
<form method='GET' action="{% url 'accounts:delete_account' %}"> <form method='GET' action="{% url 'accounts:profile' user %}">
{%csrf_token%}
<li class="list-group-item"> <li class="list-group-item">
<input class="form-control" type="submit" value="{% trans "Delete my account" %}"> <input class="form-control" type="submit" value="{% trans " View my profile" %}">
</li> </li>
</form> </form>
</ul> </ul>
</div> <ul class="list-group">
<form method='GET' action="{% url 'accounts:delete_account' %}">
{%csrf_token%}
<li class="list-group-item">
<input class="form-control" type="submit" value="{% trans " Delete my account" %}">
</li>
</form>
</ul>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -53,9 +53,9 @@
</li> </li>
{% endif %} {% endif %}
{% if member %} {% if member %}
<li class="list-group-item is-member">{% trans "Status: Member" %}</li> <li class="list-group-item">Status: <a class="{{ is_member }}" href="{% url 'resources:becomeMember' %}">{% trans "Member" %}</a></li>
{% else %} {% else %}
<li class="list-group-item">{% trans "Status: Visitor" %}</li> <li class="list-group-item">Status: {% trans " Visitor" %}</li>
{% endif %} {% endif %}
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li> <li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li>
</ul> </ul>
@ -75,6 +75,19 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<ul class="list-group">
<ul class="list-group">
<li class="list-group-item">{% trans "Challenges created" %}</li>
{% if created %}
{% for creat in created %}
<li class="list-group-item"><a href="{% url 'ctf' cat_slug=creat.category.slug ctf_slug=creat.slug %}">{{ creat.name }}</a></li>
{% endfor %}
{% else %}
<li class="list-group-item">{% trans "It seems that this user has not created any challenge yet..." %}</li>
{% endif %}
</ul>
</ul>
</div> </div>
</div> </div>

View File

@ -19,10 +19,10 @@
<form enctype="multipart/form-data" method="POST"> <form enctype="multipart/form-data" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input class="form-control" type="text" name="username" placeholder="{% trans "Username" %} *" maxlength="150" required="" id="id_username"></br> <input class="form-control" type="text" name="username" placeholder="{% trans "Username" %} *" maxlength="150" required="" id="id_username" value="{{ old_username }}"></br>
<input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br> <input class="form-control" type="password" name="password" placeholder="{% trans "Password" %} *" required="" id="id_password"></br>
<input class="form-control" type="email" name="email" placeholder="pleasedontgivemy@private.infos*" required="" maxlength="254" id="id_email"></br> <input class="form-control" type="email" name="email" placeholder="pleasedontgivemy@private.infos*" required="" maxlength="254" id="id_email" value="{{ old_email }}"></br>
<input class="form-control" type="url" name="portfolio_site" placeholder="{% trans "Personal website" %}"maxlength="200" id="id_portfolio_site"></br> <input class="form-control" type="url" name="portfolio_site" placeholder="{% trans "Personal website" %}"maxlength="200" id="id_portfolio_site" value="{{ old_website }}"></br>
<input type="submit" name="" class="form-control" value="{% trans "Register" %}"> <input type="submit" name="" class="form-control" value="{% trans "Register" %}">
</div> </div>
</form> </form>
@ -30,7 +30,6 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-12 col-md-3 right-sidebar"> <div class="col-sm-12 col-md-3 right-sidebar">
<ul class="list-group"> <ul class="list-group">
@ -40,4 +39,3 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -10,8 +10,11 @@ urlpatterns = [
path('edit/', views.edit, name='edit'), path('edit/', views.edit, name='edit'),
path('logout/', views.out, name='out'), path('logout/', views.out, name='out'),
path('rank/<str:token>', views.rank, name='rank'), path('rank/<str:token>', views.rank, name='rank'),
path('connections/connect/discord', views.connection.connect, name='connections-connect-discord'), path('connections/connect/discord', views.connection.connect_discord, name='connections-connect-discord'),
path('connections/connect/discord/authorize', views.connection.authorize, name='connections-connect-discord-authorize'), path('connections/connect/discord/authorize', views.connection.authorize_discord, name='connections-connect-discord-authorize'),
path('connections/disconnect/discord', views.connection.disconnect, name='connections-disconnect-discord'), path('connections/disconnect/discord', views.connection.disconnect_discord, name='connections-disconnect-discord'),
path('connections/connect/intra42', views.connection.connect_intra42, name='connections-connect-intra42'),
path('connections/connect/intra42/authorize', views.connection.authorize_intra42, name='connections-connect-intra42-authorize'),
path('connections/disconnect/intra42', views.connection.disconnect_intra42, name='connections-disconnect-intra42'),
path('delete_account/', views.delete_account, name='delete_account'), path('delete_account/', views.delete_account, name='delete_account'),
] ]

View File

@ -4,6 +4,9 @@ from django.views.decorators.http import require_POST
from django.views.defaults import bad_request from django.views.defaults import bad_request
from django.urls import reverse from django.urls import reverse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.contrib.sites.models import Site
from accounts.models import Campus
from django.db import IntegrityError
import os import os
oauth = OAuth() oauth = OAuth()
@ -18,21 +21,77 @@ oauth.register(
api_base_url='https://discord.com/api/' api_base_url='https://discord.com/api/'
) )
oauth.register(
name='intra42',
client_id=os.getenv('OAUTH2_INTRA42_CLIENT_ID'),
client_secret=os.getenv('OAUTH2_INTRA42_CLIENT_SECRET'),
access_token_url='https://api.intra.42.fr/oauth/token',
authorize_url='https://api.intra.42.fr/oauth/authorize',
api_base_url='https://api.intra.42.fr/'
)
@login_required @login_required
@require_POST @require_POST
def connect(request): def connect_intra42(request):
if request.user.userprofileinfo.intra42_id:
return bad_request(request, "Already connected")
site = Site.objects.get_current()
redirect_uri = reverse('accounts:connections-connect-intra42-authorize')
redirect_uri = "https://" + site.domain + redirect_uri[3:] # remove language code
return oauth.intra42.authorize_redirect(request, redirect_uri)
@login_required
def authorize_intra42(request):
if request.user.userprofileinfo.intra42_id:
return bad_request(request, "Already connected")
try:
token = oauth.intra42.authorize_access_token(request)
except:
return redirect('accounts:edit')
response = oauth.intra42.get('v2/me', token=token)
response = response.json()
intra42_id = response['id']
intra42_campus = response['campus'][0]['name']
intra42_campus_id = response['campus'][0]['id']
request.user.userprofileinfo.intra42_id = intra42_id
request.user.userprofileinfo.campus, created = Campus.objects.get_or_create(id=intra42_campus_id, name=intra42_campus)
try:
request.user.userprofileinfo.save()
return redirect('accounts:edit')
except IntegrityError:
return redirect('accounts:edit')
@login_required
@require_POST
def disconnect_intra42(request):
if not request.user.userprofileinfo.intra42_id:
return bad_request(request, "Already disconnected")
request.user.userprofileinfo.intra42_id = None
request.user.userprofileinfo.intra42_campus = None
request.user.userprofileinfo.intra42_campus_id = None
request.user.userprofileinfo.campus = None
request.user.userprofileinfo.save()
return redirect('accounts:edit')
@login_required
@require_POST
def connect_discord(request):
if request.user.userprofileinfo.discord_id: if request.user.userprofileinfo.discord_id:
return bad_request(request, "Already connected") return bad_request(request, "Already connected")
site = Site.objects.get_current()
redirect_uri = reverse('accounts:connections-connect-discord-authorize') redirect_uri = reverse('accounts:connections-connect-discord-authorize')
redirect_uri = request.build_absolute_uri(redirect_uri) redirect_uri = "https://" + site.domain + redirect_uri[3:] # remove language code
print(redirect_uri)
return oauth.discord.authorize_redirect(request, redirect_uri) return oauth.discord.authorize_redirect(request, redirect_uri)
@login_required @login_required
def authorize(request): def authorize_discord(request):
if request.user.userprofileinfo.discord_id: if request.user.userprofileinfo.discord_id:
return bad_request(request, "Already connected") return bad_request(request, "Already connected")
try:
token = oauth.discord.authorize_access_token(request) token = oauth.discord.authorize_access_token(request)
except:
return redirect('accounts:edit')
response = oauth.discord.get('users/@me', token=token) response = oauth.discord.get('users/@me', token=token)
response = response.json() response = response.json()
discord_id = response['id'] discord_id = response['id']
@ -42,7 +101,7 @@ def authorize(request):
@login_required @login_required
@require_POST @require_POST
def disconnect(request): def disconnect_discord(request):
if not request.user.userprofileinfo.discord_id: if not request.user.userprofileinfo.discord_id:
return bad_request(request, "Already disconnected") return bad_request(request, "Already disconnected")
request.user.userprofileinfo.discord_id = None request.user.userprofileinfo.discord_id = None

View File

@ -43,32 +43,69 @@ def signup(request):
user_form = UserForm() user_form = UserForm()
profile_form = UserProfileInfoForm() profile_form = UserProfileInfoForm()
registered = False registered = False
if request.method == 'POST': if request.method == 'POST':
pass1 = request.POST.get('password') username = request.POST.get('username')
if len(pass1) < 8: passwd = request.POST.get('password')
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':"The new password must be at least %d characters long." % 8}) email = request.POST.get('email')
first_isalpha = pass1[0].isalpha() website = request.POST.get('portfolio_site')
if not any(c.isdigit() for c in pass1) or not any(c.isalpha() for c in pass1):
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("The password must contain at least one letter and at least one digit or punctuation character.")}) if len(passwd) < 8:
return render(request, 'accounts/register.html', {
'user_form': user_form,
'profile_form': profile_form,
'registered_failed': _("The password must be at least 8 characters long."),
'old_username': username,
'old_email': email,
'old_website': website
})
if not any(c.isdigit() for c in passwd) or not any(c.isalpha() for c in passwd):
return render(request, 'accounts/register.html', {
'user_form': user_form,
'profile_form': profile_form,
'registered_failed': _("The password must contain at least one letter and at least one digit or punctuation character."),
'old_username': username,
'old_email': email,
'old_website': website
})
if User.objects.filter(email=request.POST.get('email')).exists(): if User.objects.filter(email=request.POST.get('email')).exists():
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that email already exists.")}) return render(request, 'accounts/register.html', {
'user_form': user_form,
'profile_form': profile_form,
'registered_failed': _("A user with that email already exists."),
'old_username': username,
'old_website': website
})
user_form = UserForm(data=request.POST) user_form = UserForm(data=request.POST)
profile_form = UserProfileInfoForm(data=request.POST) profile_form = UserProfileInfoForm(data=request.POST)
if user_form.is_valid() and profile_form.is_valid(): if user_form.is_valid() and profile_form.is_valid():
user = user_form.save() user = user_form.save()
user.set_password(user.password) user.set_password(user.password)
user.save() user.save()
profile = profile_form.save(commit=False) profile = profile_form.save(commit=False)
profile.user = user profile.user = user
profile.token = token_hex(16) profile.token = token_hex(16)
profile.save() profile.save()
registered = True registered = True
else: else:
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that username already exists.")}) return render(request, 'accounts/register.html', {
return render(request,'accounts/register.html', 'user_form': user_form,
{'user_form':user_form, 'profile_form': profile_form,
'profile_form':profile_form, 'registered_failed': _("A user with that username already exists."),
'registered':registered}) 'old_email': email,
'old_website': website
})
return render(request, 'accounts/register.html', {
'user_form': user_form,
'profile_form': profile_form,
'registered': registered
})
else: else:
return HttpResponseRedirect(reverse('home')) return HttpResponseRedirect(reverse('home'))
@ -124,16 +161,18 @@ def profile(request, user_name):
member = True member = True
else: else:
member = False member = False
cats = Category.objects.all() all_cats = Category.objects.all()
cats = [cat for cat in all_cats if CTF.objects.filter(category__name=cat.name, event=None, disabled=False)]
pointDatas = {} pointDatas = {}
for cat in cats: for cat in cats:
# prepare categories # prepare categories
solved_count = CTF_flags.objects.filter(user=user_obj, ctf__event=None , ctf__category__name=cat.name).count() solved = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name, ctf__event=None, ctf__disabled=False).order_by('flag_date')
max_count = CTF.objects.filter(category__name=cat.name, event=None).count() max_count = CTF.objects.filter(category__name=cat.name, event=None, disabled=False).count()
# get datas # get datas
somme = 0 somme = 0
solved = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name, ctf__event=None).order_by('flag_date') solved_count = len(solved)
pointDatas[cat.name] = [] pointDatas[cat.name] = []
pointDatas[cat.name].append([user_obj.date_joined.timestamp() * 1000, 0]) pointDatas[cat.name].append([user_obj.date_joined.timestamp() * 1000, 0])
percent = (solved_count / max_count) * 100 percent = (solved_count / max_count) * 100
@ -142,27 +181,16 @@ def profile(request, user_name):
somme += flag.ctf.points somme += flag.ctf.points
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme]) pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=None).order_by('-flag_date') solves = CTF_flags.objects.filter(user=user_obj, ctf__event=None, ctf__disabled=False).order_by('-flag_date')
solved = [] solved = []
somme = 0 somme = 0
solved.append([user_obj.date_joined.timestamp() * 1000, 0]) solved.append([user_obj.date_joined.timestamp() * 1000, 0])
for s in solves.reverse(): for s in solves.reverse():
somme += s.ctf.points somme += s.ctf.points
solved.append([s.flag_date.timestamp() * 1000,somme]) solved.append([s.flag_date.timestamp() * 1000,somme])
created = CTF.objects.filter(author=user_obj, event=None)
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas, return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
'rank': rank, 'score' : somme, 'member' : member}) 'rank': rank, 'score' : somme, 'member' : member, 'cats':cats, 'created':created})
def rank(request, token):
all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username')
rank = 1
for elem in all_users:
if elem.token == token:
break
rank += 1
data = {"rank": rank}
return JsonResponse(data)
@login_required @login_required
def delete_account(request): def delete_account(request):
@ -180,3 +208,14 @@ def delete_account(request):
else: else:
return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': False} ) return render(request, 'accounts/delete.html', {'deleted': False, 'bad_password': False} )
def rank(request, token):
all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username')
rank = 1
for elem in all_users:
if elem.token == token:
break
rank += 1
data = {"rank": rank}
return JsonResponse(data)

0
src/api/__init__.py Normal file
View File

3
src/api/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
src/api/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'

View File

3
src/api/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
src/api/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
src/api/urls.py Normal file
View File

@ -0,0 +1,9 @@
from django.urls import path
from . import views
urlpatterns = [
path('bot/discord', views.bot_discord_rank, name='bot_discord_rank'), # legacy, to remove when new bot is deployed
path('bot/discord/rank', views.bot_discord_rank, name='bot_discord_rank'), # use this
path('bot/discord/campus', views.bot_discord_campus, name='bot_discord_campus'),
path('events/<str:event_slug>', views.events_data, name='events_data'),
]

72
src/api/views.py Normal file
View File

@ -0,0 +1,72 @@
from django.shortcuts import render
from accounts.models import UserProfileInfo
from django.http import JsonResponse
import os
from events.models import Event, Team, EventPlayer
from django.shortcuts import get_object_or_404
# Create your views here.
def bot_discord_rank(request):
if request.method != 'GET':
return JsonResponse({'error':'bad request'})
token = request.GET.get('token')
auth_token = os.getenv('BOT_TOKEN')
if (token != auth_token or not auth_token):
return JsonResponse({'error':'not authorized'})
all_users = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')
data = {}
rank = 1
for user in all_users:
if user.discord_id:
data[user.discord_id] = rank
rank += 1
return JsonResponse(data)
def bot_discord_campus(request):
if request.method != 'GET':
return JsonResponse({'error':'bad request'})
token = request.GET.get('token')
auth_token = os.getenv('BOT_TOKEN')
if (token != auth_token or not auth_token):
return JsonResponse({'error':'not authorized'})
all_users = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')
data = {}
for user in all_users:
if user.campus and user.discord_id:
data[user.discord_id] = user.campus.name
return JsonResponse(data)
def events_data(request, event_slug):
if request.method != 'GET':
return JsonResponse({'error':'bad request'})
event_info = get_object_or_404(Event, slug=event_slug)
if event_info.password and request.GET.get('password') != event_info.password:
return JsonResponse({'error':'not authorized'})
players = EventPlayer.objects.filter(event=event_info)
data = {}
if event_info.team_size > 1:
for player in players:
if not player.team:
continue
if not player.team.name in data:
data[player.team.name] = []
data[player.team.name].append({"name": player.user.username, "score": player.score})
else:
for player in players:
data[player.user.username] = player.score
return JsonResponse(data)

View File

@ -2,8 +2,6 @@ from django.contrib import admin
from .models import Category, CTF, CTF_flags from .models import Category, CTF, CTF_flags
admin.site.register(Category) admin.site.register(Category)
#admin.site.register(CTF)
#admin.site.register(CTF_flags)
@admin.register(CTF_flags) @admin.register(CTF_flags)
class ctf_flags(admin.ModelAdmin): class ctf_flags(admin.ModelAdmin):
@ -14,12 +12,61 @@ class ctf_flags(admin.ModelAdmin):
# search list # search list
search_fields = ['ctf__category__name', 'ctf__name', 'user__username'] search_fields = ['ctf__category__name', 'ctf__name', 'user__username']
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
groups = list(request.user.groups.values_list('name', flat=True))
return qs.filter(event__name__in=groups)
def has_view_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_view_permission(request, obj)
def has_change_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_change_permission(request, obj)
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_delete_permission(request, obj)
@admin.register(CTF) @admin.register(CTF)
class ctf(admin.ModelAdmin): class ctf(admin.ModelAdmin):
#list display #list display
list_display = ['name', 'event', 'category'] list_display = ['name', 'event', 'category', 'points']
#list Filter #list Filter
list_filter = ('category', 'event') list_filter = ('category', 'event')
# search list # search list
search_fields = ['category__name', 'name', 'author__username'] search_fields = ['category__name', 'name', 'author__username']
# Register your models here.
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
groups = list(request.user.groups.values_list('name', flat=True))
return qs.filter(event__name__in=groups)
def has_view_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_view_permission(request, obj)
def has_change_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_change_permission(request, obj)

View File

View File

View File

@ -0,0 +1,28 @@
from collections import defaultdict
from django.core.management.base import BaseCommand, CommandError
from accounts.models import UserProfileInfo
from ctfs.models import CTF_flags, CTF
from math import log
class Command(BaseCommand):
help = 'Actualize challenges points based on number of solves'
def handle(self, *args, **options):
challenges = CTF.objects.filter(event=None, disabled=False).exclude(category__name="-Intro-")
for ctf in challenges:
solves = CTF_flags.objects.filter(ctf=ctf)
nb_solves = len(solves)
if nb_solves > 0:
new_points = max(200 - int(log(nb_solves)*8.5)*5, 5)
else:
new_points = 200
if new_points != ctf.points:
diff = ctf.points - new_points
ctf.points = new_points
ctf.save()
for s in solves:
s.user.userprofileinfo.score -= diff
s.user.userprofileinfo.save()

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.5 on 2022-02-03 17:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ctfs', '0006_alter_ctf_event'),
]
operations = [
migrations.AddField(
model_name='ctf',
name='disabled',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.11 on 2022-02-15 16:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ctfs', '0007_ctf_disabled'),
]
operations = [
migrations.RemoveField(
model_name='ctf',
name='description_de',
),
migrations.RemoveField(
model_name='ctf',
name='description_ru',
),
migrations.AddField(
model_name='ctf',
name='port',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.11 on 2023-09-17 17:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ctfs', '0008_auto_20220215_1713'),
]
operations = [
migrations.AddField(
model_name='ctf_flags',
name='bonus',
field=models.PositiveSmallIntegerField(default=0),
),
]

View File

@ -12,12 +12,12 @@ class Category(models.Model):
class CTF(models.Model): class CTF(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
flag = models.CharField(max_length=100) flag = models.CharField(max_length=100)
disabled = models.BooleanField(default=False)
description = models.TextField(blank=True) description = models.TextField(blank=True)
description_en = models.TextField(blank=True) description_en = models.TextField(blank=True)
description_ru = models.TextField(blank=True)
description_de = models.TextField(blank=True)
file = models.FileField(blank=True, upload_to='challenges') file = models.FileField(blank=True, upload_to='challenges')
ctf_url = models.URLField(blank=True) ctf_url = models.URLField(blank=True)
port = models.PositiveSmallIntegerField(null=True, blank=True)
event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.CASCADE) event = models.ForeignKey(Event, null=True, blank=True, on_delete=models.CASCADE)
points = models.PositiveSmallIntegerField() points = models.PositiveSmallIntegerField()
slug = models.SlugField(max_length=55) slug = models.SlugField(max_length=55)
@ -45,6 +45,7 @@ class CTF_flags(models.Model):
user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE) user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
ctf = models.ForeignKey(CTF, unique=False, on_delete=models.CASCADE) ctf = models.ForeignKey(CTF, unique=False, on_delete=models.CASCADE)
flag_date = models.DateTimeField('Flag date') flag_date = models.DateTimeField('Flag date')
bonus = models.PositiveSmallIntegerField(default=0)
class Meta: class Meta:
ordering = ['-flag_date'] ordering = ['-flag_date']

26
src/ctfs/sitemaps.py Normal file
View File

@ -0,0 +1,26 @@
from django.contrib import sitemaps
from django.urls import reverse
from .models import Category, CTF
from .views import category, ctf
class CategorySitemap(sitemaps.Sitemap):
changefreq = "weekly"
priority = 0.7
i18n = True
def items(self):
return Category.objects.all()
def location(self, obj):
return reverse(category, kwargs={'cat_slug': obj.slug})
class CTFSitemap(sitemaps.Sitemap):
changefreq = "weekly"
priority = 0.7
i18n = True
def items(self):
return CTF.objects.all()
def location(self, obj):
return reverse(ctf, kwargs={'cat_slug': obj.category.slug, 'ctf_slug': obj.slug})

@ -0,0 +1 @@
Subproject commit 5c7b5995fe12c0ed1bb10f97e56ec89377c98b54

View File

@ -2,11 +2,13 @@
{% block content %} {% block content %}
{% load i18n %} {% load i18n %}
{% load is_member %} {% load is_member %}
{% load get_chall %}
{% get_current_language as lang %}
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-9"> <div class="col-sm-12 col-md-9">
<div class="ctf-block"> <div class="ctf-block">
<div class="ctf-head"> <div class="ctf-head">
<h3>{{ ctf.name }}</h3> <h1>{{ ctf.name }}</h1>
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small> <small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
</div> </div>
{% if date < ctf.pub_date %} {% if date < ctf.pub_date %}
@ -15,11 +17,11 @@
</div> </div>
{% else %} {% else %}
<div class="ctf-body"> <div class="ctf-body">
{% if description %} {% get_chall_by_lang ctf lang as content %}
{{ description|safe }} {{ content | safe }}
{% else %} <!-- {% if ctf.port %}
{% trans "No translation available. Please try another language (English or French)." %} <b>nc challenges.42ctf.org {{ ctf.port }}</b>
{% endif %} {% endif %} -->
</div> </div>
<div class="ctf-footer"> <div class="ctf-footer">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}

View File

@ -2,8 +2,10 @@
{% block content %} {% block content %}
{% load i18n %} {% load i18n %}
<div class="row"> <div class="row">
<div class="col-12 ctf-head">
<h1>{{ cat.name }}</h1>
</div>
<div class="col-sm-12 col-md-9 news-card"> <div class="col-sm-12 col-md-9 news-card">
<h3>{{ cat.name }}</h3>
{% if ctfs %} {% if ctfs %}
<table class="table table-striped table-dark"> <table class="table table-striped table-dark">
<thead> <thead>

View File

View File

@ -0,0 +1,12 @@
from django import template
register = template.Library()
@register.simple_tag
def get_chall_by_lang(chall, lang):
filepath = "ctfs/templates/challenges/"+ lang + "/" + chall.slug + ".html"
try:
with open(filepath) as fp:
return fp.read()
except:
return chall.description_en

View File

@ -1,7 +1,15 @@
from django.contrib.sitemaps.views import sitemap
from django.urls import path from django.urls import path
from .sitemaps import CategorySitemap, CTFSitemap
from . import views from . import views
sitemaps = {
'categories': CategorySitemap(),
'challenges': CTFSitemap(),
}
urlpatterns = [ urlpatterns = [
path('<str:cat_slug>/', views.category, name='category'), path('<str:cat_slug>/', views.category, name='category'),
path('<str:cat_slug>/<str:ctf_slug>', views.ctf, name='ctf') path('<str:cat_slug>/<str:ctf_slug>', views.ctf, name='ctf'),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
] ]

View File

@ -5,33 +5,38 @@ from .models import Category, CTF, CTF_flags
from .forms import submit_flag from .forms import submit_flag
from accounts.models import UserProfileInfo from accounts.models import UserProfileInfo
from django.utils.translation import get_language from django.utils.translation import get_language
from math import log
from accounts.models import UserProfileInfo
def actualize_points(ctf):
if ctf.category.name == "-Intro-":
return
solves = CTF_flags.objects.filter(ctf=ctf)
nb_solves = len(solves)
new_points = max(200 - int(log(nb_solves)*8.5)*5, 5)
if new_points != ctf.points:
diff = ctf.points - new_points
ctf.points = new_points
ctf.save()
for s in solves:
s.user.userprofileinfo.score -= diff
s.user.userprofileinfo.save()
def get_description_by_lang(ctf):
lang = get_language()
ret = None
if lang == "fr":
ret = ctf.description
elif lang == "en":
ret = ctf.description_en
elif lang == "de":
ret = ctf.description_de
elif lang == "ru":
ret = ctf.description_ru
return ret
def category(request, cat_slug): def category(request, cat_slug):
cat = get_object_or_404(Category, slug=cat_slug) cat = get_object_or_404(Category, slug=cat_slug)
ctfs = CTF.objects.filter(category=cat, event=None).order_by('points') ctfs = CTF.objects.filter(category=cat, event=None, disabled=False).order_by('points')
for ex in ctfs: for ex in ctfs:
ex.solved_num = CTF_flags.objects.filter(ctf=ex).count() ex.solved_num = CTF_flags.objects.filter(ctf=ex).count()
ex.solved = ex.solved_by(request.user) ex.solved = ex.solved_by(request.user)
return render(request, 'ctfs/ctfs_list.html', {'ctfs' : ctfs, 'cat' : cat}) return render(request, 'ctfs/ctfs_list.html', {'ctfs' : ctfs, 'cat' : cat})
def ctf(request, cat_slug, ctf_slug): def ctf(request, cat_slug, ctf_slug):
ctf_info = get_object_or_404(CTF, slug=ctf_slug) ctf_info = get_object_or_404(CTF, slug=ctf_slug, event=None)
flagged = False flagged = False
solved_list = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date') solved_list = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
description = get_description_by_lang(ctf_info)
if request.user.is_authenticated: if request.user.is_authenticated:
if CTF_flags.objects.filter(user=request.user, ctf=ctf_info): if CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
flagged = True flagged = True
@ -39,19 +44,20 @@ def ctf(request, cat_slug, ctf_slug):
if request.user.is_authenticated: if request.user.is_authenticated:
form = submit_flag(data=request.POST) form = submit_flag(data=request.POST)
if flagged == False and form.is_valid(): if flagged == False and form.is_valid():
if CTF.objects.filter(flag=request.POST.get('flag'), slug=ctf_slug): if CTF.objects.filter(flag=request.POST.get('flag'), slug=ctf_slug, event=None):
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now()) new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now())
new.save() new.save()
profil = UserProfileInfo.objects.get(user=request.user) profil = UserProfileInfo.objects.get(user=request.user)
profil.last_submission_date = timezone.now() profil.last_submission_date = timezone.now()
profil.score += ctf_info.points profil.score += ctf_info.points
profil.save() profil.save()
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'valitated': True, 'description': description, 'date': timezone.now()}) actualize_points(ctf_info)
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'valitated': True, 'date': timezone.now()})
else: else:
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'failed': True, 'description': description, 'date': timezone.now()}) return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'failed': True, 'date': timezone.now()})
else: else:
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': True, 'description': description, 'date': timezone.now()}) return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': True, 'date': timezone.now()})
else: else:
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'description': description, 'date': timezone.now()}) return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'date': timezone.now()})
else: else:
return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': flagged, 'description': description, 'date': timezone.now()}) return render(request, 'ctfs/ctf_info.html', { 'ctf' : ctf_info, 'solved_list': solved_list, 'alvalitated': flagged, 'date': timezone.now()})

View File

@ -1,5 +1,5 @@
from django.contrib import admin from django.contrib import admin
from .models import Event, EventPlayer, Team from .models import Event, EventPlayer, Team, Bonus
@admin.register(Event) @admin.register(Event)
class event(admin.ModelAdmin): class event(admin.ModelAdmin):
@ -8,6 +8,27 @@ class event(admin.ModelAdmin):
# search list # search list
search_fields = ['name', 'slug', 'description', 'password'] search_fields = ['name', 'slug', 'description', 'password']
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
groups = list(request.user.groups.values_list('name', flat=True))
return qs.filter(name__in=groups)
def has_view_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.name).exists()
return super().has_view_permission(request, obj)
def has_change_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.name).exists()
return super().has_change_permission(request, obj)
@admin.register(EventPlayer) @admin.register(EventPlayer)
class score(admin.ModelAdmin): class score(admin.ModelAdmin):
#list display #list display
@ -17,7 +38,33 @@ class score(admin.ModelAdmin):
# search list # search list
search_fields = ['user__username', 'score', 'event__name'] search_fields = ['user__username', 'score', 'event__name']
# Register your models here. def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
groups = list(request.user.groups.values_list('name', flat=True))
return qs.filter(event__name__in=groups)
def has_view_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_view_permission(request, obj)
def has_change_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_change_permission(request, obj)
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_delete_permission(request, obj)
@admin.register(Team) @admin.register(Team)
class team(admin.ModelAdmin): class team(admin.ModelAdmin):
@ -27,3 +74,36 @@ class team(admin.ModelAdmin):
list_filter = ('event',) list_filter = ('event',)
# search list # search list
search_fields = ['name'] search_fields = ['name']
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
groups = list(request.user.groups.values_list('name', flat=True))
return qs.filter(event__name__in=groups)
def has_view_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_view_permission(request, obj)
def has_change_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_change_permission(request, obj)
def has_delete_permission(self, request, obj=None):
if request.user.is_superuser:
return True
if obj is not None:
return request.user.groups.filter(name=obj.event.name).exists()
return super().has_delete_permission(request, obj)
@admin.register(Bonus)
class bonus(admin.ModelAdmin):
#list display
list_display = ['points', 'absolute']

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.5 on 2022-02-12 18:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0007_event_auto_match'),
]
operations = [
migrations.AddField(
model_name='event',
name='dynamic',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.11 on 2022-02-15 16:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0008_event_dynamic'),
]
operations = [
migrations.AlterField(
model_name='eventplayer',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='team',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.11 on 2022-05-30 07:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_migration_campus'),
('events', '0009_auto_20220215_1706'),
]
operations = [
migrations.AddField(
model_name='event',
name='campus',
field=models.ManyToManyField(blank=True, to='accounts.Campus'),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.11 on 2023-09-17 17:00
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('events', '0010_event_campus'),
]
operations = [
migrations.CreateModel(
name='Bonus',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('absolute', models.BooleanField(default=True)),
('points', models.CharField(max_length=100, validators=[django.core.validators.int_list_validator])),
],
),
migrations.AddField(
model_name='event',
name='bonus',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.bonus'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.11 on 2023-09-17 18:38
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('events', '0011_bonus_points'),
]
operations = [
migrations.AlterField(
model_name='event',
name='bonus',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.bonus'),
),
migrations.AlterField(
model_name='eventplayer',
name='team',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.team'),
),
]

View File

@ -1,9 +1,17 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.models import timezone from django.contrib.auth.models import timezone
from django.core.validators import int_list_validator
import uuid import uuid
from accounts.models import Campus
# Create your models here. # Create your models here.
class Bonus(models.Model):
absolute = models.BooleanField(default=True)
points = models.CharField(validators=[int_list_validator], max_length=100)
def __str__(self):
return self.points
class Event(models.Model): class Event(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
@ -16,6 +24,9 @@ class Event(models.Model):
slug = models.SlugField(max_length=55) slug = models.SlugField(max_length=55)
team_size = models.PositiveIntegerField(default=1) team_size = models.PositiveIntegerField(default=1)
auto_match = models.BooleanField(default=False) auto_match = models.BooleanField(default=False)
dynamic = models.BooleanField(default=False)
campus = models.ManyToManyField(Campus, blank=True)
bonus = models.ForeignKey(Bonus, null=True, on_delete=models.SET_NULL, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name
@ -34,8 +45,6 @@ class EventPlayer(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE) event = models.ForeignKey(Event, on_delete=models.CASCADE)
score = models.PositiveIntegerField(default=0, db_index=True) score = models.PositiveIntegerField(default=0, db_index=True)
last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now) last_submission_date = models.DateTimeField('Last Submission Date', default=timezone.now)
team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True) team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, blank=True)
class Meta: class Meta:
ordering = ['-score', 'last_submission_date', 'user__username'] ordering = ['-score', 'last_submission_date', 'user__username']

24
src/events/sitemaps.py Normal file
View File

@ -0,0 +1,24 @@
from django.contrib import sitemaps
from django.urls import reverse
from .models import Event
class EventsSitemap(sitemaps.Sitemap):
changefreq = "daily"
priority = 0.7
i18n = True
def items(self):
return Event.objects.all()
def location(self, obj):
return reverse('events:event_info', kwargs={'event_slug': obj.slug})
class StaticViewSitemap(sitemaps.Sitemap):
priority = 0.7
changefreq = 'daily'
i18n = True
def items(self):
return ['events:events']
def location(self, item):
return reverse(item)

View File

@ -4,6 +4,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-9"> <div class="col-sm-12 col-md-9">
<div class="ctf-block"> <div class="ctf-block">
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
<div class="ctf-head"> <div class="ctf-head">
<h3>{{ event.name }}</h3> <h3>{{ event.name }}</h3>
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small> <small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
@ -15,6 +16,9 @@
{% if registered == False %} {% if registered == False %}
<span class="message error-msg">{% trans "You need to be registered to the event." %}</span> <span class="message error-msg">{% trans "You need to be registered to the event." %}</span>
{% else %} {% else %}
{% if invalid == True %}
<span class="message error-msg">{% trans "Invalid characters in name" %}</span>
{% endif %}
{% if exist == True %} {% if exist == True %}
<span class="message error-msg">{% trans "Name already taken." %}</span> <span class="message error-msg">{% trans "Name already taken." %}</span>
{% endif %} {% endif %}
@ -38,16 +42,18 @@
<div class="d-none d-md-block col-10 col-md-3 right-sidebar"> <div class="d-none d-md-block col-10 col-md-3 right-sidebar">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{{ event.name }}</li> <li class="list-group-item">{{ event.name }}</li>
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y'}}</li> <li class="list-group-item">{% trans "Starts at" %} : <span style="position:absolute;right: 15px;">{{ event.start_date | date:'H:i d-m-y'}}</span></li>
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y'}}</li> <li class="list-group-item">{% trans "Ends at" %} : <span style="position:absolute;right: 15px;">{{ event.end_date | date:'H:i d-m-y'}}</span></li>
</ul> </ul>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans "Manage my team" %}</li>
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">→{% trans "Join Team" %}</a>
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a> <a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a>
</ul> </ul>
{% if event.auto_match %} {% if event.auto_match %}
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans "Auto-matching" %}</li>
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}"> <form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
{%csrf_token%} {%csrf_token%}
<li class="list-group-item"> <li class="list-group-item">

View File

@ -7,7 +7,7 @@
<div class="ctf-block"> <div class="ctf-block">
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a> <a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
<div class="ctf-head"> <div class="ctf-head">
<h2>{% trans "Event" %} - {{ event.name }}</h2> <h1>{% trans "Event" %} - {{ event.name }}</h1>
<h4>{{ ctf.name }}</h4> <h4>{{ ctf.name }}</h4>
<small>{% trans "Published date" %} : {{ ctf.pub_date }}</small> <small>{% trans "Published date" %} : {{ ctf.pub_date }}</small>
</div> </div>
@ -20,16 +20,11 @@
</div> </div>
<div class="ctf-footer"> <div class="ctf-footer">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
{% if subisover == True %}
<span class="message error-msg">{% trans "Subscriptions is over." %}</span>
{% endif %}
{% if alreadyregistered == True %}
<span class="message error-msg">{% trans "You're already registered to this event." %}</span>
{% endif %}
{% if congrat == True %} {% if congrat == True %}
<p>{% trans "Congratulation !" %}</p> <p>{% trans "Congratulation !" %}</p>
{% if bonus|add:"0" > 0 %}
<p>{% trans "Bonus points awarded" %} : {{ bonus }}</p>
{% endif %}
{% elif alreadyflag == True %} {% elif alreadyflag == True %}
<p>{% trans "Already flagged" %}</p> <p>{% trans "Already flagged" %}</p>
{% elif eventisover == True %} {% elif eventisover == True %}
@ -37,7 +32,9 @@
{% elif errorform == True %} {% elif errorform == True %}
<p>{% trans "Error while processing your request. (Invalid Form)" %}</p> <p>{% trans "Error while processing your request. (Invalid Form)" %}</p>
{% elif notsub == True %} {% elif notsub == True %}
<span class="message error-msg">{% trans "Error: you're not registered to this event, so you can't register scores, fucking logic." %}</span> <span class="message error-msg">{% trans "You must register to the event before submitting flags." %}</span>
{% elif noteam == True %}
<span class="message error-msg">{% trans "This is a team event, please create or join a team before submitting flags." %}</span>
{% if ctf.ctf_url %} {% if ctf.ctf_url %}
<a class="begin-ctf-link" target="_blank" href="{{ ctf.ctf_url }}">{% trans "Start the challenge" %}</a></br> <a class="begin-ctf-link" target="_blank" href="{{ ctf.ctf_url }}">{% trans "Start the challenge" %}</a></br>
{% elif ctf.file %} {% elif ctf.file %}
@ -102,6 +99,9 @@
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans "Author" %} : <a style="position:absolute;right: 15px;" class="profile_link {{is_member}}" href="{% url 'accounts:profile' user_name=ctf.author.username %}">{{ ctf.author.username }}</a></li> <li class="list-group-item">{% trans "Author" %} : <a style="position:absolute;right: 15px;" class="profile_link {{is_member}}" href="{% url 'accounts:profile' user_name=ctf.author.username %}">{{ ctf.author.username }}</a></li>
<li class="list-group-item">{% trans "Point reward" %} : <span style="position:absolute;right: 15px;">{{ ctf.points }}</span></li> <li class="list-group-item">{% trans "Point reward" %} : <span style="position:absolute;right: 15px;">{{ ctf.points }}</span></li>
{% if ctf.event.bonus %}
<li class="list-group-item">{% trans "Speed Bonuses" %} : <span style="position:absolute;right: 15px;">{{ bonus_points }}</span></li>
{% endif %}
</ul> </ul>
</div> </div>

View File

@ -13,7 +13,7 @@
{% endif %} {% endif %}
<div class="event-block"> <div class="event-block">
<div class="event-head" style="background-image:linear-gradient(180deg, rgba(102,102,102,0.3) 100%, rgba(29,29,29,1) 100%),url('{{ event.img }}');"> <div class="event-head" style="background-image:linear-gradient(180deg, rgba(102,102,102,0.3) 100%, rgba(29,29,29,1) 100%),url('{{ event.img }}');">
<h3>{{ event.name }}</h3> <h1>{{ event.name }}</h1>
{% if ended == True %} {% if ended == True %}
<small>{% trans "This event is over." %}</small> <small>{% trans "This event is over." %}</small>
{% else %} {% else %}
@ -26,7 +26,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="event-footer"> <div class="event-footer">
{% if begun == True %} {% if begun == True or is_event_manager == True %}
<h4>{% trans "Challenges" %}</h4> <h4>{% trans "Challenges" %}</h4>
{% if ctfs %} {% if ctfs %}

View File

@ -11,6 +11,21 @@
<div class="ctf-footer"> <div class="ctf-footer">
{% if logged == True %} {% if logged == True %}
{% if userHasCampus == False %}
<span class="message error-msg">
{% trans "This event is reserved for one or more 42 campuses. If you have not connected your intranet to 42CTF, you can do so with this button: " %}
<form action="{% url 'accounts:connections-connect-intra42' %}" method='POST' class="form-inline p-2">
{%csrf_token%}
<button class="btn btn-dark" type="submit">{% trans "Connect 42" %}</button>
</form>
</span>
{% endif %}
{% if campusCanJoin == False %}
<span class="message error-msg">
{% trans "This event is reserved for one or more 42 campuses. And unfortunately your campus can't participate. Do not hesitate to contact us to organize an event on your campus!" %}
</span>
{% endif %}
{% if userHasCampus == True and campusCanJoin == True %}
{% if wrongpwd == True %} {% if wrongpwd == True %}
<span class="message error-msg">{% trans "Wrong password submited." %}</span> <span class="message error-msg">{% trans "Wrong password submited." %}</span>
{% endif %} {% endif %}
@ -24,6 +39,7 @@
<input type="text" name="password" maxlength="48" required=""> <input type="text" name="password" maxlength="48" required="">
<input class="form-control" type="submit" value=">"> <input class="form-control" type="submit" value=">">
</form> </form>
{% endif %}
{% else %} {% else %}
<h4>{% trans "You need to be logged to access this event." %}</h4> <h4>{% trans "You need to be logged to access this event." %}</h4>
{% endif %} {% endif %}

View File

@ -2,8 +2,8 @@
{% block content %} {% block content %}
{% load i18n %} {% load i18n %}
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12 ctf-head">
<h3>{% trans "Events" %}</h3> <h1>{% trans "Events" %}</h1>
</div> </div>
{% if events %} {% if events %}
{% for ev in events %} {% for ev in events %}

View File

@ -4,6 +4,7 @@
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-9"> <div class="col-sm-12 col-md-9">
<div class="ctf-block"> <div class="ctf-block">
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
<div class="ctf-head"> <div class="ctf-head">
<h3>{{ event.name }}</h3> <h3>{{ event.name }}</h3>
<small>{% trans "This event starts at" %} : {{ event.start_date }}</small> <small>{% trans "This event starts at" %} : {{ event.start_date }}</small>
@ -43,16 +44,18 @@
<div class="d-none d-md-block col-10 col-md-3 right-sidebar"> <div class="d-none d-md-block col-10 col-md-3 right-sidebar">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{{ event.name }}</li> <li class="list-group-item">{{ event.name }}</li>
<li class="list-group-item">{% trans "Starts at" %} : {{ event.start_date | date:'H:i d-m-y' }}</li> <li class="list-group-item">{% trans "Starts at" %} : <span style="position:absolute;right: 15px;">{{ event.start_date | date:'H:i d-m-y'}}</span></li>
<li class="list-group-item">{% trans "Ends at" %} : {{ event.end_date | date:'H:i d-m-y' }}</li> <li class="list-group-item">{% trans "Ends at" %} : <span style="position:absolute;right: 15px;">{{ event.end_date | date:'H:i d-m-y'}}</span></li>
</ul> </ul>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans "Manage my team" %}</li>
<a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a> <a href="{% url 'events:join_team' event_slug=event.slug %}" class="list-group-item">{% trans "Join Team" %}</a>
<a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a> <a href="{% url 'events:create_team' event_slug=event.slug %}" class="list-group-item">{% trans "Create Team" %}</a>
</ul> </ul>
{% if event.auto_match %} {% if event.auto_match %}
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">{% trans "Auto-matching" %}</li>
<form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}"> <form method='GET' action="{% url 'events:find_team' event_slug=event.slug %}">
{%csrf_token%} {%csrf_token%}
<li class="list-group-item"> <li class="list-group-item">

View File

@ -45,14 +45,28 @@
{% for p in members %} {% for p in members %}
<li class="list-group-item"><a class="profile_link" href="{% url 'accounts:profile' user_name=p.user.username %}">{{ p.user.username }}</a></li> <li class="list-group-item"><a class="profile_link" href="{% url 'accounts:profile' user_name=p.user.username %}">{{ p.user.username }}</a></li>
{% endfor %} {% endfor %}
</ul>
<form method='POST' action="{% url 'events:leave_team' event_slug=player.event.slug %}"> <form method='POST' action="{% url 'events:leave_team' event_slug=player.event.slug %}">
{%csrf_token%} {%csrf_token%}
<li class="list-group-item"> <li class="list-group-item">
<input class="form-control" type="submit" value="{% trans "Leave Team" %}"> <input class="form-control" type="submit" value="{% trans "Leave Team" %}">
</li> </li>
</form> </form>
{% if player.team.auto == False and player.event.auto_match == True %}
</ul> <form method='POST' action="{% url 'events:open_team' event_slug=player.event.slug %}">
{%csrf_token%}
<li class="list-group-item">
<input class="form-control" type="submit" value="{% trans "Open to automatching" %}">
</li>
</form>
{% elif player.event.auto_match == True %}
<form method='POST' action="{% url 'events:close_team' event_slug=player.event.slug %}">
{%csrf_token%}
<li class="list-group-item">
<input class="form-control" type="submit" value="{% trans "Close to automatching" %}">
</li>
</form>
{% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,168 @@
{% extends 'base.html' %}
{% block content %}
{% load i18n %}
{% load key_value %}
{% load is_member %}
{% ismember user.userprofileinfo as is_member %}
<div class="row">
<div class="col-sm-12 col-md-9">
<a href="{% url 'events:event_info' event_slug=event.slug %}">< Back to event</a>
<div>
<h4>{% trans "Challenges Solved by" %} <span class="{{ is_member }}">{{ user.username }} - {{ event.name }}</span></h4>
{% if solves%}
<div class="table table-dark">
<div class="card-body">
<div id="time-chart"></div>
</div>
</div>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">{% trans "Challenge Name" %}</th>
<th scope="col">{% trans "Category" %}</th>
<th scope="col">{% trans "Points" %}</th>
<th scope="col">{% trans "Bonus" %}</th>
<th scope="col">{% trans "Date" %}</th>
</tr>
</thead>
<tbody>
{% for s in solves %}
<tr>
<th scope="row"><a href="{% url 'events:event_chall_info' event_slug=event.slug chall_slug=s.ctf.slug %}">{{ s.ctf.name }}</a></th>
<td>{{ s.ctf.category.name}}</td>
<td>{{ s.ctf.points }}</td>
<td>{{ s.bonus }}</td>
<td>{{ s.flag_date|date:"Y-m-d H:i:s" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans "It seems that this user has not solved any challenge yet..." %}</p>
{% endif %}
</div>
</div>
<div class="d-none d-md-block col-10 col-md-3 right-sidebar">
<ul class="list-group">
<li class="list-group-item {{ is_member }}">{{ user.username }}</li>
<li class="list-group-item">{% trans "Score" %} : {{ score }}</li>
<li class="list-group-item">{% trans "Rank" %} : {{ rank }}</li>
{% if user.userprofileinfo.portfolio_site %}
<li class="list-group-item">
<a href="{{ user.userprofileinfo.portfolio_site }}" target="_blank">
{{ user.userprofileinfo.portfolio_site }}
</a>
</li>
{% endif %}
{% if member %}
<li class="list-group-item is-member">{% trans "Status: Member" %}</li>
{% else %}
<li class="list-group-item">{% trans "Status: Visitor" %}</li>
{% endif %}
<li class="list-group-item">{% trans "Registered since" %} {{ user.date_joined|date:"d-m-Y" }}</li>
</ul>
<ul class="list-group">
<li class="list-group-item">{% trans "Categories stats" %}</li>
{% for cat in catsDatas %}
<li class="list-group-item" style="padding-bottom: 3;padding-top: 0;">
<span>{{ cat.0 }}</span>
<div class="progress">
{% if cat.3 == '0' %}
<div class="progress-bar bg-success" role="progressbar" style="width: 0%;color:#d9d9d9;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0 %</div>
{% else %}
<div class="progress-bar bg-success" role="progressbar" style="width: {{ cat.3 }}%" aria-valuenow="{{ cat.3 }}" aria-valuemin="0" aria-valuemax="100">{{ cat.3 }} %</div>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
<ul class="list-group">
<ul class="list-group">
<li class="list-group-item">{% trans "Challenges created" %}</li>
{% if created %}
{% for creat in created %}
<li class="list-group-item"><a href="{% url 'ctf' cat_slug=creat.category.slug ctf_slug=creat.slug %}">{{ creat.name }}</a></li>
{% endfor %}
{% else %}
<li class="list-group-item">{% trans "It seems that this user has not created any challenge yet..." %}</li>
{% endif %}
</ul>
</ul>
</div>
</div>
<script src="https://code.highcharts.com/highcharts.src.js"></script>
<script>
Highcharts.theme={colors:["#2b908f","#90ee7e","#f45b5b","#7798BF","#aaeeee","#ff0066","#eeaaee","#55BF3B","#DF5353","#7798BF","#aaeeee"],chart:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:1,y2:1},stops:[[0,"#1D1D1D"],[1,"#1D1D1D"]]},style:{fontFamily:"'Unica One', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",textTransform:"uppercase",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3",textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#F0F0F3",style:{fontSize:"13px"}},marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{backgroundColor:"#1D1D1D",itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"},title:{style:{color:"#C0C0C0"}}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}},rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}},scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"}};
Highcharts.setOptions(Highcharts.theme);
Highcharts.chart('time-chart', {
title: {
text: 'Points earned for each category'
},
yAxis: {
title: {
text: 'Points earned'
}
},
xAxis: {
type: 'datetime',
labels: {
formatter: function() {
return Highcharts.dateFormat('%d.%b %Y',
this.value);
}
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle'
},
plotOptions: {
pointStart: {{ user.date_joined|timestamp_fromdate }},
series: {
label: {
connectorAllowed: false
},
allowPointSelect: true,
marker: {
enabled: true
}
}
},
series: [
{
name: 'Total',
data: {{ solved|safe }}
},
{% for cat in cats %}
{
name: '{{ cat.name }}',
data: {{ pointDatas|keyvalue:cat.name|safe }},
visible: false,
},
{% endfor %}
],
responsive: {
rules: [{
condition: {
maxWidth: 500
},
chartOptions: {
legend: {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom'
}
}
}]
}
});
</script>
{% endblock %}

View File

@ -1,10 +1,18 @@
from django.contrib.sitemaps.views import sitemap
from django.urls import path from django.urls import path
from .sitemaps import StaticViewSitemap, EventsSitemap
from . import views from . import views
app_name = "events" app_name = "events"
sitemaps = {
'events': EventsSitemap(),
'static': StaticViewSitemap(),
}
urlpatterns = [ urlpatterns = [
path('', views.events, name='events'), path('', views.events, name='events'),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
path('<str:event_slug>', views.event, name='event_info'), path('<str:event_slug>', views.event, name='event_info'),
path('<str:event_slug>/challenge/<str:chall_slug>', views.chall_event_info, name='event_chall_info'), path('<str:event_slug>/challenge/<str:chall_slug>', views.chall_event_info, name='event_chall_info'),
path('pwd/<str:event_slug>', views.submit_pwd, name='submit_pwd'), path('pwd/<str:event_slug>', views.submit_pwd, name='submit_pwd'),
@ -17,4 +25,6 @@ urlpatterns = [
path('<str:event_slug>/manage_team', views.manage_team, name='manage_team'), path('<str:event_slug>/manage_team', views.manage_team, name='manage_team'),
path('<str:event_slug>/leave_team', views.leave_team, name='leave_team'), path('<str:event_slug>/leave_team', views.leave_team, name='leave_team'),
path('find_team/<str:event_slug>', views.find_team, name='find_team'), path('find_team/<str:event_slug>', views.find_team, name='find_team'),
path('<str:event_slug>/open_team', views.open_team, name='open_team'),
path('<str:event_slug>/close_team', views.close_team, name='close_team'),
] ]

View File

@ -4,9 +4,11 @@ from django.contrib.auth.models import timezone
from ..forms import submit_flag from ..forms import submit_flag
from ..models import Event, EventPlayer, Team from ..models import Event, EventPlayer, Team
from ctfs.models import CTF, CTF_flags, Category from ctfs.models import CTF, CTF_flags, Category
from accounts.models import UserProfileInfo
from django.utils.translation import get_language from django.utils.translation import get_language
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from math import log
def get_description_by_lang(ctf): def get_description_by_lang(ctf):
lang = get_language() lang = get_language()
@ -21,6 +23,49 @@ def get_description_by_lang(ctf):
ret = ctf.description_ru ret = ctf.description_ru
return ret return ret
def actualize_points(ctf):
solves = CTF_flags.objects.filter(ctf=ctf)
nb_solves = len(solves)
new_points = max(50 - int(log(nb_solves)*2.5)*5, 5)
if new_points != ctf.points:
diff = ctf.points - new_points
ctf.points = new_points
ctf.save()
for s in solves:
player = EventPlayer.objects.get(event=ctf.event, user=s.user)
player.score -= diff
player.save()
if player.team:
player.team.score -= diff
player.team.save()
def compute_bonus_points(ctf):
if not ctf.event.bonus:
return 0
solves = CTF_flags.objects.filter(ctf=ctf)
bonuses = ctf.event.bonus.points.split(',')
if len(solves) >= len(bonuses):
return 0
else:
if ctf.event.bonus.absolute == True:
return int(bonuses[len(solves)])
else:
return int(bonuses[len(solves)]) * ctf.points // 100
def format_bonus_points(ctf):
if not ctf.event.bonus:
return None
bonuses = ctf.event.bonus.points.split(',')
if ctf.event.bonus.absolute == True:
return ''.join([b + ', ' for b in bonuses])[:-2]
return ''.join([str(ctf.points * int(b) // 100) + ', ' for b in bonuses])[:-2]
# Create your views here. # Create your views here.
def events(request): def events(request):
list_events = Event.objects.filter().order_by('-end_date', 'start_date') list_events = Event.objects.filter().order_by('-end_date', 'start_date')
@ -29,7 +74,9 @@ def events(request):
def chall_event_info(request, event_slug, chall_slug): def chall_event_info(request, event_slug, chall_slug):
event_info = get_object_or_404(Event, slug=event_slug) event_info = get_object_or_404(Event, slug=event_slug)
ctf_info = get_object_or_404(CTF, event__slug=event_info.slug, slug=chall_slug) ctf_info = get_object_or_404(CTF, event__slug=event_info.slug, slug=chall_slug)
if timezone.now() < ctf_info.pub_date:
is_event_manager = request.user.groups.filter(name=event_info.name).exists() or request.user.is_superuser
if timezone.now() < ctf_info.pub_date and not is_event_manager:
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
eventisover = False eventisover = False
alreadyflag = False alreadyflag = False
@ -37,14 +84,16 @@ def chall_event_info(request, event_slug, chall_slug):
wrongflag = False wrongflag = False
errorform = False errorform = False
notsub = False notsub = False
noteam = False
player = None player = None
bonus = 0
if request.user.is_authenticated and not request.user.is_staff: if request.user.is_authenticated and not request.user.is_staff:
player = EventPlayer.objects.filter(event=event_info, user=request.user) player = EventPlayer.objects.filter(event=event_info, user=request.user)
if not player: if not player:
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
elif not request.user.is_authenticated: elif not request.user.is_authenticated:
return redirect('accounts:signin') return redirect('accounts:signin')
if request.GET.get('EventIsOver'): if request.GET.get('EventIsOver') or timezone.now() > event_info.end_date:
eventisover = True eventisover = True
if request.GET.get('AlreadyFlagged'): if request.GET.get('AlreadyFlagged'):
alreadyflag = True alreadyflag = True
@ -56,6 +105,10 @@ def chall_event_info(request, event_slug, chall_slug):
errorform = True errorform = True
if request.GET.get('NotRegistered'): if request.GET.get('NotRegistered'):
notsub = True notsub = True
if request.GET.get('NoTeam'):
noteam = True
bonus = request.GET.get('Bonus')
bonus_points = format_bonus_points(ctf_info)
solved_challs = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date') solved_challs = CTF_flags.objects.filter(ctf=ctf_info).order_by('flag_date')
solved_list = [] solved_list = []
for s in solved_challs: for s in solved_challs:
@ -65,56 +118,65 @@ def chall_event_info(request, event_slug, chall_slug):
solved_list.append([s.user, s.flag_date]) solved_list.append([s.user, s.flag_date])
description = get_description_by_lang(ctf_info) description = get_description_by_lang(ctf_info)
return render(request, 'events/ctf_info.html', { 'ctf' : ctf_info, 'event':event_info, 'solved_list': solved_list, 'description': description, 'eventisover': eventisover, 'alreadyflag': alreadyflag, return render(request, 'events/ctf_info.html', { 'ctf' : ctf_info, 'event':event_info, 'solved_list': solved_list, 'description': description, 'eventisover': eventisover, 'alreadyflag': alreadyflag,
'congrat': congrat, 'wrongflag': wrongflag, 'errorform': errorform, 'notsub': notsub}) 'congrat': congrat, 'wrongflag': wrongflag, 'errorform': errorform, 'notsub': notsub, 'noteam':noteam, 'bonus':bonus, 'bonus_points':bonus_points})
def event(request, event_slug): def event(request, event_slug):
event_info = get_object_or_404(Event, slug=event_slug) event_info = get_object_or_404(Event, slug=event_slug)
IsRegistered = False
wrongpwd = False wrongpwd = False
alreadyregistered = False alreadyregistered = False
subisover = False subisover = False
is_event_manager = request.user.groups.filter(name=event_info.name).exists() or request.user.is_superuser
ended = (timezone.now() >= event_info.end_date)
begun = (timezone.now() >= event_info.start_date)
if is_event_manager: # we want to see all the challenges
challenges = CTF.objects.filter(event=event_info).order_by('category', 'points')
else:
challenges = CTF.objects.filter(event=event_info, pub_date__lte=timezone.now()).order_by('category', 'points')
if event_info.team_size == 1:
solved_list = EventPlayer.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'user__username')
else:
solved_list = Team.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'name')
if request.GET.get('WrongPassword'): if request.GET.get('WrongPassword'):
wrongpwd = True wrongpwd = True
if request.GET.get('AlreadyRegistered'): if request.GET.get('AlreadyRegistered'):
alreadyregistered = True alreadyregistered = True
if request.GET.get('SubscriptionIsOver'): if request.GET.get('SubscriptionIsOver'):
subisover = True subisover = True
if request.user.is_authenticated: if request.user.is_authenticated:
try: try:
player = EventPlayer.objects.get(event=event_info, user=request.user) EventPlayer.objects.get(event=event_info, user=request.user)
return render(request, 'events/event_info.html', {'event' : event_info, 'IsRegistered': True, 'ctfs': challenges, 'solved_list':solved_list,
'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover, 'is_event_manager':is_event_manager})
except: except:
player = None pass
if player:
IsRegistered = True if (event_info.campus.all() or event_info.password) and request.user.is_authenticated is False:
if not player.team and event_info.team_size > 1: return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': False})
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
if event_info.password: if event_info.campus.all() and is_event_manager is False:
if request.user.is_authenticated: user = UserProfileInfo.objects.get(user=request.user)
if request.user.is_staff is False: if user.campus is None:
if not player: return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': False, 'campusCanJoin': True})
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered}) elif user.campus not in event_info.campus.all():
elif not player.team and event_info.team_size > 1: return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': True, 'campusCanJoin': False})
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
else: if event_info.password and is_event_manager is False:
return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': False, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered}) return render(request, 'events/event_pwd.html', {'event' : event_info, 'logged': True, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'userHasCampus': True, 'campusCanJoin': True})
ended = False
if timezone.now() >= event_info.end_date: return render(request, 'events/event_info.html', {'event' : event_info, 'ctfs': challenges, 'solved_list':solved_list, 'IsRegistered': False,
ended = True 'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover, 'is_event_manager':is_event_manager})
begun = False
if timezone.now() >= event_info.start_date:
begun = True
challenges = CTF.objects.filter(event=event_info).order_by('category', 'points')
if event_info.team_size == 1:
solved_list = EventPlayer.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'user__username')
else:
solved_list = Team.objects.filter(event=event_info).order_by('-score', 'last_submission_date', 'name')
return render(request, 'events/event_info.html', {'event' : event_info, 'IsRegistered': IsRegistered, 'ctfs': challenges, 'solved_list':solved_list,
'ended': ended, 'begun': begun, 'wrongpwd': wrongpwd, 'alreadyregistered': alreadyregistered, 'subisover': subisover})
@login_required @login_required
def submit_event_flag(request, event_slug, chall_slug): def submit_event_flag(request, event_slug, chall_slug):
ev = get_object_or_404(Event, slug=event_slug) ev = get_object_or_404(Event, slug=event_slug)
response = redirect('events:event_chall_info', event_slug=event_slug, chall_slug=chall_slug) response = redirect('events:event_chall_info', event_slug=event_slug, chall_slug=chall_slug)
flagged = False
if timezone.now() >= ev.end_date: if timezone.now() >= ev.end_date:
response['Location'] += '?EventIsOver=1' response['Location'] += '?EventIsOver=1'
@ -126,11 +188,17 @@ def submit_event_flag(request, event_slug, chall_slug):
response['Location'] += '?ChallengeNotFound=1' response['Location'] += '?ChallengeNotFound=1'
return response return response
flagged = False try:
player = EventPlayer.objects.get(user=request.user, event=ev) player = EventPlayer.objects.get(event=ev, user=request.user)
except:
player = None
if player: if player:
if ev.team_size == 1 and CTF_flags.objects.filter(user=request.user, ctf=ctf_info): if ev.team_size > 1 and player.team is None:
response['Location'] += '?NoTeam=1'
return response
if ev.team_size == 1:
if CTF_flags.objects.filter(user=request.user, ctf=ctf_info):
flagged = True flagged = True
else: else:
solved_list = CTF_flags.objects.filter(ctf=ctf_info) solved_list = CTF_flags.objects.filter(ctf=ctf_info)
@ -146,18 +214,21 @@ def submit_event_flag(request, event_slug, chall_slug):
if form.is_valid(): if form.is_valid():
if ctf_info.flag == request.POST.get('flag'): if ctf_info.flag == request.POST.get('flag'):
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now()) bonus = compute_bonus_points(ctf_info)
new = CTF_flags(user = request.user, ctf = ctf_info, flag_date = timezone.now(), bonus = bonus)
new.save() new.save()
if ctf_info.points > 0: if ctf_info.points > 0:
player.last_submission_date = timezone.now() player.last_submission_date = timezone.now()
player.score += ctf_info.points player.score += (ctf_info.points + bonus)
player.save() player.save()
if player.team: if player.team:
if ctf_info.points > 0: if ctf_info.points > 0:
player.team.last_submission_date = timezone.now() player.team.last_submission_date = timezone.now()
player.team.score += ctf_info.points player.team.score += (ctf_info.points + bonus)
player.team.save() player.team.save()
response['Location'] += '?Congrat=1' if ev.dynamic:
actualize_points(ctf_info)
response['Location'] += '?Congrat=1&Bonus=' + str(bonus)
return response return response
else: else:
response['Location'] += '?WrongFlag=1' response['Location'] += '?WrongFlag=1'
@ -191,8 +262,6 @@ def submit_pwd(request, event_slug):
else: else:
new = EventPlayer(user=request.user, event=ev) new = EventPlayer(user=request.user, event=ev)
new.save() new.save()
if event_info.team_size > 1:
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
@ -214,8 +283,6 @@ def register_to_event(request, event_slug):
else: else:
new = EventPlayer(user=request.user, event=ev, score=0) new = EventPlayer(user=request.user, event=ev, score=0)
new.save() new.save()
if ev.team_size > 1:
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
@login_required @login_required
@ -243,7 +310,7 @@ def profile(request, user_name, event_slug):
percent = (solved_count / max_count) * 100 percent = (solved_count / max_count) * 100
catsDatas.append([cat.name, solved_count, max_count, '{:.0f}'.format(percent)]) catsDatas.append([cat.name, solved_count, max_count, '{:.0f}'.format(percent)])
for flag in solved: for flag in solved:
somme += flag.ctf.points somme += (flag.ctf.points + flag.bonus)
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme]) pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
solves = CTF_flags.objects.filter(user=user_obj, ctf__event=event_info).order_by('-flag_date') solves = CTF_flags.objects.filter(user=user_obj, ctf__event=event_info).order_by('-flag_date')
@ -251,10 +318,10 @@ def profile(request, user_name, event_slug):
somme = 0 somme = 0
solved.append([event_info.start_date.timestamp() * 1000, 0]) solved.append([event_info.start_date.timestamp() * 1000, 0])
for s in solves.reverse(): for s in solves.reverse():
somme += s.ctf.points somme += (s.ctf.points + s.bonus)
solved.append([s.flag_date.timestamp() * 1000,somme]) solved.append([s.flag_date.timestamp() * 1000,somme])
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas, return render(request,'events/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'catsDatas': catsDatas, 'pointDatas': pointDatas,
'rank': rank, 'score' : somme, 'cats':cats}) 'rank': rank, 'score' : player.score, 'cats':cats, 'event': event_info})

View File

@ -11,22 +11,25 @@ from random import randint
@login_required @login_required
def create_team(request, event_slug): def create_team(request, event_slug):
response = redirect('events:create_team', event_slug=event_slug)
ev = get_object_or_404(Event, slug=event_slug) ev = get_object_or_404(Event, slug=event_slug)
if request.method == 'POST': if request.method == 'POST':
teamname = request.POST.get('teamname')
if request.user.is_authenticated and ev.team_size > 1: if request.user.is_authenticated and ev.team_size > 1:
if Team.objects.filter(name=request.POST.get('teamname'), event=ev).exists(): if any(c in set('./') for c in teamname):
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : False, 'invalid' : True})
if Team.objects.filter(name=teamname, event=ev).exists():
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : True}) return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : True})
new = Team(name=request.POST.get('teamname'), password=request.POST.get('password'), event=ev) new = Team(name=teamname, password=request.POST.get('password'), event=ev)
new.save() new.save()
player = EventPlayer.objects.get(user=request.user, event=ev) player = EventPlayer.objects.get(user=request.user, event=ev)
player.team = new player.team = new
player.save() player.save()
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
else:
return render(request, 'events/create_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'exist' : False})
@login_required @login_required
def join_team(request, event_slug): def join_team(request, event_slug):
response = redirect('events:join_team', event_slug=event_slug)
ev = get_object_or_404(Event, slug=event_slug) ev = get_object_or_404(Event, slug=event_slug)
if request.method == 'POST': if request.method == 'POST':
if request.user.is_authenticated and ev.team_size > 1: if request.user.is_authenticated and ev.team_size > 1:
@ -46,9 +49,9 @@ def join_team(request, event_slug):
player = EventPlayer.objects.get(user=request.user, event=ev) player = EventPlayer.objects.get(user=request.user, event=ev)
player.team = team player.team = team
player.save() player.save()
return redirect('events:event_info', event_slug=event_slug)
else: else:
return render(request, 'events/join_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False}) return render(request, 'events/join_team.html', {'event' : ev, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
return redirect('events:event_info', event_slug=event_slug)
@login_required @login_required
def team_info(request, name, event_slug): def team_info(request, name, event_slug):
@ -102,6 +105,8 @@ def team_info(request, name, event_slug):
def manage_team(request, event_slug): def manage_team(request, event_slug):
event_info = get_object_or_404(Event, slug=event_slug) event_info = get_object_or_404(Event, slug=event_slug)
player = EventPlayer.objects.get(user=request.user, event=event_info) player = EventPlayer.objects.get(user=request.user, event=event_info)
if not player.team:
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
members = EventPlayer.objects.filter(team=player.team, event=event_info) members = EventPlayer.objects.filter(team=player.team, event=event_info)
if request.method == 'POST': if request.method == 'POST':
@ -113,8 +118,9 @@ def manage_team(request, event_slug):
pname = p_form.cleaned_data['name'] pname = p_form.cleaned_data['name']
if pname == tname: if pname == tname:
pass pass
else: elif any(c in set('./') for c in pname):
if Team.objects.filter(name=pname, event=event_info).exists(): error = _("Invalid characters in name")
elif Team.objects.filter(name=pname, event=event_info).exists():
error = _("Name already taken.") error = _("Name already taken.")
ppassword = p_form.cleaned_data['password'] ppassword = p_form.cleaned_data['password']
if error is None: if error is None:
@ -135,19 +141,21 @@ def leave_team(request, event_slug):
player = EventPlayer.objects.get(user=request.user, event=event_info) player = EventPlayer.objects.get(user=request.user, event=event_info)
team = Team.objects.get(event=event_info, name=player.team.name) team = Team.objects.get(event=event_info, name=player.team.name)
player.team = None
player.save()
members = EventPlayer.objects.filter(team=team, event=event_info)
if members.count() == 0:
team.delete()
else:
team.score -= player.score team.score -= player.score
team.save() team.save()
player.team = None
solved = CTF_flags.objects.filter(user=player.user, ctf__event=event_info) solved = CTF_flags.objects.filter(user=player.user, ctf__event=event_info)
player.score = 0 player.score = 0
solved.delete() solved.delete()
player.save() player.save()
members = EventPlayer.objects.filter(team=team, event=event_info) return redirect('events:event_info', event_slug=event_slug)
if members.count() == 0:
team.delete()
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
@login_required @login_required
def find_team(request, event_slug): def find_team(request, event_slug):
@ -174,3 +182,27 @@ def find_team(request, event_slug):
player.save() player.save()
return redirect('events:event_info', event_slug=event_slug) return redirect('events:event_info', event_slug=event_slug)
@login_required
def open_team(request, event_slug):
event_info = get_object_or_404(Event, slug=event_slug)
player = EventPlayer.objects.get(user=request.user, event=event_info)
if not player.team:
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
player.team.auto = True
player.team.save()
return redirect('events:manage_team', event_slug=event_slug)
@login_required
def close_team(request, event_slug):
event_info = get_object_or_404(Event, slug=event_slug)
player = EventPlayer.objects.get(user=request.user, event=event_info)
if not player.team:
return render(request, 'events/create_team.html', {'event' : event_info, 'logged': True, 'wrongpwd': False, 'registered' : True, 'notexist' : False})
player.team.auto = False
player.team.save()
return redirect('events:manage_team', event_slug=event_slug)

12
src/home/sitemaps.py Normal file
View File

@ -0,0 +1,12 @@
from django.contrib import sitemaps
from django.urls import reverse
class StaticViewSitemap(sitemaps.Sitemap):
priority = 0.5
changefreq = 'monthly'
i18n = True
def items(self):
return ['home', 'cgu']
def location(self, item):
return reverse(item)

View File

@ -4,7 +4,7 @@
{% get_current_language as lang %} {% get_current_language as lang %}
<div class="row"> <div class="row">
<div class="col-12 news-card"> <div class="col-12 news-card">
<h2>Conditions générales d'utilisation du site 42CTF</h2> <h1>Conditions générales d'utilisation du site 42CTF</h1>
<h5>Article 1 : Objet</h5> <h5>Article 1 : Objet</h5>

View File

@ -3,6 +3,7 @@
{% load i18n %} {% load i18n %}
{% get_current_language as lang %} {% get_current_language as lang %}
{% load is_member %} {% load is_member %}
{% load get_news %}
<div class="row"> <div class="row">
<div class="col-lg-3 col-sm-12 right-sidebar middle-sm"> <div class="col-lg-3 col-sm-12 right-sidebar middle-sm">
@ -34,17 +35,8 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{{ n.name|safe }}</h5> <h5 class="card-title">{{ n.name|safe }}</h5>
<p class="card-text"> <p class="card-text">
{% if lang == "fr" and n.content %} {% get_news_by_lang n lang as content %}
{{ n.content|safe }} {{ content | safe }}
{% elif lang == "en" and n.content_en %}
{{ n.content_en|safe }}
{% elif lang == "de" and n.content_de %}
{{ n.content_de|safe }}
{% elif lang == "ru" and n.content_ru %}
{{ n.content_ru|safe }}
{% else %}
{% trans "No translation available. Please try another language (English or French)." %}
{% endif %}
</p> </p>
</div> </div>
<div class="card-footer text-muted"> <div class="card-footer text-muted">
@ -83,13 +75,13 @@
{% endfor %} {% endfor %}
</ul> </ul>
<div class="row flex-nowrap"> <div class="row flex-nowrap">
<div class="col-lg-6 col-sm-3"> <div class="col-lg-6 col-md-6">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item active">{% trans "Flags" %}</li> <li class="list-group-item active">{% trans "Flags" %}</li>
<li class="list-group-item"><span>{{ flags }}</span></li> <li class="list-group-item"><span>{{ flags }}</span></li>
</ul> </ul>
</div> </div>
<div class="col-lg-6 col-sm-3"> <div class="col-lg-6 col-md-6">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item active">{% trans "Users" %}</li> <li class="list-group-item active">{% trans "Users" %}</li>
<li class="list-group-item"><span>{{ nb_users }}</span></li> <li class="list-group-item"><span>{{ nb_users }}</span></li>

View File

@ -0,0 +1,4 @@
Wie Sie es vielleicht schon wissen, braucht man um CTF Herausforderungen zu lösen viele Werkzeuge und es ist manchmal schwierig zu wissen welche benötigt werden.</br>
Wir haben eine VM erstellt, mit vorinstallierten Werkzeugen, damit Sie sich auf das wesentliche konzentrieren können: Flaggen!</br>
Alles was Sie tun müssen ist diese <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> herunterzuladen und auf <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b> zu importieren.<br>
Also, worauf warten Sie?

View File

@ -0,0 +1,7 @@
Haben Sie eine Änderung am Punktestand bemerkt?<br><br>
Keine Panik, alle Ihre Flaggen sind in Sicherheit. Wir haben bloß zu dynamischen Belohnungen gewächselt. Das heißt, dass die Punktzahl der Herausforderungen nicht mehr festgelegt ist: sie sinken nun jedes Mal, dass die Herausforderung gelöst wird.
Belohnungspunkte beginnen bei 200 und können nicht unter 5 fallen.<br><br>
Wir erhoffen, dass dadurch die echte Schwierigkeit der Herausforderungen besser gespiegelt werden kann. Zeitgebundene Ereignisse sind von dieser Änderung nicht beinträchtigt.

View File

@ -0,0 +1,9 @@
Suchen Sie Ihren Seelenverwandten, einen neuen Freund oder bloß einen dezenten CTF-Partner?<br><br>
Wir bei 42CTF haben was Sie brauchen: den <a href="/events/speed_dating_2022">Speed Dating CTF</a>!<br><br>
Kommen Sie alleine oder gut begleitet zu diesem sehr kurzen Wettbewerb, der nur 4 Studen dauern wird.<br>
Für dieses Team-CTF können Sie nur auf einen anderen Spieler zählen.<br>
Sie können entweder Ihren Partner aussuchen oder das Schicksal für sie entscheiden lassen.<br><br>
Viel Glück!

View File

@ -0,0 +1,8 @@
Schon immer lust gehabt etwas über SQL-Einbrüche zu lernen?<br>
<br>
Wir bieten Ihnen drei brandneue Herausvorderungen erstellt von <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 Punkte)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 Punkte)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 Punkte)<br>
<br>
Vergessen Sie nicht, dass Sie uns jederzeit auf <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> erreichen können, um uns neue Herausforderungen vorzuschalgen!

View File

@ -0,0 +1,9 @@
Neues zeitgebundenes Ereignis: <b>Welcome CTF 2021</b>!<br><br>
Datum: vom 10.12.2021 20 Uhr biz zum 12.12.2021 20 Uhr (Pariser Zeit).<br>
Es ist ein CTF um die Studenten die neulich 42 beigetreten haben zu begrüßen.<br>
Es wird nur für die Personen die ihren Kursus <b>nach</b> dem 01.09.2021 begonnen haben zugänglich sein.<br><br>
Ansonsten können Sie trotzdem die auf der Webseite bereits verfügbaren Herausforderungen lösen und versuchen das Top 10 zu erreichen!<br><br>
Registrieren Sie sich <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>hier</a>.

View File

@ -0,0 +1,4 @@
As you may already be aware, solving CTF challenges require a lot of tools and it may be difficult to find out which ones you need to install. </br>
We've made a VM with everything you need for 42CTF challenges so that you can focus on what is really important: flag ! </br>
All you need to do is download this <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> and import it in <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>.<br></b>
So, what are you waiting for ?

View File

@ -0,0 +1,7 @@
You noticed a little change in the 42CTF scoreboard ?<br><br>
Don't panic, all your flags are safe and sound. We just switched to dynamic scoring. It means that challenges points are not fixed anymore: they will now decrease with each solve.<br>
Challenges points start at 200, and cannot go below than 5.<br><br>
We hope that this will allow better reflecting of the actual difficulty of the challenges. Limited-time events are not affected by this change.

View File

@ -0,0 +1,3 @@
We're pleased to announce that 42CTF source code is now available on a self-hosted gitea <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30" alt="Gitea logo"></a><br><br>
If you want to contribute to the platform (development or translation), you can send us a message on <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Discord logo"></a> or simply fill this <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">form</a> and we'll contact you !

View File

@ -0,0 +1,9 @@
Looking for your soul mate, a new friend, or just a decent CTF companion ?<br><br>
We at 42CTF have what you need: the <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
Come alone or in good company for this very short competition, which will last only 4 hours.<br>
You will be able to count on only one other player for this team CTF.<br>
You can either choose your partner or you can let the fate decide for you.<br><br>
Good luck !

View File

@ -0,0 +1,8 @@
Always wanted to learn about SQL injections ? <br>
<br>
We offer you three brand new challenges created by <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
<br>
Don't forget that you can always reach out on <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Discord logo"></a> to propose new challenges !

View File

@ -0,0 +1,9 @@
New limited-time event : <b>Welcome CTF 2021</b> !<br><br>
Dates: from 10/12/2021 8pm to 12/12/2021 8pm (Paris time).<br>
This is a CTF to welcome the new students who join us at 42.<br>
It will be accessible only for the persons who started their cursus <b>after</b> the 01/09/2021.<br><br>
For the others, you can still solve the challenges already available on the website and try to reach the top 10 ! <br><br>
Registration <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>here</a>

View File

@ -0,0 +1,4 @@
Como ya sabrás, resolver retos CTF requiere un montón de herramientas y puede ser dificil encontrar cuales tienes que instalar. </br>
Hemos hecho una máquina virtual con todo lo que necesitas para resolver para resolver retos de 42CTF, para que te puedas concentrar en lo importante: Flags! </br>
Todo lo que necesitas es descargar esto <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> e importarlo en <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>.<br></b>
¿A qué estás esperando?

View File

@ -0,0 +1,7 @@
¿ Has notado un pequeño cambio en la Tabla de Puntos de 42CTF ?<br><br>
No entres en pánico, todas tus flags estan a salvo. Solo hemos cambiado a puntuación dinámica. Esto significa que los puntos de retos no son fijos: irán disminuyendo cada vez que son resueltos.<br>
Los puntos de retos empiezan en 200, y no pueden valer menos de 5 puntos. <br><br>
Esperamos que esto ayude a reflejar la dificultad del reto. Eventos de tiempo limitado nos e ven afectados por este cambio.

View File

@ -0,0 +1,9 @@
¿ Estás buscando tu alma gemela ? ¿ Un nuevo amigo ? ¿ O simplemente un buen compañero de CTF ?<br><br>
Aquí en 42CTF tenemos lo que necesitas: el <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
Ven solo o en compañía a esta competición cortita, que solo durará 4 horas.<br>
Podrás contar con un solo compañero para este CTF por equipos.<br>
Puedes o elegir un compañero o dejar a la fortuna que elija por ti.<br><br>
¡ Buena suerte !

View File

@ -0,0 +1,8 @@
¿ Siempre has querido aprender sobre SQL injection ? <br>
<br>
Te ofrecemos tres nuevos retos creados por <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Cuestión de lógica simple 1</a></b> (10 puntos)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Cuestión de lógica simple 2</a></b> (30 puntos)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Cuestión de lógica simple 3</a></b> (40 puntos)<br>
<br>
No te olvides que siempre puedes contactarnos en <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> para proponer nuevos retos !

View File

@ -0,0 +1,9 @@
Nuevo evento de tiempo limitado: <b>Bienvenida a CTF 2021</b> !<br><br>
Fechas: desde 10/12/2021 8pm a 12/12/2021 8pm (Hora parís).<br>
Este CTF es una bienvenida para los nuevos estudiantes que se unan a 42.<br>
Solo estará disponible para las personas que empezaron su cursus <b>después</b> de 01/09/2021.<br><br>
Para otros, puedes resolver otros retos ya disponibles e intentar llegar al top 10! <br><br>
Registro: <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>aquí</a>

View File

@ -0,0 +1,7 @@
Comme vous le savez surement déjà, résoudre des challenges nécessite beaucoup d'outils et il peut être difficile de savoir lesquels sont vraiment nécessaires. <br>
Nous avons créé une VM avec tout ce dont vous avez besoin pour 42CTF afin que vous vous concentriez sur ce qui compte vraiment : flag ! <br>
Tout ce dont vous avez besoin est de télécharger cet <b><a href="/media/xubuntu-42ctf.ova">OVA</a></b> et de l'importer dans <b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b>.<br>
Alors, qu'est ce que vous attendez ?

View File

@ -0,0 +1,7 @@
Vous avez remarqué un petit changement sur le scoreboard de 42CTF ?<br><br>
Pas de panique, tous vos flags sont sains et saufs. Nous avons juste basculé sur du scoring dynamique. Cela signifie que les points des challenges ne sont plus fixes : ils diminueront désormais à chaque nouvelle résolution.<br>
Un challenge démarre à 200 points, et ne peut pas descendre en dessous de 5 points.<br><br>
Nous espérons que cela permettra de mieux refléter la véritable difficulté des challenges. Les événements à durée limitée ne sont pas concernés par ce changement.

View File

@ -0,0 +1,3 @@
Nous sommes heureux de vous annoncer que le code source de 42CTF est désormais disponible sur un <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30" alt="Logo Gitea"></a> auto-hébergé.<br><br>
Si vous voulez contribuer a la plateforme (développement ou traduction), vous pouvez nous envoyer un message sur <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Logo Discord"></a> ou simplement remplir ce <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">formulaire</a> et nous vous contacterons !

View File

@ -0,0 +1,9 @@
À la recherche de l'âme soeur, d'un nouvel ami, ou juste d'un compagnon de CTF ?<br><br>
Nous avons ce dont vous avez besoin : le <a href="/events/speed_dating_2022">Speed Dating CTF</a> !<br><br>
Venez seul ou bien accompagné pour cette très courte compétition, qui ne durera que 4 heures.<br>
Vous ne pourrez compter que sur une seule autre personne pour ce CTF en équipe.<br>
Vous pouvez soit choisir votre partenaire, soit laisser laisser le destin décider pour vous.<br><br>
Bonne chance !

View File

@ -0,0 +1,8 @@
Vous avez toujours voulu en apprendre plus sur les injections SQL ? <br>
<br>
On vous propose trois nouveaux challenges créés par <b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>:<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
<br>
N'oubliez pas que vous pouvez toujours nous contacter sur <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30" alt="Logo Discord"></a> pour proposer des nouveaux challenges !

View File

@ -0,0 +1,9 @@
Nouvel évènement en temps limité : <b>Welcome CTF 2021</b> !<br><br>
Dates : du 10/12/2021 20h au 12/12/2021 20h. <br>
Il s'agit d'un CTF pour souhaiter la bienvenue aux nouveaux étudiants qui nous rejoignent à 42.<br>
Il ne sera donc accessible qu'aux personnes ayant effectué leur rentrée <b>après</b> le 01/09/2021.<br><br>
Pour les autres, vous pouvez toujours résoudre les challenges déjà disponibles sur le site et tenter de vous hisser dans le top 10 ! <br><br>
Inscriptions <a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>ici</a>

View File

@ -0,0 +1,4 @@
すでにお気づきかもしれませんが、CTFの課題を解くには多くのツールが必要で、どれをインストールすれば良いのかがわかりにくいかもしれません。</br>
私たちは、あなたが本当に重要なことに集中できるように、42CTFの課題に必要な全てのツールを備えたVMを作成しました。重要なのはフラグです</br>
この<b><a href="/media/xubuntu-42ctf.ova">OVA</a></b>をダウンロードし、<b><a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a></b>にインポートするだけです。<br>
さて、何をためらっているのですか?

View File

@ -0,0 +1,7 @@
42CTFのスコアボードにちょっとした変化があったことにお気づきですか<br><br>
慌てないでください、 あなたのフラグはすべて無事です。 動的スコアリングに切り替えただけです。 それはチャレンジポイントがもう固定ではないことを意味します。解決するたびに減少します。<br>
チャレンジポイントは200から始まり、5より低くなることはありません。<br><br>
これにより、課題の実際の難易度をより良く反映できるようになると期待しています。期間限定イベントは、この変更の影響は受けません。

View File

@ -0,0 +1,3 @@
42CTFのソースコードがセルフホストのgitea <a class="footer_imgs" href="https://gitea.42ctf.org" target="_blank"><img src="/static/img/gitea_logo.png" width="30"></a> で公開されたことをお知らせします。<br><br>
プラットフォームへの貢献(開発や翻訳)をしていただける方は、 <a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a> にメッセージをお送りいただくか、こちらの <a href="https://forms.42l.fr/apps/forms/bpmyGR37AR4yHGnC">フォーム</a> にご記入いただければご連絡いたします!

View File

@ -0,0 +1,9 @@
ソウルメイト、新しい友達、またはちょうど良いCTFの仲間を探していますか<br><br>
私たち42CTFは、あなたが求めているものを持っています。それは<a href="/events/speed_dating_2022">Speed Dating CTF</a>です!<br><br>
4時間という短い時間ですが、お一人でも、お仲間とご一緒でも、ぜひご参加ください。<br>
このチーム戦CTFでは、自分以外の一人のプレーヤーのみ頼ることができます。<br>
相手を選ぶもよし、運命に身を任せるもよし。<br><br>
幸運を祈ります!

View File

@ -0,0 +1,8 @@
SQLインジェクションについて学びたいと思ったことはありませんか<br>
<br>
<b><a class=profile_link href=https://www.42ctf.org/accounts/profile/aldubar>aldubar</a></b>が作成した全く新しい3つの課題を提供します。<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_1'>Simple Question of Logic 1</a></b> (10 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_2'>Simple Question of Logic 2</a></b> (30 points)<br>
- <b><a href='https://www.42ctf.org/ctfs/web/simple_question_3'>Simple Question of Logic 3</a></b> (40 points)<br>
<br>
新しい課題を提案するために、<a class="footer_imgs" href="https://discord.gg/DwZqPpA" target="_blank"><img src="/static/img/discord.png" width="30"></a>へいつでも連絡できることを忘れないでください!

View File

@ -0,0 +1,9 @@
期間限定の新イベント:<b>Welcome CTF 2021</b><br><br>
日程2021年12月10日20時2021年12月12日20時パリ時間<br>
42に入学する新入生を歓迎するためのCTFです。<br>
2021年9月1日<b>以降</b>にカーサスを開始した方のみアクセス可能です。<br><br>
それ以外の方は、ウェブサイト上で公開されている課題を解いて、トップ10入りを目指してください<br><br>
登録は<a href=https://forms.42l.fr/apps/forms/SooTbnT4PCs9na7C>こちら</a>

View File

View File

@ -0,0 +1,13 @@
from django import template
from django.core.files.storage import default_storage
register = template.Library()
@register.simple_tag
def get_news_by_lang(news, lang):
filepath = "home/templates/news/"+ lang + "/" + news.slug + ".html"
try:
with open(filepath) as fp:
return fp.read()
except:
return news.content_en

View File

@ -1,7 +1,14 @@
from django.contrib.sitemaps.views import sitemap
from django.urls import path from django.urls import path
from .sitemaps import StaticViewSitemap
from . import views from . import views
sitemaps = {
'static': StaticViewSitemap(),
}
urlpatterns = [ urlpatterns = [
path('', views.home, name='home'), path('', views.home, name='home'),
path('CGU', views.cgu, name='cgu'), path('CGU', views.cgu, name='cgu'),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
] ]

View File

@ -8,26 +8,17 @@ from django.urls import translate_url
from django.utils.translation import ( from django.utils.translation import (
LANGUAGE_SESSION_KEY, check_for_language, get_language, LANGUAGE_SESSION_KEY, check_for_language, get_language,
) )
import datetime from django.core.files.storage import default_storage
# import datetime
from django.utils import timezone
from collections import defaultdict from collections import defaultdict
import operator import operator
def get_content_by_lang(news):
lang = get_language()
ret = None
if lang == "fr":
ret = news.content
elif lang == "en":
ret = news.content_en
elif lang == "de":
ret = news.content_de
elif lang == "ru":
ret = news.content_ru
return ret
def get_weekly_top(): def get_weekly_top():
week_ago = datetime.datetime.now() - datetime.timedelta(days=7) week_ago = timezone.now() - timezone.timedelta(days=7)
weekly_flags = CTF_flags.objects.filter(flag_date__gt=week_ago) weekly_flags = CTF_flags.objects.filter(flag_date__gt=week_ago, ctf__disabled=False, ctf__event=None)
scores = defaultdict(int) scores = defaultdict(int)
for sol in weekly_flags: for sol in weekly_flags:
@ -44,15 +35,14 @@ def home(request):
lang_code = request.session[LANGUAGE_SESSION_KEY] lang_code = request.session[LANGUAGE_SESSION_KEY]
url_translated = translate_url(request.path, lang_code) url_translated = translate_url(request.path, lang_code)
if request.path != url_translated: if request.path != url_translated:
print("%s\n%s" % (request.path, url_translated))
response = HttpResponseRedirect(url_translated) response = HttpResponseRedirect(url_translated)
return response return response
news = new.objects.order_by('-pub_date')[:5] news = new.objects.order_by('-pub_date')[:5]
latest_ctfs = CTF.objects.filter(event=None).order_by('-pub_date')[:5] latest_ctfs = CTF.objects.filter(event=None, disabled=False).order_by('-pub_date')[:5]
top10 = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')[:10] top10 = UserProfileInfo.objects.select_related().order_by('-score', 'last_submission_date', 'user__username')[:10]
nb_flags = CTF_flags.objects.count() nb_flags = CTF_flags.objects.count()
nb_users = UserProfileInfo.objects.count() nb_users = UserProfileInfo.objects.count()
latest_flags = CTF_flags.objects.order_by('-flag_date')[:5] latest_flags = CTF_flags.objects.filter(ctf__event = None, ctf__disabled=False).order_by('-flag_date')[:5]
top_weekly = get_weekly_top() top_weekly = get_weekly_top()
return render(request, 'home/home.html', {'news' : news, 'ctfs' : latest_ctfs, 'top' : top10, 'flags' : nb_flags, return render(request, 'home/home.html', {'news' : news, 'ctfs' : latest_ctfs, 'top' : top10, 'flags' : nb_flags,

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More