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
33ae93ec
Commit
33ae93ec
authored
Aug 31, 2016
by
Clinton Blackburn
Committed by
GitHub
Aug 31, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #13321 from edx/clintonb/userinfo-cookie
Updated User Info Cookie
parents
7ba9048a
33636db9
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
164 additions
and
62 deletions
+164
-62
common/djangoapps/student/cookies.py
+75
-43
common/djangoapps/student/tests/factories.py
+16
-12
common/djangoapps/student/tests/test_cookies.py
+66
-0
common/djangoapps/student/tests/test_login.py
+2
-5
common/djangoapps/student/views.py
+4
-2
requirements/edx/base.txt
+1
-0
No files found.
common/djangoapps/student/cookies.py
View file @
33ae93ec
...
@@ -2,18 +2,43 @@
...
@@ -2,18 +2,43 @@
Utility functions for setting "logged in" cookies used by subdomains.
Utility functions for setting "logged in" cookies used by subdomains.
"""
"""
import
time
import
json
import
json
import
time
from
django.dispatch
import
Signal
import
six
import
urllib
from
django.utils.http
import
cookie_date
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
,
NoReverseMatch
from
django.core.urlresolvers
import
reverse
,
NoReverseMatch
from
django.dispatch
import
Signal
from
django.utils.http
import
cookie_date
from
student.models
import
CourseEnrollment
CREATE_LOGON_COOKIE
=
Signal
(
providing_args
=
[
"user"
,
"response"
])
CREATE_LOGON_COOKIE
=
Signal
(
providing_args
=
[
"user"
,
"response"
])
def
_get_cookie_settings
(
request
):
""" Returns the common cookie settings (e.g. expiration time). """
if
request
.
session
.
get_expire_at_browser_close
():
max_age
=
None
expires
=
None
else
:
max_age
=
request
.
session
.
get_expiry_age
()
expires_time
=
time
.
time
()
+
max_age
expires
=
cookie_date
(
expires_time
)
cookie_settings
=
{
'max_age'
:
max_age
,
'expires'
:
expires
,
'domain'
:
settings
.
SESSION_COOKIE_DOMAIN
,
'path'
:
'/'
,
'httponly'
:
None
,
}
return
cookie_settings
def
set_logged_in_cookies
(
request
,
response
,
user
):
def
set_logged_in_cookies
(
request
,
response
,
user
):
"""
"""
Set cookies indicating that the user is logged in.
Set cookies indicating that the user is logged in.
...
@@ -49,21 +74,7 @@ def set_logged_in_cookies(request, response, user):
...
@@ -49,21 +74,7 @@ def set_logged_in_cookies(request, response, user):
HttpResponse
HttpResponse
"""
"""
if
request
.
session
.
get_expire_at_browser_close
():
cookie_settings
=
_get_cookie_settings
(
request
)
max_age
=
None
expires
=
None
else
:
max_age
=
request
.
session
.
get_expiry_age
()
expires_time
=
time
.
time
()
+
max_age
expires
=
cookie_date
(
expires_time
)
cookie_settings
=
{
'max_age'
:
max_age
,
'expires'
:
expires
,
'domain'
:
settings
.
SESSION_COOKIE_DOMAIN
,
'path'
:
'/'
,
'httponly'
:
None
,
}
# Backwards compatibility: set the cookie indicating that the user
# Backwards compatibility: set the cookie indicating that the user
# is logged in. This is just a boolean value, so it's not very useful.
# is logged in. This is just a boolean value, so it's not very useful.
...
@@ -76,6 +87,41 @@ def set_logged_in_cookies(request, response, user):
...
@@ -76,6 +87,41 @@ def set_logged_in_cookies(request, response, user):
**
cookie_settings
**
cookie_settings
)
)
set_user_info_cookie
(
response
,
request
,
user
)
# give signal receivers a chance to add cookies
CREATE_LOGON_COOKIE
.
send
(
sender
=
None
,
user
=
user
,
response
=
response
)
return
response
def
set_user_info_cookie
(
response
,
request
,
user
):
""" Sets the user info cookie on the response. """
cookie_settings
=
_get_cookie_settings
(
request
)
# In production, TLS should be enabled so that this cookie is encrypted
# when we send it. We also need to set "secure" to True so that the browser
# will transmit it only over secure connections.
#
# In non-production environments (acceptance tests, devstack, and sandboxes),
# we still want to set this cookie. However, we do NOT want to set it to "secure"
# because the browser won't send it back to us. This can cause an infinite redirect
# loop in the third-party auth flow, which calls `is_logged_in_cookie_set` to determine
# whether it needs to set the cookie or continue to the next pipeline stage.
user_info_cookie_is_secure
=
request
.
is_secure
()
user_info
=
get_user_info_cookie_data
(
request
,
user
)
response
.
set_cookie
(
settings
.
EDXMKTG_USER_INFO_COOKIE_NAME
.
encode
(
'utf-8'
),
urllib
.
quote
(
json
.
dumps
(
user_info
)),
secure
=
user_info_cookie_is_secure
,
**
cookie_settings
)
def
get_user_info_cookie_data
(
request
,
user
):
""" Returns information that wil populate the user info cookie. """
# Set a cookie with user info. This can be used by external sites
# Set a cookie with user info. This can be used by external sites
# to customize content based on user information. Currently,
# to customize content based on user information. Currently,
# we include information that's used to customize the "account"
# we include information that's used to customize the "account"
...
@@ -94,38 +140,24 @@ def set_logged_in_cookies(request, response, user):
...
@@ -94,38 +140,24 @@ def set_logged_in_cookies(request, response, user):
pass
pass
# Convert relative URL paths to absolute URIs
# Convert relative URL paths to absolute URIs
for
url_name
,
url_path
in
header_urls
.
iteritems
(
):
for
url_name
,
url_path
in
six
.
iteritems
(
header_urls
):
header_urls
[
url_name
]
=
request
.
build_absolute_uri
(
url_path
)
header_urls
[
url_name
]
=
request
.
build_absolute_uri
(
url_path
)
enrollments
=
[]
for
enrollment
in
CourseEnrollment
.
enrollments_for_user
(
user
):
enrollments
.
append
({
'course_run_id'
:
six
.
text_type
(
enrollment
.
course_id
),
'seat_type'
:
enrollment
.
mode
})
user_info
=
{
user_info
=
{
'version'
:
settings
.
EDXMKTG_USER_INFO_COOKIE_VERSION
,
'version'
:
settings
.
EDXMKTG_USER_INFO_COOKIE_VERSION
,
'username'
:
user
.
username
,
'username'
:
user
.
username
,
'email'
:
user
.
email
,
'header_urls'
:
header_urls
,
'header_urls'
:
header_urls
,
'enrollments'
:
enrollments
,
}
}
# In production, TLS should be enabled so that this cookie is encrypted
return
user_info
# when we send it. We also need to set "secure" to True so that the browser
# will transmit it only over secure connections.
#
# In non-production environments (acceptance tests, devstack, and sandboxes),
# we still want to set this cookie. However, we do NOT want to set it to "secure"
# because the browser won't send it back to us. This can cause an infinite redirect
# loop in the third-party auth flow, which calls `is_logged_in_cookie_set` to determine
# whether it needs to set the cookie or continue to the next pipeline stage.
user_info_cookie_is_secure
=
request
.
is_secure
()
response
.
set_cookie
(
settings
.
EDXMKTG_USER_INFO_COOKIE_NAME
.
encode
(
'utf-8'
),
json
.
dumps
(
user_info
),
secure
=
user_info_cookie_is_secure
,
**
cookie_settings
)
# give signal receivers a chance to add cookies
CREATE_LOGON_COOKIE
.
send
(
sender
=
None
,
user
=
user
,
response
=
response
)
return
response
def
delete_logged_in_cookies
(
response
):
def
delete_logged_in_cookies
(
response
):
...
...
common/djangoapps/student/tests/factories.py
View file @
33ae93ec
"""Provides factories for student models."""
"""Provides factories for student models."""
import
random
import
random
from
student.models
import
(
User
,
UserProfile
,
Registration
,
CourseEnrollmentAllowed
,
CourseEnrollment
,
PendingEmailChange
,
UserStanding
,
CourseAccessRole
)
from
course_modes.models
import
CourseMode
from
django.contrib.auth.models
import
Group
,
AnonymousUser
from
datetime
import
datetime
from
datetime
import
datetime
from
uuid
import
uuid4
import
factory
import
factory
from
django.contrib.auth.models
import
Group
,
AnonymousUser
from
factory
import
lazy_attribute
from
factory
import
lazy_attribute
from
factory.django
import
DjangoModelFactory
from
factory.django
import
DjangoModelFactory
from
uuid
import
uuid4
from
pytz
import
UTC
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
pytz
import
UTC
from
course_modes.models
import
CourseMode
from
student.models
import
(
User
,
UserProfile
,
Registration
,
CourseEnrollmentAllowed
,
CourseEnrollment
,
PendingEmailChange
,
UserStanding
,
CourseAccessRole
)
# Factories are self documenting
# Factories are self documenting
# pylint: disable=missing-docstring
# pylint: disable=missing-docstring
USER_PASSWORD
=
'test'
class
GroupFactory
(
DjangoModelFactory
):
class
GroupFactory
(
DjangoModelFactory
):
class
Meta
(
object
):
class
Meta
(
object
):
model
=
Group
model
=
Group
django_get_or_create
=
(
'name'
,
)
django_get_or_create
=
(
'name'
,)
name
=
factory
.
Sequence
(
u'group{0}'
.
format
)
name
=
factory
.
Sequence
(
u'group{0}'
.
format
)
...
@@ -39,7 +42,7 @@ class UserStandingFactory(DjangoModelFactory):
...
@@ -39,7 +42,7 @@ class UserStandingFactory(DjangoModelFactory):
class
UserProfileFactory
(
DjangoModelFactory
):
class
UserProfileFactory
(
DjangoModelFactory
):
class
Meta
(
object
):
class
Meta
(
object
):
model
=
UserProfile
model
=
UserProfile
django_get_or_create
=
(
'user'
,
)
django_get_or_create
=
(
'user'
,)
user
=
None
user
=
None
name
=
factory
.
LazyAttribute
(
u'{0.user.first_name} {0.user.last_name}'
.
format
)
name
=
factory
.
LazyAttribute
(
u'{0.user.first_name} {0.user.last_name}'
.
format
)
...
@@ -83,7 +86,7 @@ class UserFactory(DjangoModelFactory):
...
@@ -83,7 +86,7 @@ class UserFactory(DjangoModelFactory):
username
=
factory
.
Sequence
(
u'robot{0}'
.
format
)
username
=
factory
.
Sequence
(
u'robot{0}'
.
format
)
email
=
factory
.
Sequence
(
u'robot+test+{0}@edx.org'
.
format
)
email
=
factory
.
Sequence
(
u'robot+test+{0}@edx.org'
.
format
)
password
=
factory
.
PostGenerationMethodCall
(
'set_password'
,
'test'
)
password
=
factory
.
PostGenerationMethodCall
(
'set_password'
,
USER_PASSWORD
)
first_name
=
factory
.
Sequence
(
u'Robot{0}'
.
format
)
first_name
=
factory
.
Sequence
(
u'Robot{0}'
.
format
)
last_name
=
'Test'
last_name
=
'Test'
is_staff
=
False
is_staff
=
False
...
@@ -155,6 +158,7 @@ class PendingEmailChangeFactory(DjangoModelFactory):
...
@@ -155,6 +158,7 @@ class PendingEmailChangeFactory(DjangoModelFactory):
new_email: sequence of new+email+{}@edx.org
new_email: sequence of new+email+{}@edx.org
activation_key: sequence of integers, padded to 30 characters
activation_key: sequence of integers, padded to 30 characters
"""
"""
class
Meta
(
object
):
class
Meta
(
object
):
model
=
PendingEmailChange
model
=
PendingEmailChange
...
...
common/djangoapps/student/tests/test_cookies.py
0 → 100644
View file @
33ae93ec
# pylint: disable=missing-docstring
from
__future__
import
unicode_literals
import
six
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.test
import
RequestFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.cookies
import
get_user_info_cookie_data
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
class
CookieTests
(
SharedModuleStoreTestCase
):
@classmethod
def
setUpClass
(
cls
):
super
(
CookieTests
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
()
def
setUp
(
self
):
super
(
CookieTests
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
def
_get_expected_header_urls
(
self
,
request
):
expected_header_urls
=
{
'logout'
:
reverse
(
'logout'
),
}
# Studio (CMS) does not have the URLs below
if
settings
.
ROOT_URLCONF
==
'lms.urls'
:
expected_header_urls
.
update
({
'account_settings'
:
reverse
(
'account_settings'
),
'learner_profile'
:
reverse
(
'learner_profile'
,
kwargs
=
{
'username'
:
self
.
user
.
username
}),
})
# Convert relative URL paths to absolute URIs
for
url_name
,
url_path
in
six
.
iteritems
(
expected_header_urls
):
expected_header_urls
[
url_name
]
=
request
.
build_absolute_uri
(
url_path
)
return
expected_header_urls
def
test_get_user_info_cookie_data
(
self
):
""" Verify the function returns data that """
request
=
RequestFactory
()
.
get
(
'/'
)
request
.
user
=
self
.
user
enrollment_mode
=
'verified'
course_id
=
self
.
course
.
id
# pylint: disable=no-member
CourseEnrollmentFactory
.
create
(
user
=
self
.
user
,
course_id
=
course_id
,
mode
=
enrollment_mode
)
actual
=
get_user_info_cookie_data
(
request
,
self
.
user
)
expected_enrollments
=
[{
'course_run_id'
:
six
.
text_type
(
course_id
),
'seat_type'
:
enrollment_mode
,
}]
expected
=
{
'version'
:
settings
.
EDXMKTG_USER_INFO_COOKIE_VERSION
,
'username'
:
self
.
user
.
username
,
'header_urls'
:
self
.
_get_expected_header_urls
(
request
),
'enrollments'
:
expected_enrollments
,
}
self
.
assertDictEqual
(
actual
,
expected
)
common/djangoapps/student/tests/test_login.py
View file @
33ae93ec
...
@@ -4,6 +4,7 @@ Tests for student activation and login
...
@@ -4,6 +4,7 @@ Tests for student activation and login
import
json
import
json
import
unittest
import
unittest
import
urllib
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.test.client
import
Client
from
django.test.client
import
Client
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
...
@@ -169,14 +170,10 @@ class LoginTest(CacheIsolationTestCase):
...
@@ -169,14 +170,10 @@ class LoginTest(CacheIsolationTestCase):
# Verify the format of the "user info" cookie set on login
# Verify the format of the "user info" cookie set on login
cookie
=
self
.
client
.
cookies
[
settings
.
EDXMKTG_USER_INFO_COOKIE_NAME
]
cookie
=
self
.
client
.
cookies
[
settings
.
EDXMKTG_USER_INFO_COOKIE_NAME
]
user_info
=
json
.
loads
(
cookie
.
value
)
user_info
=
json
.
loads
(
urllib
.
unquote
(
cookie
.
value
)
)
# Check that the version is set
self
.
assertEqual
(
user_info
[
"version"
],
settings
.
EDXMKTG_USER_INFO_COOKIE_VERSION
)
self
.
assertEqual
(
user_info
[
"version"
],
settings
.
EDXMKTG_USER_INFO_COOKIE_VERSION
)
# Check that the username and email are set
self
.
assertEqual
(
user_info
[
"username"
],
self
.
user
.
username
)
self
.
assertEqual
(
user_info
[
"username"
],
self
.
user
.
username
)
self
.
assertEqual
(
user_info
[
"email"
],
self
.
user
.
email
)
# Check that the URLs are absolute
# Check that the URLs are absolute
for
url
in
user_info
[
"header_urls"
]
.
values
():
for
url
in
user_info
[
"header_urls"
]
.
values
():
...
...
common/djangoapps/student/views.py
View file @
33ae93ec
...
@@ -106,7 +106,7 @@ from student.helpers import (
...
@@ -106,7 +106,7 @@ from student.helpers import (
auth_pipeline_urls
,
get_next_url_for_login_page
,
auth_pipeline_urls
,
get_next_url_for_login_page
,
DISABLE_UNENROLL_CERT_STATES
,
DISABLE_UNENROLL_CERT_STATES
,
)
)
from
student.cookies
import
set_logged_in_cookies
,
delete_logged_in_cookies
from
student.cookies
import
set_logged_in_cookies
,
delete_logged_in_cookies
,
set_user_info_cookie
from
student.models
import
anonymous_id_for_user
,
UserAttribute
,
EnrollStatusChange
from
student.models
import
anonymous_id_for_user
,
UserAttribute
,
EnrollStatusChange
from
shoppingcart.models
import
DonationConfiguration
,
CourseRegistrationCode
from
shoppingcart.models
import
DonationConfiguration
,
CourseRegistrationCode
...
@@ -749,7 +749,9 @@ def dashboard(request):
...
@@ -749,7 +749,9 @@ def dashboard(request):
'ecommerce_payment_page'
:
ecommerce_service
.
payment_page_url
(),
'ecommerce_payment_page'
:
ecommerce_service
.
payment_page_url
(),
})
})
return
render_to_response
(
'dashboard.html'
,
context
)
response
=
render_to_response
(
'dashboard.html'
,
context
)
set_user_info_cookie
(
response
,
request
,
user
)
return
response
def
_create_recent_enrollment_message
(
course_enrollments
,
course_modes
):
# pylint: disable=invalid-name
def
_create_recent_enrollment_message
(
course_enrollments
,
course_modes
):
# pylint: disable=invalid-name
...
...
requirements/edx/base.txt
View file @
33ae93ec
...
@@ -94,6 +94,7 @@ requests-oauthlib==0.4.1
...
@@ -94,6 +94,7 @@ requests-oauthlib==0.4.1
scipy==0.14.0
scipy==0.14.0
Shapely==1.2.16
Shapely==1.2.16
singledispatch==3.4.0.2
singledispatch==3.4.0.2
six>=1.10.0,<2.0.0
sorl-thumbnail==12.3
sorl-thumbnail==12.3
sortedcontainers==0.9.2
sortedcontainers==0.9.2
stevedore==1.10.0
stevedore==1.10.0
...
...
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