Merge branch 'master' of github.com:edx/edx-platform into EDUCATOR-5080

This commit is contained in:
Justin Lapierre
2020-07-08 08:53:44 -04:00
12 changed files with 102 additions and 142 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -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)

View File

@@ -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"

View File

@@ -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_'

View File

@@ -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."))

View File

@@ -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(

View File

@@ -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"))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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