Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
243e2660
Commit
243e2660
authored
Mar 18, 2015
by
Brian Wilson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge release to master for 20150317 release.
parent
4860837e
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
357 additions
and
138 deletions
+357
-138
common/djangoapps/cors_csrf/authentication.py
+29
-0
common/djangoapps/cors_csrf/helpers.py
+92
-0
common/djangoapps/cors_csrf/middleware.py
+4
-80
common/djangoapps/cors_csrf/tests/test_authentication.py
+56
-0
common/djangoapps/enrollment/tests/test_views.py
+80
-0
common/djangoapps/enrollment/views.py
+7
-1
common/djangoapps/util/sandboxing.py
+1
-1
common/djangoapps/util/tests/test_sandboxing.py
+6
-2
common/templates/mathjax_include.html
+26
-31
lms/djangoapps/commerce/constants.py
+1
-0
lms/djangoapps/commerce/tests.py
+26
-1
lms/djangoapps/commerce/views.py
+23
-20
lms/envs/aws.py
+5
-0
lms/static/require-config-lms.js
+0
-1
requirements/edx/github.txt
+1
-1
No files found.
common/djangoapps/cors_csrf/authentication.py
0 → 100644
View file @
243e2660
"""Django Rest Framework Authentication classes for cross-domain end-points."""
from
rest_framework
import
authentication
from
cors_csrf.helpers
import
is_cross_domain_request_allowed
,
skip_cross_domain_referer_check
class
SessionAuthenticationCrossDomainCsrf
(
authentication
.
SessionAuthentication
):
"""Session authentication that skips the referer check over secure connections.
Django Rest Framework's `SessionAuthentication` class calls Django's
CSRF middleware implementation directly, which bypasses the middleware
stack.
This version of `SessionAuthentication` performs the same workaround
as `CorsCSRFMiddleware` to skip the referer check for whitelisted
domains over a secure connection. See `cors_csrf.middleware` for
more information.
Since this subclass overrides only the `enforce_csrf()` method,
it can be mixed in with other `SessionAuthentication` subclasses.
"""
def
enforce_csrf
(
self
,
request
):
"""Skip the referer check if the cross-domain request is allowed. """
if
is_cross_domain_request_allowed
(
request
):
with
skip_cross_domain_referer_check
(
request
):
return
super
(
SessionAuthenticationCrossDomainCsrf
,
self
)
.
enforce_csrf
(
request
)
else
:
return
super
(
SessionAuthenticationCrossDomainCsrf
,
self
)
.
enforce_csrf
(
request
)
common/djangoapps/cors_csrf/helpers.py
0 → 100644
View file @
243e2660
"""Helper methods for CORS and CSRF checks. """
import
logging
import
urlparse
import
contextlib
from
django.conf
import
settings
log
=
logging
.
getLogger
(
__name__
)
def
is_cross_domain_request_allowed
(
request
):
"""Check whether we should allow the cross-domain request.
We allow a cross-domain request only if:
1) The request is made securely and the referer has "https://" as the protocol.
2) The referer domain has been whitelisted.
Arguments:
request (HttpRequest)
Returns:
bool
"""
referer
=
request
.
META
.
get
(
'HTTP_REFERER'
)
referer_parts
=
urlparse
.
urlparse
(
referer
)
if
referer
else
None
referer_hostname
=
referer_parts
.
hostname
if
referer_parts
is
not
None
else
None
# Use CORS_ALLOW_INSECURE *only* for development and testing environments;
# it should never be enabled in production.
if
not
getattr
(
settings
,
'CORS_ALLOW_INSECURE'
,
False
):
if
not
request
.
is_secure
():
log
.
debug
(
u"Request is not secure, so we cannot send the CSRF token. "
u"For testing purposes, you can disable this check by setting "
u"`CORS_ALLOW_INSECURE` to True in the settings"
)
return
False
if
not
referer
:
log
.
debug
(
u"No referer provided over a secure connection, so we cannot check the protocol."
)
return
False
if
not
referer_parts
.
scheme
==
'https'
:
log
.
debug
(
u"Referer '
%
s' must have the scheme 'https'"
)
return
False
domain_is_whitelisted
=
(
getattr
(
settings
,
'CORS_ORIGIN_ALLOW_ALL'
,
False
)
or
referer_hostname
in
getattr
(
settings
,
'CORS_ORIGIN_WHITELIST'
,
[])
)
if
not
domain_is_whitelisted
:
if
referer_hostname
is
None
:
# If no referer is specified, we can't check if it's a cross-domain
# request or not.
log
.
debug
(
u"Referrer hostname is `None`, so it is not on the whitelist."
)
elif
referer_hostname
!=
request
.
get_host
():
log
.
info
(
(
u"Domain '
%
s' is not on the cross domain whitelist. "
u"Add the domain to `CORS_ORIGIN_WHITELIST` or set "
u"`CORS_ORIGIN_ALLOW_ALL` to True in the settings."
),
referer_hostname
)
else
:
log
.
debug
(
(
u"Domain '
%
s' is the same as the hostname in the request, "
u"so we are not going to treat it as a cross-domain request."
),
referer_hostname
)
return
False
return
True
@contextlib.contextmanager
def
skip_cross_domain_referer_check
(
request
):
"""Skip the cross-domain CSRF referer check.
Django's CSRF middleware performs the referer check
only when the request is made over a secure connection.
To skip the check, we patch `request.is_secure()` to
False.
"""
is_secure_default
=
request
.
is_secure
request
.
is_secure
=
lambda
:
False
try
:
yield
finally
:
request
.
is_secure
=
is_secure_default
common/djangoapps/cors_csrf/middleware.py
View file @
243e2660
...
@@ -43,80 +43,14 @@ CSRF cookie.
...
@@ -43,80 +43,14 @@ CSRF cookie.
"""
"""
import
logging
import
logging
import
urlparse
from
django.conf
import
settings
from
django.conf
import
settings
from
django.middleware.csrf
import
CsrfViewMiddleware
from
django.middleware.csrf
import
CsrfViewMiddleware
from
django.core.exceptions
import
MiddlewareNotUsed
,
ImproperlyConfigured
from
django.core.exceptions
import
MiddlewareNotUsed
,
ImproperlyConfigured
log
=
logging
.
getLogger
(
__name__
)
from
cors_csrf.helpers
import
is_cross_domain_request_allowed
,
skip_cross_domain_referer_check
def
is_cross_domain_request_allowed
(
request
):
"""Check whether we should allow the cross-domain request.
We allow a cross-domain request only if:
1) The request is made securely and the referer has "https://" as the protocol.
2) The referer domain has been whitelisted.
Arguments:
log
=
logging
.
getLogger
(
__name__
)
request (HttpRequest)
Returns:
bool
"""
referer
=
request
.
META
.
get
(
'HTTP_REFERER'
)
referer_parts
=
urlparse
.
urlparse
(
referer
)
if
referer
else
None
referer_hostname
=
referer_parts
.
hostname
if
referer_parts
is
not
None
else
None
# Use CORS_ALLOW_INSECURE *only* for development and testing environments;
# it should never be enabled in production.
if
not
getattr
(
settings
,
'CORS_ALLOW_INSECURE'
,
False
):
if
not
request
.
is_secure
():
log
.
debug
(
u"Request is not secure, so we cannot send the CSRF token. "
u"For testing purposes, you can disable this check by setting "
u"`CORS_ALLOW_INSECURE` to True in the settings"
)
return
False
if
not
referer
:
log
.
debug
(
u"No referer provided over a secure connection, so we cannot check the protocol."
)
return
False
if
not
referer_parts
.
scheme
==
'https'
:
log
.
debug
(
u"Referer '
%
s' must have the scheme 'https'"
)
return
False
domain_is_whitelisted
=
(
getattr
(
settings
,
'CORS_ORIGIN_ALLOW_ALL'
,
False
)
or
referer_hostname
in
getattr
(
settings
,
'CORS_ORIGIN_WHITELIST'
,
[])
)
if
not
domain_is_whitelisted
:
if
referer_hostname
is
None
:
# If no referer is specified, we can't check if it's a cross-domain
# request or not.
log
.
debug
(
u"Referrer hostname is `None`, so it is not on the whitelist."
)
elif
referer_hostname
!=
request
.
get_host
():
log
.
info
(
(
u"Domain '
%
s' is not on the cross domain whitelist. "
u"Add the domain to `CORS_ORIGIN_WHITELIST` or set "
u"`CORS_ORIGIN_ALLOW_ALL` to True in the settings."
),
referer_hostname
)
else
:
log
.
debug
(
(
u"Domain '
%
s' is the same as the hostname in the request, "
u"so we are not going to treat it as a cross-domain request."
),
referer_hostname
)
return
False
return
True
class
CorsCSRFMiddleware
(
CsrfViewMiddleware
):
class
CorsCSRFMiddleware
(
CsrfViewMiddleware
):
...
@@ -134,18 +68,8 @@ class CorsCSRFMiddleware(CsrfViewMiddleware):
...
@@ -134,18 +68,8 @@ class CorsCSRFMiddleware(CsrfViewMiddleware):
log
.
debug
(
"Could not disable CSRF middleware referer check for cross-domain request."
)
log
.
debug
(
"Could not disable CSRF middleware referer check for cross-domain request."
)
return
return
is_secure_default
=
request
.
is_secure
with
skip_cross_domain_referer_check
(
request
):
return
super
(
CorsCSRFMiddleware
,
self
)
.
process_view
(
request
,
callback
,
callback_args
,
callback_kwargs
)
def
is_secure_patched
():
"""
Avoid triggering the additional CSRF middleware checks on the referrer
"""
return
False
request
.
is_secure
=
is_secure_patched
res
=
super
(
CorsCSRFMiddleware
,
self
)
.
process_view
(
request
,
callback
,
callback_args
,
callback_kwargs
)
request
.
is_secure
=
is_secure_default
return
res
class
CsrfCrossDomainCookieMiddleware
(
object
):
class
CsrfCrossDomainCookieMiddleware
(
object
):
...
...
common/djangoapps/cors_csrf/tests/test_authentication.py
0 → 100644
View file @
243e2660
"""Tests for the CORS CSRF version of Django Rest Framework's SessionAuthentication."""
from
mock
import
patch
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.test.client
import
RequestFactory
from
django.conf
import
settings
from
rest_framework.exceptions
import
AuthenticationFailed
from
cors_csrf.authentication
import
SessionAuthenticationCrossDomainCsrf
class
CrossDomainAuthTest
(
TestCase
):
"""Tests for the CORS CSRF version of Django Rest Framework's SessionAuthentication. """
URL
=
"/dummy_url"
REFERER
=
"https://www.edx.org"
CSRF_TOKEN
=
'abcd1234'
def
setUp
(
self
):
super
(
CrossDomainAuthTest
,
self
)
.
setUp
()
self
.
auth
=
SessionAuthenticationCrossDomainCsrf
()
def
test_perform_csrf_referer_check
(
self
):
request
=
self
.
_fake_request
()
with
self
.
assertRaisesRegexp
(
AuthenticationFailed
,
'CSRF'
):
self
.
auth
.
enforce_csrf
(
request
)
@patch.dict
(
settings
.
FEATURES
,
{
'ENABLE_CORS_HEADERS'
:
True
,
'ENABLE_CROSS_DOMAIN_CSRF_COOKIE'
:
True
})
@override_settings
(
CORS_ORIGIN_WHITELIST
=
[
"www.edx.org"
],
CROSS_DOMAIN_CSRF_COOKIE_NAME
=
"prod-edx-csrftoken"
,
CROSS_DOMAIN_CSRF_COOKIE_DOMAIN
=
".edx.org"
)
def
test_skip_csrf_referer_check
(
self
):
request
=
self
.
_fake_request
()
result
=
self
.
auth
.
enforce_csrf
(
request
)
self
.
assertIs
(
result
,
None
)
self
.
assertTrue
(
request
.
is_secure
())
def
_fake_request
(
self
):
"""Construct a fake request with a referer and CSRF token over a secure connection. """
factory
=
RequestFactory
()
factory
.
cookies
[
settings
.
CSRF_COOKIE_NAME
]
=
self
.
CSRF_TOKEN
request
=
factory
.
post
(
self
.
URL
,
HTTP_REFERER
=
self
.
REFERER
,
HTTP_X_CSRFTOKEN
=
self
.
CSRF_TOKEN
)
request
.
is_secure
=
lambda
:
True
return
request
common/djangoapps/enrollment/tests/test_views.py
View file @
243e2660
...
@@ -6,6 +6,8 @@ import json
...
@@ -6,6 +6,8 @@ import json
import
unittest
import
unittest
from
mock
import
patch
from
mock
import
patch
from
django.test
import
Client
from
django.core.handlers.wsgi
import
WSGIRequest
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
rest_framework.test
import
APITestCase
from
rest_framework.test
import
APITestCase
from
rest_framework
import
status
from
rest_framework
import
status
...
@@ -504,3 +506,81 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
...
@@ -504,3 +506,81 @@ class EnrollmentEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
url
=
reverse
(
'courseenrollments'
)
url
=
reverse
(
'courseenrollments'
)
resp
=
self
.
client
.
get
(
url
)
resp
=
self
.
client
.
get
(
url
)
return
json
.
loads
(
resp
.
content
)
return
json
.
loads
(
resp
.
content
)
def
cross_domain_config
(
func
):
"""Decorator for configuring a cross-domain request. """
feature_flag_decorator
=
patch
.
dict
(
settings
.
FEATURES
,
{
'ENABLE_CORS_HEADERS'
:
True
,
'ENABLE_CROSS_DOMAIN_CSRF_COOKIE'
:
True
})
settings_decorator
=
override_settings
(
CORS_ORIGIN_WHITELIST
=
[
"www.edx.org"
],
CROSS_DOMAIN_CSRF_COOKIE_NAME
=
"prod-edx-csrftoken"
,
CROSS_DOMAIN_CSRF_COOKIE_DOMAIN
=
".edx.org"
)
is_secure_decorator
=
patch
.
object
(
WSGIRequest
,
'is_secure'
,
return_value
=
True
)
return
feature_flag_decorator
(
settings_decorator
(
is_secure_decorator
(
func
)
)
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentCrossDomainTest
(
ModuleStoreTestCase
):
"""Test cross-domain calls to the enrollment end-points. """
USERNAME
=
"Bob"
EMAIL
=
"bob@example.com"
PASSWORD
=
"edx"
REFERER
=
"https://www.edx.org"
def
setUp
(
self
):
""" Create a course and user, then log in. """
super
(
EnrollmentCrossDomainTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
self
.
user
=
UserFactory
.
create
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
client
=
Client
(
enforce_csrf_checks
=
True
)
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
@cross_domain_config
def
test_cross_domain_change_enrollment
(
self
,
*
args
):
# pylint: disable=unused-argument
csrf_cookie
=
self
.
_get_csrf_cookie
()
resp
=
self
.
_cross_domain_post
(
csrf_cookie
)
# Expect that the request gets through successfully,
# passing the CSRF checks (including the referer check).
self
.
assertEqual
(
resp
.
status_code
,
200
)
@cross_domain_config
def
test_cross_domain_missing_csrf
(
self
,
*
args
):
# pylint: disable=unused-argument
resp
=
self
.
_cross_domain_post
(
'invalid_csrf_token'
)
self
.
assertEqual
(
resp
.
status_code
,
401
)
def
_get_csrf_cookie
(
self
):
"""Retrieve the cross-domain CSRF cookie. """
url
=
reverse
(
'courseenrollment'
,
kwargs
=
{
'course_id'
:
unicode
(
self
.
course
.
id
)
})
resp
=
self
.
client
.
get
(
url
,
HTTP_REFERER
=
self
.
REFERER
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
'prod-edx-csrftoken'
,
resp
.
cookies
)
# pylint: disable=no-member
return
resp
.
cookies
[
'prod-edx-csrftoken'
]
.
value
# pylint: disable=no-member
def
_cross_domain_post
(
self
,
csrf_cookie
):
"""Perform a cross-domain POST request. """
url
=
reverse
(
'courseenrollments'
)
params
=
json
.
dumps
({
'course_details'
:
{
'course_id'
:
unicode
(
self
.
course
.
id
),
},
'user'
:
self
.
user
.
username
})
return
self
.
client
.
post
(
url
,
params
,
content_type
=
'application/json'
,
HTTP_REFERER
=
self
.
REFERER
,
HTTP_X_CSRFTOKEN
=
csrf_cookie
)
common/djangoapps/enrollment/views.py
View file @
243e2660
...
@@ -15,6 +15,7 @@ from rest_framework.throttling import UserRateThrottle
...
@@ -15,6 +15,7 @@ from rest_framework.throttling import UserRateThrottle
from
rest_framework.views
import
APIView
from
rest_framework.views
import
APIView
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
embargo
import
api
as
embargo_api
from
embargo
import
api
as
embargo_api
from
cors_csrf.authentication
import
SessionAuthenticationCrossDomainCsrf
from
cors_csrf.decorators
import
ensure_csrf_cookie_cross_domain
from
cors_csrf.decorators
import
ensure_csrf_cookie_cross_domain
from
util.authentication
import
SessionAuthenticationAllowInactiveUser
,
OAuth2AuthenticationAllowInactiveUser
from
util.authentication
import
SessionAuthenticationAllowInactiveUser
,
OAuth2AuthenticationAllowInactiveUser
from
util.disable_rate_limit
import
can_disable_rate_limit
from
util.disable_rate_limit
import
can_disable_rate_limit
...
@@ -25,6 +26,11 @@ from enrollment.errors import (
...
@@ -25,6 +26,11 @@ from enrollment.errors import (
)
)
class
EnrollmentCrossDomainSessionAuth
(
SessionAuthenticationAllowInactiveUser
,
SessionAuthenticationCrossDomainCsrf
):
"""Session authentication that allows inactive users and cross-domain requests. """
pass
class
ApiKeyPermissionMixIn
(
object
):
class
ApiKeyPermissionMixIn
(
object
):
"""
"""
This mixin is used to provide a convenience function for doing individual permission checks
This mixin is used to provide a convenience function for doing individual permission checks
...
@@ -277,7 +283,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
...
@@ -277,7 +283,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
* user: The ID of the user.
* user: The ID of the user.
"""
"""
authentication_classes
=
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
authentication_classes
=
OAuth2AuthenticationAllowInactiveUser
,
EnrollmentCrossDomainSessionAuth
permission_classes
=
ApiKeyHeaderPermissionIsAuthenticated
,
permission_classes
=
ApiKeyHeaderPermissionIsAuthenticated
,
throttle_classes
=
EnrollmentUserThrottle
,
throttle_classes
=
EnrollmentUserThrottle
,
...
...
common/djangoapps/util/sandboxing.py
View file @
243e2660
...
@@ -25,7 +25,7 @@ def can_execute_unsafe_code(course_id):
...
@@ -25,7 +25,7 @@ def can_execute_unsafe_code(course_id):
# To others using this: the code as-is is brittle and likely to be changed in the future,
# To others using this: the code as-is is brittle and likely to be changed in the future,
# as per the TODO, so please consider carefully before adding more values to COURSES_WITH_UNSAFE_CODE
# as per the TODO, so please consider carefully before adding more values to COURSES_WITH_UNSAFE_CODE
for
regex
in
getattr
(
settings
,
'COURSES_WITH_UNSAFE_CODE'
,
[]):
for
regex
in
getattr
(
settings
,
'COURSES_WITH_UNSAFE_CODE'
,
[]):
if
re
.
match
(
regex
,
course_id
.
to_deprecated_string
(
)):
if
re
.
match
(
regex
,
unicode
(
course_id
)):
return
True
return
True
return
False
return
False
...
...
common/djangoapps/util/tests/test_sandboxing.py
View file @
243e2660
...
@@ -3,6 +3,7 @@ Tests for sandboxing.py in util app
...
@@ -3,6 +3,7 @@ Tests for sandboxing.py in util app
"""
"""
from
django.test
import
TestCase
from
django.test
import
TestCase
from
opaque_keys.edx.locator
import
LibraryLocator
from
util.sandboxing
import
can_execute_unsafe_code
from
util.sandboxing
import
can_execute_unsafe_code
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
...
@@ -12,12 +13,13 @@ class SandboxingTest(TestCase):
...
@@ -12,12 +13,13 @@ class SandboxingTest(TestCase):
"""
"""
Test sandbox whitelisting
Test sandbox whitelisting
"""
"""
@override_settings
(
COURSES_WITH_UNSAFE_CODE
=
[
'edX/full/.*'
])
@override_settings
(
COURSES_WITH_UNSAFE_CODE
=
[
'edX/full/.*'
,
'library:v1-edX+.*'
])
def
test_sandbox_exclusion
(
self
):
def
test_sandbox_exclusion
(
self
):
"""
"""
Test to make sure that a non-match returns false
Test to make sure that a non-match returns false
"""
"""
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'notful'
,
'empty'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'notful'
,
'empty'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
LibraryLocator
(
'edY'
,
'test_bank'
)))
@override_settings
(
COURSES_WITH_UNSAFE_CODE
=
[
'edX/full/.*'
])
@override_settings
(
COURSES_WITH_UNSAFE_CODE
=
[
'edX/full/.*'
])
def
test_sandbox_inclusion
(
self
):
def
test_sandbox_inclusion
(
self
):
...
@@ -26,10 +28,12 @@ class SandboxingTest(TestCase):
...
@@ -26,10 +28,12 @@ class SandboxingTest(TestCase):
"""
"""
self
.
assertTrue
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2012_Fall'
)))
self
.
assertTrue
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2012_Fall'
)))
self
.
assertTrue
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2013_Spring'
)))
self
.
assertTrue
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2013_Spring'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
LibraryLocator
(
'edX'
,
'test_bank'
)))
def
test_courses_with_unsafe_code_default
(
self
):
def
test_course
like
s_with_unsafe_code_default
(
self
):
"""
"""
Test that the default setting for COURSES_WITH_UNSAFE_CODE is an empty setting, e.g. we don't use @override_settings in these tests
Test that the default setting for COURSES_WITH_UNSAFE_CODE is an empty setting, e.g. we don't use @override_settings in these tests
"""
"""
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2012_Fall'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2012_Fall'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2013_Spring'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
SlashSeparatedCourseKey
(
'edX'
,
'full'
,
'2013_Spring'
)))
self
.
assertFalse
(
can_execute_unsafe_code
(
LibraryLocator
(
'edX'
,
'test_bank'
)))
common/templates/mathjax_include.html
View file @
243e2660
...
@@ -6,38 +6,33 @@
...
@@ -6,38 +6,33 @@
## This enables ASCIIMathJAX, and is used by js_textbox
## This enables ASCIIMathJAX, and is used by js_textbox
<
%
def
name=
"mathjaxConfig()"
>
%if mathjax_mode is not Undefined and mathjax_mode == 'wiki':
%if mathjax_mode is not Undefined and mathjax_mode == 'wiki':
<script
type=
"text/x-mathjax-config"
>
MathJax.Hub.Config({
MathJax
.
Hub
.
Config
({
tex2jax: {inlineMath: [ ['$','$'], ["\\(","\\)"]],
tex2jax
:
{
inlineMath
:
[
[
'$'
,
'$'
],
[
"
\\
("
,
"
\\
)"
]],
displayMath: [ ['$$','$$'], ["\\[","\\]"]]}
displayMath
:
[
[
'$$'
,
'$$'
],
[
"
\\
["
,
"
\\
]"
]]}
});
});
%else:
HUB
=
MathJax
.
Hub
MathJax.Hub.Config({
</script>
tex2jax: {
%else:
inlineMath: [
<script
type=
"text/x-mathjax-config"
>
["\\(","\\)"],
MathJax
.
Hub
.
Config
({
['[mathjaxinline]','[/mathjaxinline]']
tex2jax
:
{
],
inlineMath
:
[
displayMath: [
[
"
\\
("
,
"
\\
)"
],
["\\[","\\]"],
[
'[mathjaxinline]'
,
'[/mathjaxinline]'
]
['[mathjax]','[/mathjax]']
],
]
displayMath
:
[
}
[
"
\\
["
,
"
\\
]"
],
});
[
'[mathjax]'
,
'[/mathjax]'
]
%endif
]
MathJax.Hub.Configured();
}
window.HUB = MathJax.Hub;
});
</
%
def>
HUB
=
MathJax
.
Hub
</script>
%endif
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
MathJax extension libraries -->
MathJax extension libraries -->
<script
type=
"text/javascript"
>
<script
type=
"text/javascript"
src=
"https://cdn.mathjax.org/mathjax/2.4-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full"
></script>
;(
function
(
require
)
{
'use strict'
;
require
([
'mathjax'
],
function
()
{
$
{
mathjaxConfig
()}
});
}).
call
(
this
,
require
||
RequireJS
.
require
);
</script>
lms/djangoapps/commerce/constants.py
View file @
243e2660
...
@@ -19,3 +19,4 @@ class Messages(object):
...
@@ -19,3 +19,4 @@ class Messages(object):
NO_SKU_ENROLLED
=
u'The {enrollment_mode} mode for {course_id} does not have a SKU. Enrolling {username} directly.'
NO_SKU_ENROLLED
=
u'The {enrollment_mode} mode for {course_id} does not have a SKU. Enrolling {username} directly.'
ORDER_COMPLETED
=
u'Order {order_number} was completed.'
ORDER_COMPLETED
=
u'Order {order_number} was completed.'
ORDER_INCOMPLETE_ENROLLED
=
u'Order {order_number} was created, but is not yet complete. User was enrolled.'
ORDER_INCOMPLETE_ENROLLED
=
u'Order {order_number} was created, but is not yet complete. User was enrolled.'
NO_HONOR_MODE
=
u'Course {course_id} does not have an honor mode.'
lms/djangoapps/commerce/tests.py
View file @
243e2660
...
@@ -254,7 +254,6 @@ class OrdersViewTests(EnrollmentEventTestMixin, ModuleStoreTestCase):
...
@@ -254,7 +254,6 @@ class OrdersViewTests(EnrollmentEventTestMixin, ModuleStoreTestCase):
response
=
self
.
_post_to_view
()
response
=
self
.
_post_to_view
()
# Validate the response
# Validate the response
self
.
_mock_ecommerce_api
()
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
msg
=
Messages
.
NO_ECOM_API
.
format
(
username
=
self
.
user
.
username
,
course_id
=
self
.
course
.
id
)
msg
=
Messages
.
NO_ECOM_API
.
format
(
username
=
self
.
user
.
username
,
course_id
=
self
.
course
.
id
)
self
.
assertResponseMessage
(
response
,
msg
)
self
.
assertResponseMessage
(
response
,
msg
)
...
@@ -272,3 +271,29 @@ class OrdersViewTests(EnrollmentEventTestMixin, ModuleStoreTestCase):
...
@@ -272,3 +271,29 @@ class OrdersViewTests(EnrollmentEventTestMixin, ModuleStoreTestCase):
course_mode
.
save
()
course_mode
.
save
()
self
.
_test_course_without_sku
()
self
.
_test_course_without_sku
()
def
_test_professional_mode_only
(
self
):
""" Verifies that the view behaves appropriately when the course only has a professional mode. """
CourseMode
.
objects
.
filter
(
course_id
=
self
.
course
.
id
)
.
delete
()
mode
=
'no-id-professional'
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
mode
,
mode_display_name
=
mode
,
sku
=
uuid4
()
.
hex
.
decode
(
'ascii'
))
self
.
_mock_ecommerce_api
()
response
=
self
.
_post_to_view
()
self
.
assertEqual
(
response
.
status_code
,
406
)
msg
=
Messages
.
NO_HONOR_MODE
.
format
(
course_id
=
self
.
course
.
id
)
self
.
assertResponseMessage
(
response
,
msg
)
@httpretty.activate
def
test_course_with_professional_mode_only
(
self
):
""" Verifies that the view behaves appropriately when the course only has a professional mode. """
self
.
_test_professional_mode_only
()
@httpretty.activate
@override_settings
(
ECOMMERCE_API_URL
=
None
,
ECOMMERCE_API_SIGNING_KEY
=
None
)
def
test_no_settings_and_professional_mode_only
(
self
):
"""
Verifies that the view behaves appropriately when the course only has a professional mode and
the E-Commerce API is not configured.
"""
self
.
_test_professional_mode_only
()
lms/djangoapps/commerce/views.py
View file @
243e2660
...
@@ -54,17 +54,16 @@ class OrdersView(APIView):
...
@@ -54,17 +54,16 @@ class OrdersView(APIView):
return
True
,
course_key
,
None
return
True
,
course_key
,
None
def
_get_jwt
(
self
,
user
):
def
_get_jwt
(
self
,
user
,
ecommerce_api_signing_key
):
"""
"""
Returns a JWT object with the specified user's info.
Returns a JWT object with the specified user's info.
Raises AttributeError if settings.ECOMMERCE_API_SIGNING_KEY is not set.
"""
"""
data
=
{
data
=
{
'username'
:
user
.
username
,
'username'
:
user
.
username
,
'email'
:
user
.
email
'email'
:
user
.
email
}
}
return
jwt
.
encode
(
data
,
getattr
(
settings
,
'ECOMMERCE_API_SIGNING_KEY'
)
)
return
jwt
.
encode
(
data
,
ecommerce_api_signing_key
)
def
_enroll
(
self
,
course_key
,
user
):
def
_enroll
(
self
,
course_key
,
user
):
""" Enroll the user in the course. """
""" Enroll the user in the course. """
...
@@ -79,40 +78,44 @@ class OrdersView(APIView):
...
@@ -79,40 +78,44 @@ class OrdersView(APIView):
if
not
valid
:
if
not
valid
:
return
DetailResponse
(
error
,
status
=
HTTP_406_NOT_ACCEPTABLE
)
return
DetailResponse
(
error
,
status
=
HTTP_406_NOT_ACCEPTABLE
)
# Ensure that the course has an honor mode with SKU
honor_mode
=
CourseMode
.
mode_for_course
(
course_key
,
CourseMode
.
HONOR
)
course_id
=
unicode
(
course_key
)
# If there is no honor course mode, this most likely a Prof-Ed course. Return an error so that the JS
# redirects to track selection.
if
not
honor_mode
:
msg
=
Messages
.
NO_HONOR_MODE
.
format
(
course_id
=
course_id
)
return
DetailResponse
(
msg
,
status
=
HTTP_406_NOT_ACCEPTABLE
)
elif
not
honor_mode
.
sku
:
# If there are no course modes with SKUs, enroll the user without contacting the external API.
msg
=
Messages
.
NO_SKU_ENROLLED
.
format
(
enrollment_mode
=
CourseMode
.
HONOR
,
course_id
=
course_id
,
username
=
user
.
username
)
log
.
debug
(
msg
)
self
.
_enroll
(
course_key
,
user
)
return
DetailResponse
(
msg
)
# Ensure that the E-Commerce API is setup properly
# Ensure that the E-Commerce API is setup properly
ecommerce_api_url
=
getattr
(
settings
,
'ECOMMERCE_API_URL'
,
None
)
ecommerce_api_url
=
getattr
(
settings
,
'ECOMMERCE_API_URL'
,
None
)
ecommerce_api_signing_key
=
getattr
(
settings
,
'ECOMMERCE_API_SIGNING_KEY'
,
None
)
ecommerce_api_signing_key
=
getattr
(
settings
,
'ECOMMERCE_API_SIGNING_KEY'
,
None
)
if
not
(
ecommerce_api_url
and
ecommerce_api_signing_key
):
if
not
(
ecommerce_api_url
and
ecommerce_api_signing_key
):
self
.
_enroll
(
course_key
,
user
)
self
.
_enroll
(
course_key
,
user
)
msg
=
Messages
.
NO_ECOM_API
.
format
(
username
=
user
.
username
,
course_id
=
unicode
(
course_key
)
)
msg
=
Messages
.
NO_ECOM_API
.
format
(
username
=
user
.
username
,
course_id
=
course_id
)
log
.
debug
(
msg
)
log
.
debug
(
msg
)
return
DetailResponse
(
msg
)
return
DetailResponse
(
msg
)
# Default to honor mode. In the future we may expand this view to support additional modes.
mode
=
CourseMode
.
DEFAULT_MODE_SLUG
course_modes
=
CourseMode
.
objects
.
filter
(
course_id
=
course_key
,
mode_slug
=
mode
)
\
.
exclude
(
sku__isnull
=
True
)
.
exclude
(
sku__exact
=
''
)
# If there are no course modes with SKUs, enroll the user without contacting the external API.
if
not
course_modes
.
exists
():
msg
=
Messages
.
NO_SKU_ENROLLED
.
format
(
enrollment_mode
=
mode
,
course_id
=
unicode
(
course_key
),
username
=
user
.
username
)
log
.
debug
(
msg
)
self
.
_enroll
(
course_key
,
user
)
return
DetailResponse
(
msg
)
# Contact external API
# Contact external API
headers
=
{
headers
=
{
'Content-Type'
:
'application/json'
,
'Content-Type'
:
'application/json'
,
'Authorization'
:
'JWT {}'
.
format
(
self
.
_get_jwt
(
user
))
'Authorization'
:
'JWT {}'
.
format
(
self
.
_get_jwt
(
user
,
ecommerce_api_signing_key
))
}
}
url
=
'{}/orders/'
.
format
(
ecommerce_api_url
.
strip
(
'/'
))
url
=
'{}/orders/'
.
format
(
ecommerce_api_url
.
strip
(
'/'
))
try
:
try
:
timeout
=
getattr
(
settings
,
'ECOMMERCE_API_TIMEOUT'
,
5
)
timeout
=
getattr
(
settings
,
'ECOMMERCE_API_TIMEOUT'
,
5
)
response
=
requests
.
post
(
url
,
data
=
json
.
dumps
({
'sku'
:
course_modes
[
0
]
.
sku
}),
headers
=
headers
,
response
=
requests
.
post
(
url
,
data
=
json
.
dumps
({
'sku'
:
honor_mode
.
sku
}),
headers
=
headers
,
timeout
=
timeout
)
timeout
=
timeout
)
except
Exception
as
ex
:
# pylint: disable=broad-except
except
Exception
as
ex
:
# pylint: disable=broad-except
log
.
exception
(
'Call to E-Commerce API failed:
%
s.'
,
ex
.
message
)
log
.
exception
(
'Call to E-Commerce API failed:
%
s.'
,
ex
.
message
)
...
@@ -144,7 +147,7 @@ class OrdersView(APIView):
...
@@ -144,7 +147,7 @@ class OrdersView(APIView):
'status'
:
order_status
,
'status'
:
order_status
,
'complete_status'
:
OrderStatus
.
COMPLETE
,
'complete_status'
:
OrderStatus
.
COMPLETE
,
'username'
:
user
.
username
,
'username'
:
user
.
username
,
'course_id'
:
unicode
(
course_key
)
,
'course_id'
:
course_id
,
}
}
log
.
error
(
msg
,
msg_kwargs
)
log
.
error
(
msg
,
msg_kwargs
)
...
...
lms/envs/aws.py
View file @
243e2660
...
@@ -556,3 +556,8 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
...
@@ -556,3 +556,8 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
##### CDN EXPERIMENT/MONITORING FLAGS #####
##### CDN EXPERIMENT/MONITORING FLAGS #####
PERFORMANCE_GRAPHITE_URL
=
ENV_TOKENS
.
get
(
'PERFORMANCE_GRAPHITE_URL'
,
PERFORMANCE_GRAPHITE_URL
)
PERFORMANCE_GRAPHITE_URL
=
ENV_TOKENS
.
get
(
'PERFORMANCE_GRAPHITE_URL'
,
PERFORMANCE_GRAPHITE_URL
)
CDN_VIDEO_URLS
=
ENV_TOKENS
.
get
(
'CDN_VIDEO_URLS'
,
CDN_VIDEO_URLS
)
CDN_VIDEO_URLS
=
ENV_TOKENS
.
get
(
'CDN_VIDEO_URLS'
,
CDN_VIDEO_URLS
)
##### ECOMMERCE API CONFIGURATION SETTINGS #####
ECOMMERCE_API_URL
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_URL'
,
ECOMMERCE_API_URL
)
ECOMMERCE_API_SIGNING_KEY
=
AUTH_TOKENS
.
get
(
'ECOMMERCE_API_SIGNING_KEY'
,
ECOMMERCE_API_SIGNING_KEY
)
ECOMMERCE_API_TIMEOUT
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_TIMEOUT'
,
ECOMMERCE_API_TIMEOUT
)
lms/static/require-config-lms.js
View file @
243e2660
...
@@ -67,7 +67,6 @@
...
@@ -67,7 +67,6 @@
"ova"
:
'js/vendor/ova/ova'
,
"ova"
:
'js/vendor/ova/ova'
,
"catch"
:
'js/vendor/ova/catch/js/catch'
,
"catch"
:
'js/vendor/ova/catch/js/catch'
,
"handlebars"
:
'js/vendor/ova/catch/js/handlebars-1.1.2'
,
"handlebars"
:
'js/vendor/ova/catch/js/handlebars-1.1.2'
,
"mathjax"
:
'https://cdn.mathjax.org/mathjax/2.4-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full'
// end of files needed by OVA
// end of files needed by OVA
},
},
shim
:
{
shim
:
{
...
...
requirements/edx/github.txt
View file @
243e2660
...
@@ -29,7 +29,7 @@ git+https://github.com/mitocw/django-cas.git@60a5b8e5a62e63e0d5d224a87f0b489201a
...
@@ -29,7 +29,7 @@ git+https://github.com/mitocw/django-cas.git@60a5b8e5a62e63e0d5d224a87f0b489201a
-e git+https://github.com/edx/bok-choy.git@d62839324cbea30dda564596f20175f9d5c28516#egg=bok_choy
-e git+https://github.com/edx/bok-choy.git@d62839324cbea30dda564596f20175f9d5c28516#egg=bok_choy
-e git+https://github.com/edx-solutions/django-splash.git@7579d052afcf474ece1239153cffe1c89935bc4f#egg=django-splash
-e git+https://github.com/edx-solutions/django-splash.git@7579d052afcf474ece1239153cffe1c89935bc4f#egg=django-splash
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/edx-ora2.git@
4773573b79bc530f0fe7c8f90a10491e4224dc2d
#egg=edx-ora2
-e git+https://github.com/edx/edx-ora2.git@
release-2015-03-16T17.59
#egg=edx-ora2
-e git+https://github.com/edx/edx-submissions.git@8fb070d2a3087dd7656d27022e550d12e3b85ba3#egg=edx-submissions
-e git+https://github.com/edx/edx-submissions.git@8fb070d2a3087dd7656d27022e550d12e3b85ba3#egg=edx-submissions
-e git+https://github.com/edx/opaque-keys.git@1254ed4d615a428591850656f39f26509b86d30a#egg=opaque-keys
-e git+https://github.com/edx/opaque-keys.git@1254ed4d615a428591850656f39f26509b86d30a#egg=opaque-keys
-e git+https://github.com/edx/ease.git@97de68448e5495385ba043d3091f570a699d5b5f#egg=ease
-e git+https://github.com/edx/ease.git@97de68448e5495385ba043d3091f570a699d5b5f#egg=ease
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment