codeforlife-portal 5.33.5__py2.py3-none-any.whl → 8.9.9__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfl_common/common/__init__.py +1 -0
- cfl_common/common/app_settings.py +66 -0
- cfl_common/common/apps.py +6 -0
- cfl_common/common/context_processors.py +9 -0
- cfl_common/common/csp_config.py +85 -0
- cfl_common/common/helpers/__init__.py +0 -0
- cfl_common/common/helpers/data_migration_loader.py +42 -0
- cfl_common/common/helpers/emails.py +393 -0
- cfl_common/common/helpers/generators.py +52 -0
- cfl_common/common/helpers/organisation.py +10 -0
- cfl_common/common/mail.py +201 -0
- cfl_common/common/migrations/0001_initial.py +240 -0
- cfl_common/common/migrations/0002_emailverification.py +55 -0
- cfl_common/common/migrations/0003_aimmocharacter.py +31 -0
- cfl_common/common/migrations/0004_add_aimmocharacters.py +17 -0
- cfl_common/common/migrations/0005_add_worksheets.py +8 -0
- cfl_common/common/migrations/0006_update_aimmo_character_image_path.py +17 -0
- cfl_common/common/migrations/0007_add_pdf_names_to_first_two_worksheets.py +8 -0
- cfl_common/common/migrations/0008_unlock_worksheet_3.py +11 -0
- cfl_common/common/migrations/0009_add_blocked_time_to_teacher_and_student.py +24 -0
- cfl_common/common/migrations/0010_remove_teacher_title.py +18 -0
- cfl_common/common/migrations/0011_student_login_id.py +18 -0
- cfl_common/common/migrations/0012_usersession.py +39 -0
- cfl_common/common/migrations/0013_class_school.py +42 -0
- cfl_common/common/migrations/0014_login_type.py +29 -0
- cfl_common/common/migrations/0015_dailyactivity.py +31 -0
- cfl_common/common/migrations/0016_joinreleasestudent.py +42 -0
- cfl_common/common/migrations/0017_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0018_update_aimmo_character_image_path.py +15 -0
- cfl_common/common/migrations/0019_aimmocharacter_alt.py +16 -0
- cfl_common/common/migrations/0020_class_is_active_and_null_access_code.py +23 -0
- cfl_common/common/migrations/0021_school_is_active.py +28 -0
- cfl_common/common/migrations/0022_school_cleanup.py +29 -0
- cfl_common/common/migrations/0023_userprofile_aimmo_badges.py +22 -0
- cfl_common/common/migrations/0024_teacher_invited_by.py +25 -0
- cfl_common/common/migrations/0025_schoolteacherinvitation.py +47 -0
- cfl_common/common/migrations/0026_teacher_remove_join_request.py +22 -0
- cfl_common/common/migrations/0027_class_created_by.py +25 -0
- cfl_common/common/migrations/0028_coding_club_downloads.py +23 -0
- cfl_common/common/migrations/0029_dynamicelement.py +22 -0
- cfl_common/common/migrations/0030_add_maintenance_banner.py +25 -0
- cfl_common/common/migrations/0031_improve_admin_panel.py +56 -0
- cfl_common/common/migrations/0032_dailyactivity_level_control_submits.py +18 -0
- cfl_common/common/migrations/0033_password_reset_tracking_fields.py +23 -0
- cfl_common/common/migrations/0034_dailyactivity_daily_school_student_lockout_reset.py +18 -0
- cfl_common/common/migrations/0035_rename_lockout_fields.py +27 -0
- cfl_common/common/migrations/0036_rename_awaiting_email_verification_userprofile_is_verified.py +17 -0
- cfl_common/common/migrations/0037_migrate_email_verification.py +21 -0
- cfl_common/common/migrations/0038_delete_emailverification.py +16 -0
- cfl_common/common/migrations/0039_copy_email_to_username.py +18 -0
- cfl_common/common/migrations/0040_school_county.py +18 -0
- cfl_common/common/migrations/0041_populate_gb_counties.py +27 -0
- cfl_common/common/migrations/0042_totalactivity.py +25 -0
- cfl_common/common/migrations/0043_add_total_activity.py +30 -0
- cfl_common/common/migrations/0044_update_activity_models.py +33 -0
- cfl_common/common/migrations/0045_otp.py +23 -0
- cfl_common/common/migrations/0046_alter_school_country.py +19 -0
- cfl_common/common/migrations/0047_delete_school_postcode.py +16 -0
- cfl_common/common/migrations/0048_unique_school_names.py +42 -0
- cfl_common/common/migrations/0049_anonymise_orphan_users.py +29 -0
- cfl_common/common/migrations/0050_anonymise_orphan_schools.py +30 -0
- cfl_common/common/migrations/0051_verify_returning_users.py +26 -0
- cfl_common/common/migrations/0052_add_cse_fields.py +68 -0
- cfl_common/common/migrations/0053_clean_class_data.py +24 -0
- cfl_common/common/migrations/0054_delete_aimmo_models.py +20 -0
- cfl_common/common/migrations/0055_alter_schoolteacherinvitation_token.py +18 -0
- cfl_common/common/migrations/0056_set_non_school_teachers_as_non_admins.py +25 -0
- cfl_common/common/migrations/0057_teacher_teacher__is_admin.py +19 -0
- cfl_common/common/migrations/0058_userprofile_google_refresh_token_and_more.py +24 -0
- cfl_common/common/migrations/__init__.py +0 -0
- cfl_common/common/models.py +557 -0
- cfl_common/common/permissions.py +84 -0
- cfl_common/common/tests/__init__.py +0 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_schools.py +30 -0
- cfl_common/common/tests/test_migration_anonymise_orphan_users.py +30 -0
- cfl_common/common/tests/test_migration_blocked_time.py +15 -0
- cfl_common/common/tests/test_migration_remove_teacher_title.py +13 -0
- cfl_common/common/tests/test_migration_unique_school_names.py +33 -0
- cfl_common/common/tests/test_migration_verify_returning_users.py +59 -0
- cfl_common/common/tests/test_models.py +87 -0
- cfl_common/common/tests/utils/__init__.py +0 -0
- cfl_common/common/tests/utils/classes.py +38 -0
- cfl_common/common/tests/utils/email.py +67 -0
- cfl_common/common/tests/utils/organisation.py +41 -0
- cfl_common/common/tests/utils/student.py +123 -0
- cfl_common/common/tests/utils/teacher.py +73 -0
- cfl_common/common/tests/utils/user.py +27 -0
- cfl_common/common/utils.py +56 -0
- cfl_common/setup.py +61 -0
- codeforlife_portal-8.9.9.dist-info/METADATA +226 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/RECORD +339 -241
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/WHEEL +1 -1
- codeforlife_portal-8.9.9.dist-info/licenses/LICENSE.md +3 -0
- {codeforlife_portal-5.33.5.dist-info → codeforlife_portal-8.9.9.dist-info}/top_level.txt +1 -0
- deploy/middleware/maintenance.py +25 -0
- deploy/middleware/screentime_warning.py +29 -0
- deploy/middleware/security.py +5 -6
- deploy/middleware/session_timeout.py +4 -2
- deploy/middleware/tmp_basic_auth.py +41 -0
- example_project/portal_test_settings.py +239 -0
- example_project/settings.py +156 -17
- example_project/urls.py +5 -6
- portal/__init__.py +1 -1
- portal/admin.py +142 -29
- portal/app_settings.py +8 -7
- portal/forms/dotmailer.py +6 -4
- portal/forms/invite_teacher.py +19 -10
- portal/forms/organisation.py +137 -68
- portal/forms/play.py +53 -98
- portal/forms/registration.py +70 -164
- portal/forms/teach.py +147 -121
- portal/handlers.py +1 -2
- portal/helpers/decorators.py +30 -10
- portal/helpers/password.py +86 -47
- portal/helpers/ratelimit.py +32 -15
- portal/helpers/regexes.py +5 -0
- portal/helpers/request_handlers.py +10 -0
- portal/migrations/0044_auto_20150430_0959.py +6 -2
- portal/mixins/__init__.py +1 -0
- portal/mixins/cron_mixin.py +12 -0
- portal/permissions/__init__.py +1 -0
- portal/permissions/is_cron_request_from_google.py +14 -0
- portal/static/portal/img/10_years_anniversary.png +0 -0
- portal/static/portal/img/RR_logo_grass_background.png +0 -0
- portal/static/portal/img/coding_club_hero.jpg +0 -0
- portal/static/portal/img/coding_club_python_pack.png +0 -0
- portal/static/portal/img/facebook.png +0 -0
- portal/static/portal/img/gitbook.png +0 -0
- portal/static/portal/img/howe_dell_1.png +0 -0
- portal/static/portal/img/howe_dell_2.png +0 -0
- portal/static/portal/img/howe_dell_3.png +0 -0
- portal/static/portal/img/logo_cfl.png +0 -0
- portal/static/portal/img/logo_cfl_powered.svg +35 -0
- portal/static/portal/img/logo_cfl_reminder_cards.jpg +0 -0
- portal/static/portal/img/logo_ocado_group.png +0 -0
- portal/static/portal/img/logo_python_den.svg +21 -0
- portal/static/portal/img/long_europe_map.png +0 -0
- portal/static/portal/img/python_den.png +0 -0
- portal/static/portal/img/python_den_banner.svg +26 -0
- portal/static/portal/img/rapid_router_landing_hero.png +0 -0
- portal/static/portal/img/rr_advanced.png +0 -0
- portal/static/portal/img/ten_year_map_pin.svg +1 -0
- portal/static/portal/img/thumbnail_educate_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_rapid_router.png +0 -0
- portal/static/portal/img/thumbnail_python_den.png +0 -0
- portal/static/portal/img/twitter.png +0 -0
- portal/static/portal/js/carouselCards.js +25 -0
- portal/static/portal/js/common.js +96 -1
- portal/static/portal/js/independentLogin.js +16 -0
- portal/static/portal/js/independentRegistration.js +86 -0
- portal/static/portal/js/levelControl.js +77 -0
- portal/static/portal/js/lib/jquery.min.js +2 -0
- portal/static/portal/js/organisation_manage.js +142 -14
- portal/static/portal/js/passwordStrength.js +154 -64
- portal/static/portal/js/resetPassword.js +23 -0
- portal/static/portal/js/riveted.min.js +238 -239
- portal/static/portal/js/school.js +13 -0
- portal/static/portal/js/studentLogin.js +16 -0
- portal/static/portal/js/teacherEditStudent.js +23 -0
- portal/static/portal/js/teacherLogin.js +16 -0
- portal/static/portal/js/tenYearMap.js +14 -0
- portal/static/portal/sass/colorbox.scss +0 -1
- portal/static/portal/sass/modules/_colours.scss +1 -0
- portal/static/portal/sass/modules/_levels.scss +1 -1
- portal/static/portal/sass/modules/_mixins.scss +21 -0
- portal/static/portal/sass/partials/_banners.scss +4 -177
- portal/static/portal/sass/partials/_buttons.scss +12 -15
- portal/static/portal/sass/partials/_carousel.scss +129 -0
- portal/static/portal/sass/partials/_footer.scss +21 -22
- portal/static/portal/sass/partials/_forms.scss +60 -5
- portal/static/portal/sass/partials/_grids.scss +34 -61
- portal/static/portal/sass/partials/_header.scss +28 -20
- portal/static/portal/sass/partials/_images.scss +292 -39
- portal/static/portal/sass/partials/_popup.scss +18 -15
- portal/static/portal/sass/partials/_tables.scss +12 -20
- portal/static/portal/sass/partials/_text.scss +6 -10
- portal/static/portal/sass/styles.scss +0 -1
- portal/static/portal/video/code for life .pdf +0 -0
- portal/strings/about.py +5 -0
- portal/strings/coding_club.py +9 -0
- portal/strings/play.py +6 -5
- portal/strings/teach.py +1 -1
- portal/strings/teacher_resources.py +2 -8
- portal/strings/ten_year_map.py +13 -0
- portal/templates/403.html +2 -2
- portal/templates/404.html +1 -1
- portal/templates/500.html +2 -2
- portal/templates/{captcha → django_recaptcha}/includes/js_v2_invisible.html +3 -3
- portal/templates/{captcha → django_recaptcha}/widget_v2_invisible.html +2 -2
- portal/templates/email.html +4 -2
- portal/templates/maintenance.html +34 -0
- portal/templates/portal/about.html +94 -62
- portal/templates/portal/base.html +176 -152
- portal/templates/portal/coding_club.html +100 -0
- portal/templates/portal/contribute.html +56 -52
- portal/templates/portal/email_invitation_sent.html +1 -1
- portal/templates/portal/email_style_template.html +374 -0
- portal/templates/portal/email_verification_failed.html +1 -1
- portal/templates/portal/email_verification_needed.html +9 -9
- portal/templates/portal/form_shapes.html +20 -8
- portal/templates/portal/getinvolved.html +6 -6
- portal/templates/portal/home.html +35 -10
- portal/templates/portal/home_learning.html +19 -19
- portal/templates/portal/locked_out.html +0 -1
- portal/templates/portal/locked_out_school_student.html +16 -0
- portal/templates/portal/login/independent_student.html +31 -15
- portal/templates/portal/login/student.html +10 -7
- portal/templates/portal/login/student_class_code.html +7 -4
- portal/templates/portal/login/teacher.html +34 -17
- portal/templates/portal/partials/banner.html +18 -4
- portal/templates/portal/partials/benefits.html +1 -1
- portal/templates/portal/partials/card_list.html +34 -24
- portal/templates/portal/partials/character_list.html +5 -5
- portal/templates/portal/partials/cookie_list.html +161 -0
- portal/templates/portal/partials/delete_popup.html +18 -0
- portal/templates/portal/partials/footer.html +57 -26
- portal/templates/portal/partials/header.html +118 -117
- portal/templates/portal/partials/hero_card.html +4 -3
- portal/templates/portal/partials/info_popup.html +3 -3
- portal/templates/portal/partials/invite_admin_teacher.html +23 -0
- portal/templates/portal/partials/popup.html +7 -2
- portal/templates/portal/partials/register_newsletter_tickbox.html +2 -5
- portal/templates/portal/partials/screentime_popup.html +14 -0
- portal/templates/portal/partials/service_unavailable_popup.html +17 -0
- portal/templates/portal/partials/session_popup.html +19 -0
- portal/templates/portal/play/student_dashboard.html +42 -29
- portal/templates/portal/play/student_edit_account.html +64 -9
- portal/templates/portal/play.html +61 -41
- portal/templates/portal/privacy_notice.html +697 -0
- portal/templates/portal/register.html +122 -92
- portal/templates/portal/reset_password.html +20 -40
- portal/templates/portal/reset_password_confirm.html +9 -4
- portal/templates/portal/reset_password_email_sent.html +15 -13
- portal/templates/portal/teach/base_registering.html +1 -1
- portal/templates/portal/teach/class.html +4 -6
- portal/templates/portal/teach/dashboard.html +212 -117
- portal/templates/portal/teach/invited.html +90 -0
- portal/templates/portal/teach/onboarding_classes.html +5 -3
- portal/templates/portal/teach/onboarding_print.html +1 -1
- portal/templates/portal/teach/onboarding_school.html +26 -139
- portal/templates/portal/teach/onboarding_students.html +1 -1
- portal/templates/portal/teach/teacher_dismiss_students.html +73 -55
- portal/templates/portal/teach/teacher_edit_class.html +168 -11
- portal/templates/portal/teach/teacher_edit_student.html +12 -5
- portal/templates/portal/teach/teacher_move_all_classes.html +25 -38
- portal/templates/portal/teach/teacher_move_students_to_class.html +1 -1
- portal/templates/portal/teach.html +61 -42
- portal/templates/portal/ten_year_map.html +147 -0
- portal/templates/portal/terms.html +191 -42
- portal/templates/two_factor/core/login.html +71 -59
- portal/templates/two_factor/core/setup.html +58 -49
- portal/templates/two_factor/profile/disable.html +1 -1
- portal/templates/two_factor/profile/profile.html +35 -17
- portal/templatetags/app_tags.py +59 -84
- portal/templatetags/card_list_tags.py +0 -4
- portal/tests/base_test.py +14 -3
- portal/tests/conftest.py +0 -15
- portal/tests/migrations/test_migration_make_portaladmin_teacher.py +2 -6
- portal/tests/migrations/test_migration_preview_users.py +3 -9
- portal/tests/migrations/test_migration_remove_guardian.py +1 -3
- portal/tests/migrations/test_migration_use_common_models.py +2 -6
- portal/tests/migrations/test_migration_verify_portaladmin.py +1 -3
- portal/tests/pageObjects/portal/admin/admin_base_page.py +0 -21
- portal/tests/pageObjects/portal/base_page.py +16 -26
- portal/tests/pageObjects/portal/email_verification_needed_page.py +3 -2
- portal/tests/pageObjects/portal/game_page.py +12 -19
- portal/tests/pageObjects/portal/home_page.py +13 -15
- portal/tests/pageObjects/portal/independent_login_page.py +13 -17
- portal/tests/pageObjects/portal/password_reset_form_page.py +20 -4
- portal/tests/pageObjects/portal/password_reset_page.py +25 -0
- portal/tests/pageObjects/portal/play/account_page.py +18 -27
- portal/tests/pageObjects/portal/play/dashboard_page.py +4 -4
- portal/tests/pageObjects/portal/play/join_school_or_club_page.py +8 -10
- portal/tests/pageObjects/portal/play/play_base_page.py +5 -3
- portal/tests/pageObjects/portal/signup_page.py +28 -59
- portal/tests/pageObjects/portal/student_login_class_code.py +6 -9
- portal/tests/pageObjects/portal/student_login_page.py +6 -8
- portal/tests/pageObjects/portal/teach/add_independent_student_to_class_page.py +3 -3
- portal/tests/pageObjects/portal/teach/added_independent_student_to_class_page.py +3 -1
- portal/tests/pageObjects/portal/teach/class_page.py +36 -13
- portal/tests/pageObjects/portal/teach/dashboard_page.py +43 -84
- portal/tests/pageObjects/portal/teach/dismiss_students_page.py +7 -5
- portal/tests/pageObjects/portal/teach/edit_student_page.py +10 -8
- portal/tests/pageObjects/portal/teach/move_class_page.py +5 -10
- portal/tests/pageObjects/portal/teach/move_classes_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_disambiguate_page.py +4 -2
- portal/tests/pageObjects/portal/teach/move_students_page.py +6 -13
- portal/tests/pageObjects/portal/teach/onboarding_classes_page.py +5 -3
- portal/tests/pageObjects/portal/teach/onboarding_organisation_page.py +11 -49
- portal/tests/pageObjects/portal/teach/onboarding_student_list_page.py +7 -12
- portal/tests/pageObjects/portal/teach/onboarding_students_page.py +4 -27
- portal/tests/pageObjects/portal/teach/teach_base_page.py +6 -4
- portal/tests/pageObjects/portal/teacher_login_page.py +10 -16
- portal/tests/selenium_test_case.py +3 -43
- portal/tests/snapshots/snap_test_partials.py +11 -165
- portal/tests/test_2FA.py +15 -33
- portal/tests/test_admin.py +15 -97
- portal/tests/test_api.py +212 -91
- portal/tests/test_captcha_forms.py +2 -2
- portal/tests/test_class.py +374 -24
- portal/tests/test_emails.py +83 -20
- portal/tests/{test_newsletter_footer.py → test_global_forms.py} +5 -5
- portal/tests/test_helper_methods.py +30 -0
- portal/tests/test_independent_student.py +255 -144
- portal/tests/test_invite_teacher.py +318 -10
- portal/tests/test_middleware.py +96 -9
- portal/tests/test_organisation.py +78 -262
- portal/tests/test_partials.py +0 -88
- portal/tests/test_ratelimit.py +218 -36
- portal/tests/test_school_student.py +35 -40
- portal/tests/test_security.py +12 -31
- portal/tests/test_teacher.py +425 -325
- portal/tests/test_teacher_student.py +103 -91
- portal/tests/test_views.py +900 -76
- portal/tests/utils/classes.py +2 -2
- portal/tests/utils/messages.py +13 -28
- portal/urls.py +235 -166
- portal/views/admin.py +0 -332
- portal/views/api.py +82 -48
- portal/views/cron/__init__.py +1 -0
- portal/views/cron/user.py +322 -0
- portal/views/dotmailer.py +9 -1
- portal/views/email.py +33 -77
- portal/views/google_analytics.py +28 -0
- portal/views/home.py +126 -97
- portal/views/legal.py +13 -0
- portal/views/login/independent_student.py +5 -5
- portal/views/login/student.py +51 -14
- portal/views/login/teacher.py +2 -6
- portal/views/organisation.py +20 -189
- portal/views/registration.py +97 -17
- portal/views/student/edit_account_details.py +99 -72
- portal/views/student/play.py +81 -62
- portal/views/teacher/dashboard.py +421 -149
- portal/views/teacher/teach.py +226 -177
- portal/views/two_factor/core.py +22 -19
- portal/views/two_factor/profile.py +2 -2
- codeforlife_portal-5.33.5.dist-info/LICENSE.md +0 -577
- codeforlife_portal-5.33.5.dist-info/METADATA +0 -38
- deploy/permissions.py +0 -2
- example_project/manage.py +0 -10
- portal/autoconfig.py +0 -141
- portal/csp_config.py +0 -60
- portal/forms/add_game.py +0 -33
- portal/helpers/location.py +0 -121
- portal/static/portal/img/kurono_hero.jpg +0 -0
- portal/static/portal/img/kurono_landing_hero.png +0 -0
- portal/static/portal/img/kurono_logo.svg +0 -1
- portal/static/portal/img/kurono_logo_grey_background.svg +0 -1
- portal/static/portal/img/kurono_logo_mark.svg +0 -1
- portal/static/portal/img/kurono_resources_hero.jpg +0 -0
- portal/static/portal/img/kurono_story.png +0 -0
- portal/static/portal/img/ocado-swirl.svg +0 -22
- portal/static/portal/img/thumbnail_educate_kurono.png +0 -0
- portal/static/portal/img/thumbnail_educate_resources_and_progress_tracking.png +0 -0
- portal/static/portal/img/thumbnail_kurono_resources.png +0 -0
- portal/static/portal/img/thumbnail_play_kurono.png +0 -0
- portal/static/portal/img/x_close_video.png +0 -0
- portal/static/portal/js/aimmoGame.js +0 -106
- portal/static/portal/js/deleteWorkspaces.js +0 -14
- portal/static/portal/js/fuzzySchoolLookup.js +0 -46
- portal/static/portal/js/lib/jquery-3.5.1.min.js +0 -2
- portal/static/portal/js/lib/jquery-ui-1.12.1.min.js +0 -13
- portal/static/portal/sass/partials/_videos.scss +0 -10
- portal/static/portal/video/aimmo_play_now_background_video.mp4 +0 -0
- portal/strings/student_aimmo_dashboard.py +0 -6
- portal/templates/portal/admin/aggregated_data.html +0 -35
- portal/templates/portal/admin/map.html +0 -70
- portal/templates/portal/mouseflow.html +0 -9
- portal/templates/portal/partials/aimmo_games_table.html +0 -83
- portal/templates/portal/partials/register_over_required_age_tickbox.html +0 -9
- portal/templates/portal/play/independent_student_dashboard.html +0 -64
- portal/templates/portal/play/student_aimmo_dashboard.html +0 -63
- portal/templates/portal/privacy_policy.html +0 -483
- portal/templates/portal/reset_password_email.html +0 -9
- portal/templates/portal/teach/invite.html +0 -25
- portal/templates/portal/teach/teacher_aimmo_dashboard.html +0 -95
- portal/templates/portal/teach/teacher_resources.html +0 -68
- portal/templatetags/character_list_tags.py +0 -16
- portal/tests/pageObjects/portal/kurono_teacher_dashboard_page.py +0 -49
- portal/tests/pageObjects/portal/student_password_reset_form_page.py +0 -23
- portal/tests/pageObjects/portal/teach/onboarding_revoke_request_page.py +0 -20
- portal/tests/pageObjects/portal/teacher_password_reset_form_page.py +0 -23
- portal/tests/test_aimmo_dashboards.py +0 -172
- portal/tests/test_location.py +0 -217
- portal/tests/utils/aimmo_games.py +0 -30
- portal/views/aimmo/dashboard.py +0 -119
- portal/views/privacy_policy.py +0 -9
- portal/views/teacher/teacher_resources.py +0 -42
- {portal/views/aimmo → cfl_common}/__init__.py +0 -0
portal/tests/test_ratelimit.py
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
from __future__ import absolute_import
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
|
+
from unittest.mock import ANY, Mock, patch
|
|
4
5
|
|
|
6
|
+
import pytest
|
|
5
7
|
import pytz
|
|
6
|
-
import
|
|
7
|
-
from common.models import
|
|
8
|
+
from common.mail import campaign_ids
|
|
9
|
+
from common.models import DailyActivity, Student, Teacher
|
|
8
10
|
from common.tests.utils.classes import create_class_directly
|
|
9
11
|
from common.tests.utils.organisation import create_organisation_directly
|
|
10
12
|
from common.tests.utils.student import (
|
|
11
13
|
create_independent_student_directly,
|
|
12
14
|
create_school_student_directly,
|
|
15
|
+
generate_independent_student_details,
|
|
13
16
|
)
|
|
14
|
-
from common.tests.utils.teacher import signup_teacher_directly
|
|
17
|
+
from common.tests.utils.teacher import generate_details, signup_teacher_directly
|
|
15
18
|
from django.core import mail
|
|
16
19
|
from django.test import Client, TestCase
|
|
17
|
-
from django.urls import reverse
|
|
20
|
+
from django.urls import reverse, reverse_lazy
|
|
18
21
|
|
|
19
22
|
from portal.helpers.ratelimit import get_ratelimit_count_for_user
|
|
20
23
|
from portal.views.login import has_user_lockout_expired
|
|
@@ -27,20 +30,17 @@ class TestRatelimit(TestCase):
|
|
|
27
30
|
def _teacher_login(self, username, password):
|
|
28
31
|
return self.client.post(
|
|
29
32
|
reverse("teacher_login"),
|
|
30
|
-
{
|
|
31
|
-
"auth-username": username,
|
|
32
|
-
"auth-password": password,
|
|
33
|
-
"teacher_login_view-current_step": "auth",
|
|
34
|
-
},
|
|
33
|
+
{"auth-username": username, "auth-password": password, "teacher_login_view-current_step": "auth"},
|
|
35
34
|
)
|
|
36
35
|
|
|
37
36
|
def _student_login(self, username, password):
|
|
37
|
+
return self.client.post(reverse("independent_student_login"), {"username": username, "password": password})
|
|
38
|
+
|
|
39
|
+
def _student_school_login(self, access_code, student_name, student_password):
|
|
38
40
|
return self.client.post(
|
|
39
|
-
reverse("
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
"password": password,
|
|
43
|
-
},
|
|
41
|
+
reverse("student_login", kwargs={"access_code": access_code}),
|
|
42
|
+
{"username": student_name, "password": student_password},
|
|
43
|
+
follow=True,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
def _teacher_update_account_bad_request(self) -> None:
|
|
@@ -79,23 +79,13 @@ class TestRatelimit(TestCase):
|
|
|
79
79
|
|
|
80
80
|
def _reset_password_request(self, email):
|
|
81
81
|
return self.client.post(
|
|
82
|
-
reverse("teacher_password_reset"),
|
|
83
|
-
{
|
|
84
|
-
"email": email,
|
|
85
|
-
"g-recaptcha-response": "something",
|
|
86
|
-
},
|
|
82
|
+
reverse("teacher_password_reset"), {"email": email, "g-recaptcha-response": "something"}
|
|
87
83
|
)
|
|
88
84
|
|
|
89
85
|
def _reset_password(self, url, new_password):
|
|
90
|
-
return self.client.post(
|
|
91
|
-
url,
|
|
92
|
-
{
|
|
93
|
-
"new_password1": new_password,
|
|
94
|
-
"new_password2": new_password,
|
|
95
|
-
},
|
|
96
|
-
)
|
|
86
|
+
return self.client.post(url, {"new_password1": new_password, "new_password2": new_password})
|
|
97
87
|
|
|
98
|
-
def _is_user_blocked(self, model: Teacher or Student, username: str) -> bool:
|
|
88
|
+
def _is_user_blocked(self, model: Teacher or Student, username: str, access_code: str = None) -> bool:
|
|
99
89
|
"""
|
|
100
90
|
Checks if a Teacher or a Student object is blocked, by checking if they
|
|
101
91
|
have a blocked_time value, and if so, if it the lockout has expired or not.
|
|
@@ -103,20 +93,30 @@ class TestRatelimit(TestCase):
|
|
|
103
93
|
:param username: The username of the Teacher or Student.
|
|
104
94
|
:return: Whether or not the model object is marked as blocked.
|
|
105
95
|
"""
|
|
106
|
-
user =
|
|
96
|
+
user = (
|
|
97
|
+
model.objects.get(new_user__username=username)
|
|
98
|
+
if not access_code
|
|
99
|
+
else model.objects.get(new_user__first_name=username, class_field__access_code=access_code)
|
|
100
|
+
)
|
|
107
101
|
if user.blocked_time:
|
|
108
102
|
return not has_user_lockout_expired(user)
|
|
109
103
|
else:
|
|
110
104
|
return False
|
|
111
105
|
|
|
112
|
-
def _block_user(self, model: Teacher or Student, username: str) -> None:
|
|
106
|
+
def _block_user(self, model: Teacher or Student, username: str, access_code=None) -> None:
|
|
113
107
|
"""
|
|
114
108
|
Finds the Teacher or Student corresponding to the username, and sets it as
|
|
115
109
|
blocked and sets the blocked date to now.
|
|
116
110
|
:param model: The model Class to be checked against.
|
|
117
111
|
:param username: The username of the Teacher or Student.
|
|
118
112
|
"""
|
|
119
|
-
|
|
113
|
+
|
|
114
|
+
user = (
|
|
115
|
+
model.objects.get(new_user__username=username)
|
|
116
|
+
if access_code is None
|
|
117
|
+
else model.objects.get(new_user__first_name=username, class_field__access_code=access_code)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
120
|
user.blocked_time = datetime.now(tz=pytz.utc)
|
|
121
121
|
user.save()
|
|
122
122
|
|
|
@@ -137,6 +137,54 @@ class TestRatelimit(TestCase):
|
|
|
137
137
|
|
|
138
138
|
assert self._is_user_blocked(Teacher, email)
|
|
139
139
|
|
|
140
|
+
def test_student_login_ratelimit(self):
|
|
141
|
+
"""
|
|
142
|
+
Given a student,
|
|
143
|
+
When they perform 6 failed login attempts,
|
|
144
|
+
Then on the 6th one, the student should be blocked.
|
|
145
|
+
"""
|
|
146
|
+
teacher_email, teacher_password = signup_teacher_directly()
|
|
147
|
+
school = create_organisation_directly(teacher_email)
|
|
148
|
+
klass, klass_name, klass_access_code = create_class_directly(teacher_email)
|
|
149
|
+
student_name, student_password, student = create_school_student_directly(klass_access_code)
|
|
150
|
+
|
|
151
|
+
for i in range(10):
|
|
152
|
+
response = self._student_school_login(klass_access_code, student_name, "bad_password")
|
|
153
|
+
|
|
154
|
+
assert not self._is_user_blocked(Student, student_name, klass_access_code)
|
|
155
|
+
|
|
156
|
+
_ = self._student_school_login(klass_access_code, student_name, "bad_password")
|
|
157
|
+
|
|
158
|
+
assert self._is_user_blocked(Student, student_name, klass_access_code)
|
|
159
|
+
student = Student.objects.get(id=student.id)
|
|
160
|
+
current_student = Student.objects.get(
|
|
161
|
+
new_user__first_name=student_name, class_field__access_code=klass_access_code
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# now check if teacher can unlock it, both ways :)
|
|
165
|
+
url = reverse_lazy("teacher_class_password_reset", kwargs={"access_code": klass_access_code})
|
|
166
|
+
data = {"transfer_students": [[current_student.id]]}
|
|
167
|
+
c = Client()
|
|
168
|
+
|
|
169
|
+
c.login(username=teacher_email, password=teacher_password)
|
|
170
|
+
c.post(url, data)
|
|
171
|
+
assert not self._is_user_blocked(Student, student_name, klass_access_code)
|
|
172
|
+
|
|
173
|
+
# now block again and test the edit by student method
|
|
174
|
+
self._block_user(Student, student_name, klass_access_code)
|
|
175
|
+
assert self._is_user_blocked(Student, student_name, klass_access_code)
|
|
176
|
+
url = reverse_lazy("teacher_edit_student", kwargs={"pk": current_student.id})
|
|
177
|
+
strong_password = "£EDCVFR$5tgbnhy6"
|
|
178
|
+
data = {"password": strong_password, "confirm_password": strong_password, "set_password": ""}
|
|
179
|
+
|
|
180
|
+
c.post(url, data)
|
|
181
|
+
assert not self._is_user_blocked(Student, student_name, klass_access_code)
|
|
182
|
+
c.logout()
|
|
183
|
+
student = Student.objects.get(id=student.id)
|
|
184
|
+
self._student_school_login(klass_access_code, student_name, "password1")
|
|
185
|
+
student = Student.objects.get(id=student.id)
|
|
186
|
+
assert not self._is_user_blocked(Student, student_name, klass_access_code)
|
|
187
|
+
|
|
140
188
|
def test_independent_student_login_ratelimit(self):
|
|
141
189
|
"""
|
|
142
190
|
Given an independent student,
|
|
@@ -277,7 +325,8 @@ class TestRatelimit(TestCase):
|
|
|
277
325
|
|
|
278
326
|
assert get_ratelimit_count_for_user(email) == 1
|
|
279
327
|
|
|
280
|
-
|
|
328
|
+
@patch("portal.forms.registration.send_dotdigital_email")
|
|
329
|
+
def test_teacher_reset_password_unblocks_user(self, mock_send_dotdigital_email: Mock):
|
|
281
330
|
"""
|
|
282
331
|
Given a blocked teacher,
|
|
283
332
|
When they reset they password,
|
|
@@ -295,17 +344,150 @@ class TestRatelimit(TestCase):
|
|
|
295
344
|
# Ask for reset password link
|
|
296
345
|
self._reset_password_request(email)
|
|
297
346
|
|
|
298
|
-
|
|
347
|
+
mock_send_dotdigital_email.assert_called_once_with(
|
|
348
|
+
campaign_ids["reset_password"], ANY, personalization_values=ANY
|
|
349
|
+
)
|
|
299
350
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
351
|
+
reset_password_url = mock_send_dotdigital_email.call_args.kwargs["personalization_values"][
|
|
352
|
+
"RESET_PASSWORD_LINK"
|
|
353
|
+
]
|
|
303
354
|
|
|
304
355
|
new_password = "AnotherPassword12!"
|
|
305
356
|
|
|
306
|
-
self._reset_password(
|
|
357
|
+
self._reset_password(reset_password_url, new_password)
|
|
307
358
|
|
|
308
359
|
login_response = self._teacher_login(email, new_password)
|
|
309
360
|
|
|
310
361
|
assert login_response.status_code == 302
|
|
311
362
|
assert not self._is_user_blocked(Teacher, email)
|
|
363
|
+
|
|
364
|
+
def test_lockout_reset_tracking(self):
|
|
365
|
+
old_date = datetime.now() - timedelta(days=1)
|
|
366
|
+
old_daily_activity = DailyActivity(date=old_date)
|
|
367
|
+
old_daily_activity.save()
|
|
368
|
+
teacher_email, teacher_password = signup_teacher_directly()
|
|
369
|
+
indy_email, indy_password, student = create_independent_student_directly()
|
|
370
|
+
create_organisation_directly(teacher_email)
|
|
371
|
+
|
|
372
|
+
self._block_user(Teacher, teacher_email)
|
|
373
|
+
self._block_user(Student, indy_email)
|
|
374
|
+
|
|
375
|
+
# check teacher response for resetting password
|
|
376
|
+
url = reverse_lazy("teacher_password_reset")
|
|
377
|
+
data = {"email": teacher_email}
|
|
378
|
+
|
|
379
|
+
c = Client()
|
|
380
|
+
|
|
381
|
+
response = c.post(url, data=data)
|
|
382
|
+
old_daily_activity = DailyActivity.objects.get(date=old_date)
|
|
383
|
+
current_daily_activity = DailyActivity.objects.get(date=datetime.now())
|
|
384
|
+
|
|
385
|
+
assert response.status_code == 200
|
|
386
|
+
assert old_daily_activity.teacher_lockout_resets == 0
|
|
387
|
+
assert current_daily_activity.teacher_lockout_resets == 1
|
|
388
|
+
# now check the indy student
|
|
389
|
+
|
|
390
|
+
url = reverse_lazy("student_password_reset")
|
|
391
|
+
data = {"email": indy_email}
|
|
392
|
+
c = Client()
|
|
393
|
+
|
|
394
|
+
response = c.post(url, data=data)
|
|
395
|
+
old_daily_activity = DailyActivity.objects.get(date=old_date)
|
|
396
|
+
current_daily_activity = DailyActivity.objects.get(date=datetime.now())
|
|
397
|
+
|
|
398
|
+
assert response.status_code == 200
|
|
399
|
+
assert old_daily_activity.indy_lockout_resets == 0
|
|
400
|
+
assert current_daily_activity.indy_lockout_resets == 1
|
|
401
|
+
# finally check the school student
|
|
402
|
+
|
|
403
|
+
# method 1
|
|
404
|
+
_, _, klass_access_code = create_class_directly(teacher_email)
|
|
405
|
+
student_name, _, student = create_school_student_directly(klass_access_code)
|
|
406
|
+
|
|
407
|
+
self._block_user(Student, student_name, access_code=klass_access_code)
|
|
408
|
+
|
|
409
|
+
c = Client()
|
|
410
|
+
c.login(username=teacher_email, password=teacher_password)
|
|
411
|
+
|
|
412
|
+
url = reverse_lazy("teacher_edit_student", kwargs={"pk": student.id})
|
|
413
|
+
strong_password = "£EDCVFR$5tgb"
|
|
414
|
+
data = {"password": strong_password, "confirm_password": strong_password, "set_password": ""}
|
|
415
|
+
|
|
416
|
+
response = c.post(url, data)
|
|
417
|
+
old_daily_activity = DailyActivity.objects.get(date=old_date)
|
|
418
|
+
current_daily_activity = DailyActivity.objects.get(date=datetime.now())
|
|
419
|
+
|
|
420
|
+
assert response.status_code == 200
|
|
421
|
+
assert old_daily_activity.school_student_lockout_resets == 0
|
|
422
|
+
assert current_daily_activity.school_student_lockout_resets == 1
|
|
423
|
+
|
|
424
|
+
# method 2
|
|
425
|
+
self._block_user(Student, student_name, access_code=klass_access_code)
|
|
426
|
+
url = reverse_lazy("teacher_class_password_reset", kwargs={"access_code": klass_access_code})
|
|
427
|
+
data = {"transfer_students": [[student.id]]}
|
|
428
|
+
|
|
429
|
+
response = c.post(url, data)
|
|
430
|
+
old_daily_activity = DailyActivity.objects.get(date=old_date)
|
|
431
|
+
current_daily_activity = DailyActivity.objects.get(date=datetime.now())
|
|
432
|
+
|
|
433
|
+
assert response.status_code == 200
|
|
434
|
+
assert old_daily_activity.school_student_lockout_resets == 0
|
|
435
|
+
assert current_daily_activity.school_student_lockout_resets == 2
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
439
|
+
@pytest.mark.django_db
|
|
440
|
+
def test_teacher_already_registered_email(mock_send_dotdigital_email: Mock, client):
|
|
441
|
+
first_name, last_name, email, password = generate_details()
|
|
442
|
+
register_url = reverse("register")
|
|
443
|
+
data = {
|
|
444
|
+
"teacher_signup-teacher_first_name": first_name,
|
|
445
|
+
"teacher_signup-teacher_last_name": last_name,
|
|
446
|
+
"teacher_signup-teacher_email": email,
|
|
447
|
+
"teacher_signup-consent_ticked": "on",
|
|
448
|
+
"teacher_signup-teacher_password": password,
|
|
449
|
+
"teacher_signup-teacher_confirm_password": password,
|
|
450
|
+
"g-recaptcha-response": "something",
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
# Register the teacher first time, there should be a registration email
|
|
454
|
+
client.post(register_url, data)
|
|
455
|
+
mock_send_dotdigital_email.assert_called_once_with(campaign_ids["verify_new_user"], ANY, personalization_values=ANY)
|
|
456
|
+
|
|
457
|
+
# Register with the same email again, there should also be an already registered email
|
|
458
|
+
client.post(register_url, data)
|
|
459
|
+
assert len(mail.outbox) == 1
|
|
460
|
+
|
|
461
|
+
# Register with the same email one more time, there shouldn't be any new emails
|
|
462
|
+
client.post(register_url, data)
|
|
463
|
+
assert len(mail.outbox) == 1
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@patch("common.helpers.emails.send_dotdigital_email")
|
|
467
|
+
@pytest.mark.django_db
|
|
468
|
+
def test_independent_student_already_registered_email(mock_send_dotdigital_email: Mock, client):
|
|
469
|
+
name, username, email_address, password = generate_independent_student_details()
|
|
470
|
+
register_url = reverse("register")
|
|
471
|
+
data = {
|
|
472
|
+
"independent_student_signup-date_of_birth_day": 7,
|
|
473
|
+
"independent_student_signup-date_of_birth_month": 10,
|
|
474
|
+
"independent_student_signup-date_of_birth_year": 1997,
|
|
475
|
+
"independent_student_signup-name": name,
|
|
476
|
+
"independent_student_signup-email": email_address,
|
|
477
|
+
"independent_student_signup-consent_ticked": "on",
|
|
478
|
+
"independent_student_signup-password": password,
|
|
479
|
+
"independent_student_signup-confirm_password": password,
|
|
480
|
+
"g-recaptcha-response": "something",
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
# Register the independent student first time, there should be a registration email
|
|
484
|
+
client.post(register_url, data)
|
|
485
|
+
mock_send_dotdigital_email.assert_called_once_with(campaign_ids["verify_new_user"], ANY, personalization_values=ANY)
|
|
486
|
+
|
|
487
|
+
# Register with the same email again, there should also be an already registered email
|
|
488
|
+
client.post(register_url, data)
|
|
489
|
+
assert len(mail.outbox) == 1
|
|
490
|
+
|
|
491
|
+
# Reset mock and register with the same email one more time, there shouldn't be any new emails
|
|
492
|
+
client.post(register_url, data)
|
|
493
|
+
assert len(mail.outbox) == 1
|
|
@@ -7,10 +7,7 @@ from common.tests.utils.teacher import signup_teacher_directly
|
|
|
7
7
|
|
|
8
8
|
from portal.tests.pageObjects.portal.home_page import HomePage
|
|
9
9
|
from .base_test import BaseTest
|
|
10
|
-
from .utils.messages import
|
|
11
|
-
is_student_details_updated_message_showing,
|
|
12
|
-
is_password_updated_message_showing,
|
|
13
|
-
)
|
|
10
|
+
from .utils.messages import is_student_details_updated_message_showing, is_password_updated_message_showing
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
class TestSchoolStudent(BaseTest):
|
|
@@ -36,15 +33,10 @@ class TestSchoolStudent(BaseTest):
|
|
|
36
33
|
student_name, _, _ = create_school_student_directly(access_code)
|
|
37
34
|
|
|
38
35
|
self.selenium.get(self.live_server_url)
|
|
39
|
-
page = (
|
|
40
|
-
HomePage(self.selenium)
|
|
41
|
-
.go_to_student_login_page()
|
|
42
|
-
.student_input_access_code_failure("not a class code")
|
|
43
|
-
)
|
|
36
|
+
page = HomePage(self.selenium).go_to_student_login_page().student_input_access_code_failure("not a class code")
|
|
44
37
|
|
|
45
38
|
assert page.has_access_code_input_failed(
|
|
46
|
-
"form-login-school-class-code",
|
|
47
|
-
"Uh oh! You didn't input a valid class code.",
|
|
39
|
+
"form-login-school-class-code", "Uh oh! You didn't input a valid class code."
|
|
48
40
|
)
|
|
49
41
|
|
|
50
42
|
def test_login_failure(self):
|
|
@@ -61,9 +53,7 @@ class TestSchoolStudent(BaseTest):
|
|
|
61
53
|
.student_login_failure(student_name, "some other password")
|
|
62
54
|
)
|
|
63
55
|
|
|
64
|
-
assert page.has_login_failed(
|
|
65
|
-
"form-login-school", "Invalid name, class access code or password"
|
|
66
|
-
)
|
|
56
|
+
assert page.has_login_failed("form-login-school", "Invalid name, class access code or password")
|
|
67
57
|
|
|
68
58
|
def test_login_nonexistent_class(self):
|
|
69
59
|
email, _ = signup_teacher_directly()
|
|
@@ -79,9 +69,7 @@ class TestSchoolStudent(BaseTest):
|
|
|
79
69
|
.student_login_failure(student_name, student_password)
|
|
80
70
|
)
|
|
81
71
|
|
|
82
|
-
assert page.has_login_failed(
|
|
83
|
-
"form-login-school", "Invalid name, class access code or password"
|
|
84
|
-
)
|
|
72
|
+
assert page.has_login_failed("form-login-school", "Invalid name, class access code or password")
|
|
85
73
|
|
|
86
74
|
def test_login_empty_class(self):
|
|
87
75
|
email, _ = signup_teacher_directly()
|
|
@@ -98,9 +86,7 @@ class TestSchoolStudent(BaseTest):
|
|
|
98
86
|
.student_login_failure(student_name, student_password)
|
|
99
87
|
)
|
|
100
88
|
|
|
101
|
-
assert page.has_login_failed(
|
|
102
|
-
"form-login-school", "Invalid name, class access code or password"
|
|
103
|
-
)
|
|
89
|
+
assert page.has_login_failed("form-login-school", "Invalid name, class access code or password")
|
|
104
90
|
|
|
105
91
|
def test_update_password_current_password_wrong(self):
|
|
106
92
|
email, _ = signup_teacher_directly()
|
|
@@ -118,12 +104,10 @@ class TestSchoolStudent(BaseTest):
|
|
|
118
104
|
assert self.is_dashboard(page)
|
|
119
105
|
|
|
120
106
|
page = page.go_to_account_page().update_password_failure(
|
|
121
|
-
"
|
|
107
|
+
"£EDCVFR$5tgb", "£EDCVFR$5tgb", "Wrong_123$£$3_Password"
|
|
122
108
|
)
|
|
123
109
|
assert self.is_account_page(page)
|
|
124
|
-
assert page.was_form_invalid(
|
|
125
|
-
"student_account_form", "Your current password was incorrect"
|
|
126
|
-
)
|
|
110
|
+
assert page.was_form_invalid("student_account_form", "Your current password was incorrect")
|
|
127
111
|
|
|
128
112
|
def test_update_password_passwords_not_match(self):
|
|
129
113
|
email, _ = signup_teacher_directly()
|
|
@@ -140,13 +124,9 @@ class TestSchoolStudent(BaseTest):
|
|
|
140
124
|
)
|
|
141
125
|
assert self.is_dashboard(page)
|
|
142
126
|
|
|
143
|
-
page = page.go_to_account_page().update_password_failure(
|
|
144
|
-
"NewPassword1", "OtherPassword1", student_password
|
|
145
|
-
)
|
|
127
|
+
page = page.go_to_account_page().update_password_failure("£EDECVFR$5tgb", "%TGBNHY^&ujm,ki8", student_password)
|
|
146
128
|
assert self.is_account_page(page)
|
|
147
|
-
assert page.was_form_invalid(
|
|
148
|
-
"student_account_form", "Your new passwords do not match"
|
|
149
|
-
)
|
|
129
|
+
assert page.was_form_invalid("student_account_form", "Your new passwords do not match")
|
|
150
130
|
|
|
151
131
|
def test_update_password_too_weak(self):
|
|
152
132
|
email, _ = signup_teacher_directly()
|
|
@@ -163,16 +143,14 @@ class TestSchoolStudent(BaseTest):
|
|
|
163
143
|
)
|
|
164
144
|
assert self.is_dashboard(page)
|
|
165
145
|
|
|
166
|
-
page = page.go_to_account_page().update_password_failure(
|
|
167
|
-
"tiny", "tiny", student_password
|
|
168
|
-
)
|
|
146
|
+
page = page.go_to_account_page().update_password_failure("tiny", "tiny", student_password)
|
|
169
147
|
assert self.is_account_page(page)
|
|
170
148
|
assert page.was_form_invalid(
|
|
171
149
|
"student_account_form",
|
|
172
150
|
"Password not strong enough, consider using at least 6 characters and making it hard to guess.",
|
|
173
151
|
)
|
|
174
152
|
|
|
175
|
-
def
|
|
153
|
+
def test_update_password_too_common(self):
|
|
176
154
|
email, _ = signup_teacher_directly()
|
|
177
155
|
create_organisation_directly(email)
|
|
178
156
|
_, _, access_code = create_class_directly(email)
|
|
@@ -187,18 +165,35 @@ class TestSchoolStudent(BaseTest):
|
|
|
187
165
|
)
|
|
188
166
|
assert self.is_dashboard(page)
|
|
189
167
|
|
|
190
|
-
|
|
168
|
+
page = page.go_to_account_page().update_password_failure("Password123$", "Password123$", student_password)
|
|
169
|
+
assert self.is_account_page(page)
|
|
170
|
+
assert page.was_form_invalid(
|
|
171
|
+
"student_account_form", "Password is too common, consider using a different password."
|
|
172
|
+
)
|
|
191
173
|
|
|
192
|
-
|
|
193
|
-
|
|
174
|
+
def test_update_password_success(self):
|
|
175
|
+
email, _ = signup_teacher_directly()
|
|
176
|
+
create_organisation_directly(email)
|
|
177
|
+
_, _, access_code = create_class_directly(email)
|
|
178
|
+
student_name, student_password, _ = create_school_student_directly(access_code)
|
|
179
|
+
|
|
180
|
+
self.selenium.get(self.live_server_url)
|
|
181
|
+
page = (
|
|
182
|
+
HomePage(self.selenium)
|
|
183
|
+
.go_to_student_login_page()
|
|
184
|
+
.student_input_access_code(access_code)
|
|
185
|
+
.student_login(student_name, student_password)
|
|
194
186
|
)
|
|
187
|
+
assert self.is_dashboard(page)
|
|
188
|
+
|
|
189
|
+
new_password = "£EDCVFR$%TGBhny6"
|
|
190
|
+
|
|
191
|
+
page = page.go_to_account_page().update_password_success(new_password, student_password)
|
|
195
192
|
assert is_student_details_updated_message_showing(self.selenium)
|
|
196
193
|
assert is_password_updated_message_showing(self.selenium)
|
|
197
194
|
assert self.is_login_class_code_page(page)
|
|
198
195
|
|
|
199
|
-
page = page.student_input_access_code(access_code).student_login(
|
|
200
|
-
student_name, new_password
|
|
201
|
-
)
|
|
196
|
+
page = page.student_input_access_code(access_code).student_login(student_name, new_password)
|
|
202
197
|
assert self.is_dashboard(page)
|
|
203
198
|
|
|
204
199
|
def is_dashboard(self, page):
|
portal/tests/test_security.py
CHANGED
|
@@ -2,13 +2,12 @@ from __future__ import absolute_import
|
|
|
2
2
|
|
|
3
3
|
from builtins import str
|
|
4
4
|
|
|
5
|
-
from django.contrib.auth.models import User
|
|
6
|
-
from django.urls import reverse, reverse_lazy
|
|
7
|
-
from django.test import Client, TestCase
|
|
8
|
-
|
|
9
5
|
from common.models import School, Student, UserProfile
|
|
10
6
|
from common.tests.utils.classes import create_class_directly
|
|
11
7
|
from common.tests.utils.teacher import signup_teacher_directly
|
|
8
|
+
from django.contrib.auth.models import User
|
|
9
|
+
from django.test import Client, TestCase
|
|
10
|
+
from django.urls import reverse, reverse_lazy
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class SecurityTestCase(TestCase):
|
|
@@ -20,7 +19,7 @@ class SecurityTestCase(TestCase):
|
|
|
20
19
|
c = Client()
|
|
21
20
|
assert c.login(username=email2, password=pass2)
|
|
22
21
|
page = reverse(view_name, args=[access_code])
|
|
23
|
-
|
|
22
|
+
assert not c.get(page).status_code == 200
|
|
24
23
|
|
|
25
24
|
def _test_incorrect_teacher_no_info_leak(self, view_name):
|
|
26
25
|
email1, _ = signup_teacher_directly()
|
|
@@ -33,10 +32,10 @@ class SecurityTestCase(TestCase):
|
|
|
33
32
|
invalid_page = reverse(view_name, args=[access_code])
|
|
34
33
|
invalid_login_code = c.get(invalid_page).status_code
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
non_existent_page = reverse(view_name, args=["AAAAA"])
|
|
36
|
+
non_existent_code = c.get(non_existent_page).status_code
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
assert non_existent_code == invalid_login_code
|
|
40
39
|
|
|
41
40
|
def test_reminder_cards_info_leak(self):
|
|
42
41
|
"""Check that it isn't leaked whether an access code exists."""
|
|
@@ -55,20 +54,11 @@ class SecurityTestCase(TestCase):
|
|
|
55
54
|
stu = Student(user=profile)
|
|
56
55
|
stu.save()
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
c.get(reverse("teacher_edit_student", kwargs={"pk": "9999"})).status_code
|
|
60
|
-
c.get(reverse("teacher_edit_student", kwargs={"pk": stu.pk})).status_code
|
|
57
|
+
assert (
|
|
58
|
+
c.get(reverse("teacher_edit_student", kwargs={"pk": "9999"})).status_code
|
|
59
|
+
== c.get(reverse("teacher_edit_student", kwargs={"pk": stu.pk})).status_code
|
|
61
60
|
)
|
|
62
61
|
|
|
63
|
-
def test_cannot_lookup_schools_if_not_logged_in(self):
|
|
64
|
-
client = Client()
|
|
65
|
-
|
|
66
|
-
url = reverse("organisation_fuzzy_lookup")
|
|
67
|
-
data = {"fuzzy_name": ["A"]}
|
|
68
|
-
response = client.get(url, data=data)
|
|
69
|
-
|
|
70
|
-
self.assertEqual(403, response.status_code)
|
|
71
|
-
|
|
72
62
|
def test_cannot_create_school_with_email_as_name(self):
|
|
73
63
|
number_of_existing_schools = len(School.objects.all())
|
|
74
64
|
|
|
@@ -78,16 +68,11 @@ class SecurityTestCase(TestCase):
|
|
|
78
68
|
client.login(username=email, password=password)
|
|
79
69
|
|
|
80
70
|
url = reverse("onboarding-organisation")
|
|
81
|
-
data = {
|
|
82
|
-
"name": email,
|
|
83
|
-
"postcode": "TEST",
|
|
84
|
-
"country": "GB",
|
|
85
|
-
"create_organisation": "",
|
|
86
|
-
}
|
|
71
|
+
data = {"name": email, "postcode": "TEST", "country": "GB", "create_organisation": ""}
|
|
87
72
|
|
|
88
73
|
client.post(url, data)
|
|
89
74
|
|
|
90
|
-
|
|
75
|
+
assert number_of_existing_schools == len(School.objects.all())
|
|
91
76
|
|
|
92
77
|
def test_reminder_cards_wrong_teacher(self):
|
|
93
78
|
"""Try and view reminder cards without being the teacher for that class."""
|
|
@@ -97,7 +82,3 @@ class SecurityTestCase(TestCase):
|
|
|
97
82
|
"""Try and view a class page without being the teacher for that class."""
|
|
98
83
|
self._test_incorrect_teacher_cannot_login("onboarding-class")
|
|
99
84
|
|
|
100
|
-
def test_anonymous_cannot_access_teaching_materials(self):
|
|
101
|
-
c = Client()
|
|
102
|
-
page = reverse_lazy("materials")
|
|
103
|
-
self.assertNotEqual(str(c.get(page).status_code)[0], 2)
|