From 4f80fd65404a01eb9f2b4fa90022263af627f1bb Mon Sep 17 00:00:00 2001 From: Waheed Ahmed Date: Wed, 1 Jul 2020 18:07:23 +0500 Subject: [PATCH 1/2] Improve password reset rate limit. Used django-ratelimit instead of django-ratelimit-backend to configure two different rate limit configurations for same endpoint. PROD-1708 --- cms/envs/bok_choy.yml | 3 + cms/envs/common.py | 4 ++ .../djangoapps/util/request_rate_limiter.py | 72 ------------------- lms/envs/bok_choy.yml | 7 +- lms/envs/common.py | 8 +-- .../user_authn/views/password_reset.py | 35 ++++----- .../user_authn/views/tests/test_password.py | 24 +++++-- .../views/tests/test_reset_password.py | 49 ++++++++----- 8 files changed, 81 insertions(+), 121 deletions(-) diff --git a/cms/envs/bok_choy.yml b/cms/envs/bok_choy.yml index c6c90bddf8..6aa54f0b84 100644 --- a/cms/envs/bok_choy.yml +++ b/cms/envs/bok_choy.yml @@ -112,6 +112,9 @@ MODULESTORE: - ENGINE: xmodule.modulestore.xml.XMLModuleStore NAME: xml OPTIONS: {data_dir: '** OVERRIDDEN **', default_class: xmodule.hidden_module.HiddenDescriptor} +# We need to test different scenarios, following setting effectively disbale rate limiting +PASSWORD_RESET_IP_RATE: '1/s' +PASSWORD_RESET_EMAIL_RATE: '1/s' SECRET_KEY: '' SERVER_EMAIL: devops@example.com SESSION_COOKIE_DOMAIN: null diff --git a/cms/envs/common.py b/cms/envs/common.py index edbfb8b849..11dc5dd9cc 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2254,3 +2254,7 @@ DISABLE_DEPRECATED_SIGNUP_URL = False ##### LOGISTRATION RATE LIMIT SETTINGS ##### LOGISTRATION_RATELIMIT_RATE = '100/5m' + +##### PASSWORD RESET RATE LIMIT SETTINGS ##### +PASSWORD_RESET_IP_RATE = '1/m' +PASSWORD_RESET_EMAIL_RATE = '2/h' diff --git a/common/djangoapps/util/request_rate_limiter.py b/common/djangoapps/util/request_rate_limiter.py index 384ac28276..015890963d 100644 --- a/common/djangoapps/util/request_rate_limiter.py +++ b/common/djangoapps/util/request_rate_limiter.py @@ -3,9 +3,6 @@ A utility class which wraps the RateLimitMixin 3rd party class to do bad request which can be used for rate limiting """ -from datetime import datetime, timedelta - -from django.conf import settings from ratelimitbackend.backends import RateLimitMixin @@ -32,72 +29,3 @@ class BadRequestRateLimiter(RequestRateLimiter): Default rate limit is 30 requests for every 5 minutes. """ pass - - -class PasswordResetEmailRateLimiter(RequestRateLimiter): - """ - Rate limiting requests to send password reset emails. - """ - email_rate_limit = getattr(settings, 'PASSWORD_RESET_EMAIL_RATE_LIMIT', {}) - requests = email_rate_limit.get('no_of_emails', 1) - cache_timeout_seconds = email_rate_limit.get('per_seconds', 60) - reset_email_cache_prefix = 'resetemail' - - def key(self, request, dt): - """ - Returns IP based cache key. - """ - return '%s-%s-%s' % ( - self.reset_email_cache_prefix, - self.get_ip(request), - dt.strftime('%Y%m%d%H%M'), - ) - - def email_key(self, request, dt): - """ - Returns email based cache key. - """ - return '%s-%s-%s' % ( - self.reset_email_cache_prefix, - self.get_email(request), - dt.strftime('%Y%m%d%H%M'), - ) - - def expire_after(self): - """ - Returns timeout for cache keys. - """ - return self.cache_timeout_seconds - - def get_email(self, request): - """ - Returns email id for cache key. - """ - user = request.user - # Prefer logged-in user's email - email = user.email if user.is_authenticated else request.POST.get('email') - return email - - def keys_to_check(self, request): - """ - Return list of IP and email based keys. - """ - keys = super(PasswordResetEmailRateLimiter, self).keys_to_check(request) - - now = datetime.now() - email_keys = [ - self.email_key( - request, - now - timedelta(minutes=minute), - ) for minute in range(self.minutes + 1) - ] - keys.extend(email_keys) - - return keys - - def tick_request_counter(self, request): - """ - Ticks any counters used to compute when rate limit has been reached. - """ - for key in self.keys_to_check(request): - self.cache_incr(key) diff --git a/lms/envs/bok_choy.yml b/lms/envs/bok_choy.yml index 9e555ebaaa..6feab78ef8 100644 --- a/lms/envs/bok_choy.yml +++ b/lms/envs/bok_choy.yml @@ -211,9 +211,8 @@ MODULESTORE: NAME: xml OPTIONS: {data_dir: '** OVERRIDDEN **', default_class: xmodule.hidden_module.HiddenDescriptor} # We need to test different scenarios, following setting effectively disbale rate limiting -PASSWORD_RESET_EMAIL_RATE_LIMIT: - no_of_emails: 1 - per_seconds: 1 +PASSWORD_RESET_IP_RATE: '1/s' +PASSWORD_RESET_EMAIL_RATE: '1/s' PASSWORD_RESET_SUPPORT_LINK: https://support.example.com/password-reset-help.html REGISTRATION_EXTENSION_FORM: openedx.core.djangoapps.user_api.tests.test_helpers.TestCaseForm REGISTRATION_EXTRA_FIELDS: {city: hidden, country: required, gender: optional, goals: optional, @@ -237,7 +236,7 @@ TECH_SUPPORT_EMAIL: technical@example.com THIRD_PARTY_AUTH_BACKENDS: [social_core.backends.google.GoogleOAuth2, social_core.backends.linkedin.LinkedinOAuth2, social_core.backends.facebook.FacebookOAuth2, third_party_auth.dummy.DummyBackend, third_party_auth.saml.SAMLAuthBackend] -THIRD_PARTY_AUTH: +THIRD_PARTY_AUTH: Google: SOCIAL_AUTH_GOOGLE_OAUTH2_KEY": "test" SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET": "test" diff --git a/lms/envs/common.py b/lms/envs/common.py index 38221127d8..6792b1398f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -531,10 +531,6 @@ SOFTWARE_SECURE_REQUEST_RETRY_DELAY = 60 * 60 # Maximum of 6 retries before giving up. SOFTWARE_SECURE_RETRY_MAX_ATTEMPTS = 6 -PASSWORD_RESET_EMAIL_RATE_LIMIT = { - 'no_of_emails': 1, - 'per_seconds': 60 -} RETRY_CALENDAR_SYNC_EMAIL_MAX_ATTEMPTS = 5 # Deadline message configurations COURSE_MESSAGE_ALERT_DURATION_IN_DAYS = 14 @@ -3777,6 +3773,10 @@ RATELIMIT_RATE = '120/m' ##### LOGISTRATION RATE LIMIT SETTINGS ##### LOGISTRATION_RATELIMIT_RATE = '100/5m' +##### PASSWORD RESET RATE LIMIT SETTINGS ##### +PASSWORD_RESET_IP_RATE = '1/m' +PASSWORD_RESET_EMAIL_RATE = '2/h' + ############### Settings for Retirement ##################### RETIRED_USERNAME_PREFIX = 'retired__user_' RETIRED_EMAIL_PREFIX = 'retired__user_' diff --git a/openedx/core/djangoapps/user_authn/views/password_reset.py b/openedx/core/djangoapps/user_authn/views/password_reset.py index 066ee2f47b..17d5276b8a 100644 --- a/openedx/core/djangoapps/user_authn/views/password_reset.py +++ b/openedx/core/djangoapps/user_authn/views/password_reset.py @@ -24,6 +24,7 @@ from django.views.decorators.http import require_POST from edx_ace import ace from edx_ace.recipient import Recipient from eventtracking import tracker +from ratelimit.decorators import ratelimit from rest_framework.views import APIView from edxmako.shortcuts import render_to_string @@ -43,8 +44,9 @@ from student.forms import send_account_recovery_email_for_user from student.models import AccountRecovery from util.json_request import JsonResponse from util.password_policy_validators import normalize_password, validate_password -from util.request_rate_limiter import PasswordResetEmailRateLimiter +POST_EMAIL_KEY = 'post:email' +REAL_IP_KEY = 'openedx.core.djangoapps.util.ratelimit.real_ip' SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated' # Maintaining this naming for backwards compatibility. @@ -238,15 +240,19 @@ def request_password_change(email, is_secure): @csrf_exempt @require_POST +@ratelimit(key=POST_EMAIL_KEY, rate=settings.PASSWORD_RESET_EMAIL_RATE) +@ratelimit(key=REAL_IP_KEY, rate=settings.PASSWORD_RESET_IP_RATE) def password_reset(request): """ Attempts to send a password reset e-mail. """ + user = request.user + # Prefer logged-in user's email + email = user.email if user.is_authenticated else request.POST.get('email') + AUDIT_LOG.info("Password reset initiated for email %s.", email) - password_reset_email_limiter = PasswordResetEmailRateLimiter() - - if password_reset_email_limiter.is_rate_limit_exceeded(request): - AUDIT_LOG.warning("Password reset rate limit exceeded") + if getattr(request, 'limited', False): + AUDIT_LOG.warning("Password reset rate limit exceeded for email %s.", email) return JsonResponse( { 'success': False, @@ -277,8 +283,6 @@ def password_reset(request): # bad user? tick the rate limiter counter AUDIT_LOG.info("Bad password_reset user passed in.") - password_reset_email_limiter.tick_request_counter(request) - return JsonResponse({ 'success': True, 'value': render_to_string('registration/password_reset_done.html', {}), @@ -522,6 +526,8 @@ def _get_user_from_email(email): @require_POST +@ratelimit(key=POST_EMAIL_KEY, rate=settings.PASSWORD_RESET_EMAIL_RATE) +@ratelimit(key=REAL_IP_KEY, rate=settings.PASSWORD_RESET_IP_RATE) def password_change_request_handler(request): """Handle password change requests originating from the account page. @@ -546,20 +552,18 @@ def password_change_request_handler(request): POST /account/password """ + user = request.user + # Prefer logged-in user's email + email = user.email if user.is_authenticated else request.POST.get('email') + AUDIT_LOG.info("Password reset initiated for user %s.", email) - password_reset_email_limiter = PasswordResetEmailRateLimiter() - - if password_reset_email_limiter.is_rate_limit_exceeded(request): - AUDIT_LOG.warning("Password reset rate limit exceeded") + if getattr(request, 'limited', False): + AUDIT_LOG.warning("Password reset rate limit exceeded for email %s.", email) return HttpResponse( _("Your previous request is in progress, please try again in a few moments."), status=403 ) - user = request.user - # Prefer logged-in user's email - email = user.email if user.is_authenticated else request.POST.get('email') - if email: try: request_password_change(email, request.is_secure()) @@ -591,7 +595,6 @@ def password_change_request_handler(request): .format(email=email, error=err)) return HttpResponse(_("Some error occured during password change. Please try again"), status=500) - password_reset_email_limiter.tick_request_counter(request) return HttpResponse(status=200) else: return HttpResponseBadRequest(_("No email address provided.")) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_password.py index 5bee8f5269..f1c9666dc6 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_password.py @@ -5,17 +5,21 @@ Tests for user authorization password-related functionality. import json import logging import re +from datetime import datetime, timedelta import ddt from django.conf import settings from django.contrib.auth import get_user_model from django.core import mail +from django.core.cache import cache from django.test import TestCase from django.test.client import RequestFactory from django.urls import reverse +from freezegun import freeze_time from mock import Mock, patch from oauth2_provider.models import AccessToken as dot_access_token from oauth2_provider.models import RefreshToken as dot_refresh_token +from pytz import UTC from testfixtures import LogCapture from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories @@ -110,6 +114,7 @@ class TestPasswordChange(CreateAccountMixin, CacheIsolationTestCase): result = self.client.login(username=self.USERNAME, password=self.OLD_PASSWORD) self.assertTrue(result) mail.outbox = [] + cache.clear() def test_password_change(self): # Request a password change while logged in, simulating @@ -259,11 +264,16 @@ class TestPasswordChange(CreateAccountMixin, CacheIsolationTestCase): # Send the view an email address not tied to any user response = self._change_password(email=self.NEW_EMAIL) self.assertEqual(response.status_code, 200) - logger.check((LOGGER_NAME, 'INFO', 'Invalid password reset attempt')) + + expected_logs = ( + (LOGGER_NAME, 'INFO', 'Password reset initiated for user {}.'.format(self.NEW_EMAIL)), + (LOGGER_NAME, 'INFO', 'Invalid password reset attempt') + ) + logger.check(*expected_logs) def test_password_change_rate_limited(self): """ - Tests that consecutive password reset requests are rate limited. + Tests that password reset requests are rate limited as expected. """ # Log out the user created during test setup, to prevent the view from # selecting the logged-in user's email address over the email provided @@ -273,11 +283,11 @@ class TestPasswordChange(CreateAccountMixin, CacheIsolationTestCase): response = self._change_password(email=self.NEW_EMAIL) self.assertEqual(response.status_code, status) - with patch( - 'util.request_rate_limiter.PasswordResetEmailRateLimiter.is_rate_limit_exceeded', - return_value=False - ): - response = self._change_password(email=self.NEW_EMAIL) + # now reset the time to 1 min from now in future and change the email and + # verify that it will allow another request from same IP + reset_time = datetime.now(UTC) + timedelta(seconds=61) + with freeze_time(reset_time): + response = self._change_password(email=self.OLD_EMAIL) self.assertEqual(response.status_code, 200) @ddt.data( diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py index 8450540a8b..1a83bc0d93 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_reset_password.py @@ -7,6 +7,7 @@ import json import re import unicodedata import unittest +from datetime import datetime, timedelta import ddt from django.conf import settings @@ -22,8 +23,10 @@ from django.test.client import RequestFactory from django.test.utils import override_settings from django.urls import reverse from django.utils.http import int_to_base36 +from freezegun import freeze_time from mock import Mock, patch from oauth2_provider import models as dot_models +from pytz import UTC from six.moves import range from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories @@ -181,32 +184,42 @@ class ResetPasswordTests(EventTestMixin, CacheIsolationTestCase): def test_ratelimited_from_different_ips_with_same_email(self): """ - Test that password reset endpoint allow only one request per minute + Test that password reset endpoint allow only two requests per hour per email address. """ cache.clear() - good_req = self.request_factory.post('/password_reset/', {'email': 'thisdoesnotexist@foo.com'}) - good_req.user = AnonymousUser() - good_resp = password_reset(good_req) - self.assertEqual(good_resp.status_code, 200) + self.request_password_reset(200) + # now reset the time to 1 min from now in future and change the email and + # verify that it will allow another request from same IP + for status in [200, 403]: + reset_time = datetime.now(UTC) + timedelta(seconds=61) + with freeze_time(reset_time): + self.request_password_reset(status) - # change the IP and verify that the rate limiter should kick in and - # give a Forbidden response if the request is for same email address. + # Even changing the IP will not allow more than two requests for same email. new_ip = "8.8.8.8" - self.assertNotEqual(good_req.META.get('REMOTE_ADDR'), new_ip) - - bad_req = self.request_factory.post( - '/password_reset/', - {'email': 'thisdoesnotexist@foo.com'}, - REMOTE_ADDR=new_ip - ) - bad_req.user = AnonymousUser() - bad_resp = password_reset(bad_req) - self.assertEqual(bad_resp.status_code, 403) - self.assertEqual(bad_req.META.get('REMOTE_ADDR'), new_ip) + self.request_password_reset(403, new_ip=new_ip) cache.clear() + def request_password_reset(self, status, new_ip=None): + extra_args = {} + if new_ip: + extra_args = {'REMOTE_ADDR': new_ip} + + reset_request = self.request_factory.post( + '/password_reset/', + {'email': 'thisdoesnotexist@foo.com'}, + **extra_args + ) + + if new_ip: + self.assertEqual(reset_request.META.get('REMOTE_ADDR'), new_ip) + + reset_request.user = AnonymousUser() + response = password_reset(reset_request) + self.assertEqual(response.status_code, status) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', "Test only valid in LMS") @ddt.data(('plain_text', "You're receiving this e-mail because you requested a password reset"), ('html', "You're receiving this e-mail because you requested a password reset")) From 4b078a056dbee7219d7d92cb5acae44449da805b Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Wed, 8 Jul 2020 08:06:07 -0400 Subject: [PATCH 2/2] Updating Python Requirements (#24419) --- requirements/edx/base.txt | 10 +++++----- requirements/edx/coverage.txt | 2 +- requirements/edx/development.txt | 16 ++++++++-------- requirements/edx/testing.txt | 14 +++++++------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 29325fae43..4976bd7383 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -53,7 +53,7 @@ django-appconf==1.0.4 # via -r requirements/edx/base.in, django-statici18n django-celery==3.3.1 # via -r requirements/edx/base.in, edx-bulk-grades django-classy-tags==1.0.0 # via django-sekizai django-config-models==2.0.2 # via -r requirements/edx/base.in, edx-enterprise -django-cookies-samesite==0.6.4 # via -r requirements/edx/base.in +django-cookies-samesite==0.6.6 # via -r requirements/edx/base.in django-cors-headers==2.5.3 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in django-countries==5.5 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in, edx-enterprise django-crum==0.7.6 # via -r requirements/edx/base.in, edx-enterprise, edx-proctoring, edx-rbac, super-csv @@ -69,7 +69,7 @@ django-mysql==3.7.1 # via -r requirements/edx/base.in django-oauth-toolkit==1.3.2 # via -r requirements/edx/base.in django-object-actions==2.0.0 # via edx-enterprise django-pipeline==1.7.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in -django-pyfs==2.1 # via -r requirements/edx/base.in +django-pyfs==2.2 # via -r requirements/edx/base.in django-ratelimit-backend==2.0 # via -r requirements/edx/base.in django-require==1.0.11 # via -r requirements/edx/base.in django-sekizai==1.1.0 # via -r requirements/edx/base.in, django-wiki @@ -158,7 +158,7 @@ git+https://github.com/edx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752 mongoengine==0.20.0 # via -r requirements/edx/base.in more-itertools==8.4.0 # via -r requirements/edx/paver.txt, zipp mpmath==1.1.0 # via sympy -mysqlclient==2.0.0 # via -r requirements/edx/base.in +mysqlclient==2.0.1 # via -r requirements/edx/base.in newrelic==5.14.1.144 # via -r requirements/edx/base.in, edx-django-utils nltk==3.5 # via -r requirements/edx/../edx-sandbox/shared.txt, chem nodeenv==1.4.0 # via -r requirements/edx/base.in @@ -179,7 +179,7 @@ polib==1.1.0 # via edx-i18n-tools psutil==1.2.1 # via -r requirements/edx/paver.txt, edx-django-utils py2neo==3.1.2 # via -r requirements/edx/base.in pycontracts==1.8.12 # via -r requirements/edx/base.in, edx-user-state-client -pycountry==20.7.2 # via -r requirements/edx/base.in +pycountry==20.7.3 # via -r requirements/edx/base.in pycparser==2.20 # via -r requirements/edx/../edx-sandbox/shared.txt, cffi pycryptodome==3.9.8 # via lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/base.in, edx-proctoring, pyjwkest @@ -217,7 +217,7 @@ scipy==1.4.1 # via -c requirements/edx/../constraints.txt, chem, op semantic-version==2.8.5 # via edx-drf-extensions shapely==1.7.0 # via -r requirements/edx/base.in simplejson==3.17.0 # via -r requirements/edx/base.in, sailthru-client, super-csv, xblock-utils -six==1.15.0 # via -r requirements/edx/../edx-sandbox/shared.txt, -r requirements/edx/base.in, -r requirements/edx/paver.txt, analytics-python, bleach, chem, crowdsourcehinter-xblock, cryptography, django-classy-tags, django-countries, django-pyfs, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, event-tracking, fs, fs-s3fs, help-tokens, html5lib, isodate, libsass, mock, openedx-calc, packaging, paver, pycontracts, pyjwkest, python-dateutil, python-memcached, python-swiftclient, social-auth-app-django, social-auth-core, stevedore, xblock +six==1.15.0 # via -r requirements/edx/../edx-sandbox/shared.txt, -r requirements/edx/base.in, -r requirements/edx/paver.txt, analytics-python, bleach, chem, crowdsourcehinter-xblock, cryptography, django-classy-tags, django-countries, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, event-tracking, fs, fs-s3fs, help-tokens, html5lib, isodate, libsass, mock, openedx-calc, packaging, paver, pycontracts, pyjwkest, python-dateutil, python-memcached, python-swiftclient, social-auth-app-django, social-auth-core, stevedore, xblock slumber==0.7.1 # via edx-bulk-grades, edx-enterprise, edx-rest-api-client social-auth-app-django==4.0.0 # via -r requirements/edx/base.in social-auth-core==3.3.3 # via -r requirements/edx/base.in, social-auth-app-django diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt index 0f257b589f..2276c00f93 100644 --- a/requirements/edx/coverage.txt +++ b/requirements/edx/coverage.txt @@ -4,7 +4,7 @@ # # make upgrade # -coverage==5.1 # via -r requirements/edx/coverage.in +coverage==5.2 # via -r requirements/edx/coverage.in diff-cover==3.0.1 # via -r requirements/edx/coverage.in importlib-metadata==1.7.0 # via inflect, pluggy inflect==3.0.2 # via -c requirements/edx/../constraints.txt, jinja2-pluralize diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index e17ce22ec6..a70acee5a1 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -49,7 +49,7 @@ colorama==0.4.1 # via -r requirements/edx/testing.txt, radon contextlib2==0.6.0.post1 # via -r requirements/edx/testing.txt coreapi==2.3.3 # via -r requirements/edx/testing.txt, drf-yasg coreschema==0.0.4 # via -r requirements/edx/testing.txt, coreapi, drf-yasg -coverage==5.1 # via -r requirements/edx/testing.txt, pytest-cov +coverage==5.2 # via -r requirements/edx/testing.txt, pytest-cov git+https://github.com/nedbat/coverage_pytest_plugin.git@29de030251471e200ff255eb9e549218cd60e872#egg=coverage_pytest_plugin==0.0 # via -r requirements/edx/testing.txt crowdsourcehinter-xblock==0.6 # via -r requirements/edx/testing.txt cryptography==2.9.2 # via -r requirements/edx/testing.txt, django-fernet-fields, edx-enterprise, social-auth-core @@ -64,7 +64,7 @@ django-appconf==1.0.4 # via -r requirements/edx/testing.txt, django-statici1 django-celery==3.3.1 # via -r requirements/edx/testing.txt, edx-bulk-grades django-classy-tags==1.0.0 # via -r requirements/edx/testing.txt, django-sekizai django-config-models==2.0.2 # via -r requirements/edx/testing.txt, edx-enterprise -django-cookies-samesite==0.6.4 # via -r requirements/edx/testing.txt +django-cookies-samesite==0.6.6 # via -r requirements/edx/testing.txt django-cors-headers==2.5.3 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt django-countries==5.5 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt, edx-enterprise django-crum==0.7.6 # via -r requirements/edx/testing.txt, edx-enterprise, edx-proctoring, edx-rbac, super-csv @@ -81,7 +81,7 @@ django-mysql==3.7.1 # via -r requirements/edx/testing.txt django-oauth-toolkit==1.3.2 # via -r requirements/edx/testing.txt django-object-actions==2.0.0 # via -r requirements/edx/testing.txt, edx-enterprise django-pipeline==1.7.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt -django-pyfs==2.1 # via -r requirements/edx/testing.txt +django-pyfs==2.2 # via -r requirements/edx/testing.txt django-ratelimit-backend==2.0 # via -r requirements/edx/testing.txt django-require==1.0.11 # via -r requirements/edx/testing.txt django-sekizai==1.1.0 # via -r requirements/edx/testing.txt, django-wiki @@ -193,7 +193,7 @@ git+https://github.com/edx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752 mongoengine==0.20.0 # via -r requirements/edx/testing.txt more-itertools==8.4.0 # via -r requirements/edx/testing.txt, pytest, zipp mpmath==1.1.0 # via -r requirements/edx/testing.txt, sympy -mysqlclient==2.0.0 # via -r requirements/edx/testing.txt +mysqlclient==2.0.1 # via -r requirements/edx/testing.txt newrelic==5.14.1.144 # via -r requirements/edx/testing.txt, edx-django-utils nltk==3.5 # via -r requirements/edx/testing.txt, chem nodeenv==1.4.0 # via -r requirements/edx/testing.txt @@ -219,7 +219,7 @@ py2neo==3.1.2 # via -r requirements/edx/testing.txt py==1.9.0 # via -r requirements/edx/testing.txt, pytest, tox pycodestyle==2.6.0 # via -r requirements/edx/testing.txt, flake8 pycontracts==1.8.12 # via -r requirements/edx/testing.txt, edx-user-state-client -pycountry==20.7.2 # via -r requirements/edx/testing.txt +pycountry==20.7.3 # via -r requirements/edx/testing.txt pycparser==2.20 # via -r requirements/edx/testing.txt, cffi pycryptodome==3.9.8 # via -r requirements/edx/testing.txt, lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/testing.txt, edx-proctoring, pyjwkest @@ -277,7 +277,7 @@ semantic-version==2.8.5 # via -r requirements/edx/testing.txt, edx-drf-extensi shapely==1.7.0 # via -r requirements/edx/testing.txt simplejson==3.17.0 # via -r requirements/edx/testing.txt, sailthru-client, super-csv, xblock-utils singledispatch==3.4.0.3 # via -r requirements/edx/testing.txt -six==1.15.0 # via -r requirements/edx/pip-tools.txt, -r requirements/edx/testing.txt, analytics-python, astroid, bleach, bok-choy, chem, crowdsourcehinter-xblock, cryptography, diff-cover, django-classy-tags, django-countries, django-pyfs, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-lint, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, edx-sphinx-theme, event-tracking, freezegun, fs, fs-s3fs, help-tokens, html5lib, httpretty, isodate, jsonschema, libsass, mando, mock, openedx-calc, packaging, pathlib2, paver, pip-tools, pycontracts, pyjwkest, pytest-xdist, python-dateutil, python-memcached, python-swiftclient, singledispatch, social-auth-app-django, social-auth-core, sphinxcontrib-httpdomain, stevedore, tox, transifex-client, virtualenv, xblock +six==1.15.0 # via -r requirements/edx/pip-tools.txt, -r requirements/edx/testing.txt, analytics-python, astroid, bleach, bok-choy, chem, crowdsourcehinter-xblock, cryptography, diff-cover, django-classy-tags, django-countries, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-lint, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, edx-sphinx-theme, event-tracking, freezegun, fs, fs-s3fs, help-tokens, html5lib, httpretty, isodate, jsonschema, libsass, mando, mock, openedx-calc, packaging, pathlib2, paver, pip-tools, pycontracts, pyjwkest, pytest-xdist, python-dateutil, python-memcached, python-swiftclient, singledispatch, social-auth-app-django, social-auth-core, sphinxcontrib-httpdomain, stevedore, tox, transifex-client, virtualenv, xblock slumber==0.7.1 # via -r requirements/edx/testing.txt, edx-bulk-grades, edx-enterprise, edx-rest-api-client smmap==3.0.4 # via -r requirements/edx/testing.txt, gitdb snowballstemmer==2.0.0 # via sphinx @@ -286,7 +286,7 @@ social-auth-core==3.3.3 # via -r requirements/edx/testing.txt, social-auth-app sorl-thumbnail==12.6.3 # via -r requirements/edx/testing.txt sortedcontainers==2.2.2 # via -r requirements/edx/testing.txt, pdfminer.six soupsieve==2.0.1 # via -r requirements/edx/testing.txt, beautifulsoup4 -sphinx==3.1.1 # via edx-sphinx-theme, sphinxcontrib-httpdomain +sphinx==3.1.2 # via edx-sphinx-theme, sphinxcontrib-httpdomain sphinxcontrib-applehelp==1.0.2 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==1.0.3 # via sphinx @@ -314,7 +314,7 @@ unidiff==0.6.0 # via -r requirements/edx/testing.txt, coverage-pytest uritemplate==3.0.1 # via -r requirements/edx/testing.txt, coreapi, drf-yasg urllib3==1.25.9 # via -r requirements/edx/testing.txt, elasticsearch, geoip2, requests, selenium, transifex-client user-util==0.2 # via -r requirements/edx/testing.txt -virtualenv==20.0.25 # via -r requirements/edx/testing.txt, tox +virtualenv==20.0.26 # via -r requirements/edx/testing.txt, tox voluptuous==0.11.7 # via -r requirements/edx/testing.txt, ora2 vulture==1.5 # via -r requirements/edx/development.in watchdog==0.10.3 # via -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index fa92bf1420..4a728499d6 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -48,7 +48,7 @@ colorama==0.4.1 # via radon contextlib2==0.6.0.post1 # via -r requirements/edx/base.txt coreapi==2.3.3 # via -r requirements/edx/base.txt, drf-yasg coreschema==0.0.4 # via -r requirements/edx/base.txt, coreapi, drf-yasg -coverage==5.1 # via -r requirements/edx/coverage.txt, pytest-cov +coverage==5.2 # via -r requirements/edx/coverage.txt, pytest-cov git+https://github.com/nedbat/coverage_pytest_plugin.git@29de030251471e200ff255eb9e549218cd60e872#egg=coverage_pytest_plugin==0.0 # via -r requirements/edx/testing.in crowdsourcehinter-xblock==0.6 # via -r requirements/edx/base.txt cryptography==2.9.2 # via -r requirements/edx/base.txt, django-fernet-fields, edx-enterprise, social-auth-core @@ -63,7 +63,7 @@ django-appconf==1.0.4 # via -r requirements/edx/base.txt, django-statici18n django-celery==3.3.1 # via -r requirements/edx/base.txt, edx-bulk-grades django-classy-tags==1.0.0 # via -r requirements/edx/base.txt, django-sekizai django-config-models==2.0.2 # via -r requirements/edx/base.txt, edx-enterprise -django-cookies-samesite==0.6.4 # via -r requirements/edx/base.txt +django-cookies-samesite==0.6.6 # via -r requirements/edx/base.txt django-cors-headers==2.5.3 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt django-countries==5.5 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt, edx-enterprise django-crum==0.7.6 # via -r requirements/edx/base.txt, edx-enterprise, edx-proctoring, edx-rbac, super-csv @@ -79,7 +79,7 @@ django-mysql==3.7.1 # via -r requirements/edx/base.txt django-oauth-toolkit==1.3.2 # via -r requirements/edx/base.txt django-object-actions==2.0.0 # via -r requirements/edx/base.txt, edx-enterprise django-pipeline==1.7.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt -django-pyfs==2.1 # via -r requirements/edx/base.txt +django-pyfs==2.2 # via -r requirements/edx/base.txt django-ratelimit-backend==2.0 # via -r requirements/edx/base.txt django-require==1.0.11 # via -r requirements/edx/base.txt django-sekizai==1.1.0 # via -r requirements/edx/base.txt, django-wiki @@ -185,7 +185,7 @@ git+https://github.com/edx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752 mongoengine==0.20.0 # via -r requirements/edx/base.txt more-itertools==8.4.0 # via -r requirements/edx/base.txt, -r requirements/edx/coverage.txt, pytest, zipp mpmath==1.1.0 # via -r requirements/edx/base.txt, sympy -mysqlclient==2.0.0 # via -r requirements/edx/base.txt +mysqlclient==2.0.1 # via -r requirements/edx/base.txt newrelic==5.14.1.144 # via -r requirements/edx/base.txt, edx-django-utils nltk==3.5 # via -r requirements/edx/base.txt, chem nodeenv==1.4.0 # via -r requirements/edx/base.txt @@ -210,7 +210,7 @@ py2neo==3.1.2 # via -r requirements/edx/base.txt py==1.9.0 # via pytest, tox pycodestyle==2.6.0 # via -r requirements/edx/testing.in, flake8 pycontracts==1.8.12 # via -r requirements/edx/base.txt, edx-user-state-client -pycountry==20.7.2 # via -r requirements/edx/base.txt +pycountry==20.7.3 # via -r requirements/edx/base.txt pycparser==2.20 # via -r requirements/edx/base.txt, cffi pycryptodome==3.9.8 # via -r requirements/edx/base.txt, lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/base.txt, edx-proctoring, pyjwkest @@ -266,7 +266,7 @@ semantic-version==2.8.5 # via -r requirements/edx/base.txt, edx-drf-extensions shapely==1.7.0 # via -r requirements/edx/base.txt simplejson==3.17.0 # via -r requirements/edx/base.txt, sailthru-client, super-csv, xblock-utils singledispatch==3.4.0.3 # via -r requirements/edx/testing.in -six==1.15.0 # via -r requirements/edx/base.txt, -r requirements/edx/coverage.txt, analytics-python, astroid, bleach, bok-choy, chem, crowdsourcehinter-xblock, cryptography, diff-cover, django-classy-tags, django-countries, django-pyfs, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-lint, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, event-tracking, freezegun, fs, fs-s3fs, help-tokens, html5lib, httpretty, isodate, libsass, mando, mock, openedx-calc, packaging, pathlib2, paver, pycontracts, pyjwkest, pytest-xdist, python-dateutil, python-memcached, python-swiftclient, singledispatch, social-auth-app-django, social-auth-core, stevedore, tox, transifex-client, virtualenv, xblock +six==1.15.0 # via -r requirements/edx/base.txt, -r requirements/edx/coverage.txt, analytics-python, astroid, bleach, bok-choy, chem, crowdsourcehinter-xblock, cryptography, diff-cover, django-classy-tags, django-countries, django-sekizai, django-simple-history, django-statici18n, drf-yasg, edx-ace, edx-bulk-grades, edx-ccx-keys, edx-django-release-util, edx-drf-extensions, edx-enterprise, edx-i18n-tools, edx-lint, edx-milestones, edx-opaque-keys, edx-rbac, edx-search, event-tracking, freezegun, fs, fs-s3fs, help-tokens, html5lib, httpretty, isodate, libsass, mando, mock, openedx-calc, packaging, pathlib2, paver, pycontracts, pyjwkest, pytest-xdist, python-dateutil, python-memcached, python-swiftclient, singledispatch, social-auth-app-django, social-auth-core, stevedore, tox, transifex-client, virtualenv, xblock slumber==0.7.1 # via -r requirements/edx/base.txt, edx-bulk-grades, edx-enterprise, edx-rest-api-client smmap==3.0.4 # via gitdb social-auth-app-django==4.0.0 # via -r requirements/edx/base.txt @@ -293,7 +293,7 @@ unidiff==0.6.0 # via -r requirements/edx/testing.in, coverage-pytest- uritemplate==3.0.1 # via -r requirements/edx/base.txt, coreapi, drf-yasg urllib3==1.25.9 # via -r requirements/edx/base.txt, elasticsearch, geoip2, requests, selenium, transifex-client user-util==0.2 # via -r requirements/edx/base.txt -virtualenv==20.0.25 # via tox +virtualenv==20.0.26 # via tox voluptuous==0.11.7 # via -r requirements/edx/base.txt, ora2 watchdog==0.10.3 # via -r requirements/edx/base.txt wcwidth==0.2.5 # via pytest