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
289469dd
Commit
289469dd
authored
Mar 20, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7353 from edx/mobile/third-party-oauth-reg
Mobile registration with Google/FB
parents
8a1877f0
dfcef9dd
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
401 additions
and
199 deletions
+401
-199
common/djangoapps/oauth_exchange/forms.py
+1
-1
common/djangoapps/oauth_exchange/tests/test_forms.py
+4
-7
common/djangoapps/oauth_exchange/tests/test_views.py
+4
-7
common/djangoapps/oauth_exchange/tests/utils.py
+18
-48
common/djangoapps/student/tests/test_login.py
+15
-37
common/djangoapps/student/views.py
+44
-6
common/djangoapps/third_party_auth/pipeline.py
+72
-81
common/djangoapps/third_party_auth/settings.py
+1
-0
common/djangoapps/third_party_auth/tests/test_change_enrollment.py
+2
-2
common/djangoapps/third_party_auth/tests/utils.py
+79
-0
lms/urls.py
+2
-0
openedx/core/djangoapps/user_api/tests/test_views.py
+159
-10
No files found.
common/djangoapps/oauth_exchange/forms.py
View file @
289469dd
...
...
@@ -57,7 +57,7 @@ class AccessTokenExchangeForm(ScopeMixin, OAuthForm):
}
)
self
.
request
.
session
[
pipeline
.
AUTH_ENTRY_KEY
]
=
pipeline
.
AUTH_ENTRY_API
self
.
request
.
session
[
pipeline
.
AUTH_ENTRY_KEY
]
=
pipeline
.
AUTH_ENTRY_
LOGIN_
API
client_id
=
self
.
cleaned_data
[
"client_id"
]
try
:
...
...
common/djangoapps/oauth_exchange/tests/test_forms.py
View file @
289469dd
...
...
@@ -12,11 +12,8 @@ from provider import scope
import
social.apps.django_app.utils
as
social_utils
from
oauth_exchange.forms
import
AccessTokenExchangeForm
from
oauth_exchange.tests.utils
import
(
AccessTokenExchangeTestMixin
,
AccessTokenExchangeMixinFacebook
,
AccessTokenExchangeMixinGoogle
)
from
oauth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
third_party_auth.tests.utils
import
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
class
AccessTokenExchangeFormTest
(
AccessTokenExchangeTestMixin
):
...
...
@@ -50,7 +47,7 @@ class AccessTokenExchangeFormTest(AccessTokenExchangeTestMixin):
@httpretty.activate
class
AccessTokenExchangeFormTestFacebook
(
AccessTokenExchangeFormTest
,
AccessTokenExchange
MixinFacebook
,
ThirdPartyOAuthTest
MixinFacebook
,
TestCase
):
"""
...
...
@@ -64,7 +61,7 @@ class AccessTokenExchangeFormTestFacebook(
@httpretty.activate
class
AccessTokenExchangeFormTestGoogle
(
AccessTokenExchangeFormTest
,
AccessTokenExchange
MixinGoogle
,
ThirdPartyOAuthTest
MixinGoogle
,
TestCase
):
"""
...
...
common/djangoapps/oauth_exchange/tests/test_views.py
View file @
289469dd
...
...
@@ -14,11 +14,8 @@ import provider.constants
from
provider
import
scope
from
provider.oauth2.models
import
AccessToken
from
oauth_exchange.tests.utils
import
(
AccessTokenExchangeTestMixin
,
AccessTokenExchangeMixinFacebook
,
AccessTokenExchangeMixinGoogle
)
from
oauth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
third_party_auth.tests.utils
import
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
class
AccessTokenExchangeViewTest
(
AccessTokenExchangeTestMixin
):
...
...
@@ -95,7 +92,7 @@ class AccessTokenExchangeViewTest(AccessTokenExchangeTestMixin):
@httpretty.activate
class
AccessTokenExchangeViewTestFacebook
(
AccessTokenExchangeViewTest
,
AccessTokenExchange
MixinFacebook
,
ThirdPartyOAuthTest
MixinFacebook
,
TestCase
):
"""
...
...
@@ -109,7 +106,7 @@ class AccessTokenExchangeViewTestFacebook(
@httpretty.activate
class
AccessTokenExchangeViewTestGoogle
(
AccessTokenExchangeViewTest
,
AccessTokenExchange
MixinGoogle
,
ThirdPartyOAuthTest
MixinGoogle
,
TestCase
):
"""
...
...
common/djangoapps/oauth_exchange/tests/utils.py
View file @
289469dd
"""
Test utilities for OAuth access token exchange
"""
import
json
import
httpretty
import
provider.constants
from
provider.oauth2.models
import
Client
from
social.apps.django_app.default.models
import
UserSocialAuth
from
student.tests.factories
import
UserFactory
from
third_party_auth.tests.utils
import
ThirdPartyOAuthTestMixin
class
AccessTokenExchangeTestMixin
(
object
):
class
AccessTokenExchangeTestMixin
(
ThirdPartyOAuthTestMixin
):
"""
A mixin to define test cases for access token exchange. The following
methods must be implemented by subclasses:
...
...
@@ -21,40 +17,12 @@ class AccessTokenExchangeTestMixin(object):
def
setUp
(
self
):
super
(
AccessTokenExchangeTestMixin
,
self
)
.
setUp
()
self
.
client_id
=
"test_client_id"
self
.
oauth_client
=
Client
.
objects
.
create
(
client_id
=
self
.
client_id
,
client_type
=
provider
.
constants
.
PUBLIC
)
self
.
social_uid
=
"test_social_uid"
self
.
user
=
UserFactory
()
UserSocialAuth
.
objects
.
create
(
user
=
self
.
user
,
provider
=
self
.
BACKEND
,
uid
=
self
.
social_uid
)
self
.
access_token
=
"test_access_token"
# Initialize to minimal data
self
.
data
=
{
"access_token"
:
self
.
access_token
,
"client_id"
:
self
.
client_id
,
}
def
_setup_provider_response
(
self
,
success
):
"""
Register a mock response for the third party user information endpoint;
success indicates whether the response status code should be 200 or 400
"""
if
success
:
status
=
200
body
=
json
.
dumps
({
self
.
UID_FIELD
:
self
.
social_uid
})
else
:
status
=
400
body
=
json
.
dumps
({})
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
USER_URL
,
body
=
body
,
status
=
status
,
content_type
=
"application/json"
)
def
_assert_error
(
self
,
_data
,
_expected_error
,
_expected_error_description
):
"""
Given request data, execute a test and check that the expected error
...
...
@@ -101,6 +69,12 @@ class AccessTokenExchangeTestMixin(object):
"test_client_id is not a public client"
)
def
test_inactive_user
(
self
):
self
.
user
.
is_active
=
False
self
.
user
.
save
()
# pylint: disable=no-member
self
.
_setup_provider_response
(
success
=
True
)
self
.
_assert_success
(
self
.
data
,
expected_scopes
=
[])
def
test_invalid_acess_token
(
self
):
self
.
_setup_provider_response
(
success
=
False
)
self
.
_assert_error
(
self
.
data
,
"invalid_grant"
,
"access_token is not valid"
)
...
...
@@ -110,18 +84,14 @@ class AccessTokenExchangeTestMixin(object):
self
.
_setup_provider_response
(
success
=
True
)
self
.
_assert_error
(
self
.
data
,
"invalid_grant"
,
"access_token is not valid"
)
def
test_user_automatically_linked_by_email
(
self
):
UserSocialAuth
.
objects
.
all
()
.
delete
()
self
.
_setup_provider_response
(
success
=
True
,
email
=
self
.
user
.
email
)
self
.
_assert_success
(
self
.
data
,
expected_scopes
=
[])
class
AccessTokenExchangeMixinFacebook
(
object
):
"""Tests access token exchange with the Facebook backend"""
BACKEND
=
"facebook"
USER_URL
=
"https://graph.facebook.com/me"
# In facebook responses, the "id" field is used as the user's identifier
UID_FIELD
=
"id"
class
AccessTokenExchangeMixinGoogle
(
object
):
"""Tests access token exchange with the Google backend"""
BACKEND
=
"google-oauth2"
USER_URL
=
"https://www.googleapis.com/oauth2/v1/userinfo"
# In google-oauth2 responses, the "email" field is used as the user's identifier
UID_FIELD
=
"email"
def
test_inactive_user_not_automatically_linked
(
self
):
UserSocialAuth
.
objects
.
all
()
.
delete
()
self
.
_setup_provider_response
(
success
=
True
,
email
=
self
.
user
.
email
)
self
.
user
.
is_active
=
False
self
.
user
.
save
()
# pylint: disable=no-member
self
.
_assert_error
(
self
.
data
,
"invalid_grant"
,
"access_token is not valid"
)
common/djangoapps/student/tests/test_login.py
View file @
289469dd
...
...
@@ -10,14 +10,18 @@ from django.conf import settings
from
django.core.cache
import
cache
from
django.core.urlresolvers
import
reverse
,
NoReverseMatch
from
django.http
import
HttpResponseBadRequest
,
HttpResponse
from
external_auth.models
import
ExternalAuthMap
import
httpretty
from
mock
import
patch
from
social.apps.django_app.default.models
import
UserSocialAuth
from
external_auth.models
import
ExternalAuthMap
from
student.tests.factories
import
UserFactory
,
RegistrationFactory
,
UserProfileFactory
from
student.views
import
login_oauth_token
from
third_party_auth.tests.utils
import
(
ThirdPartyOAuthTestMixin
,
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -437,7 +441,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
@httpretty.activate
class
LoginOAuthTokenMixin
(
object
):
class
LoginOAuthTokenMixin
(
ThirdPartyOAuthTestMixin
):
"""
Mixin with tests for the login_oauth_token view. A TestCase that includes
this must define the following:
...
...
@@ -448,30 +452,8 @@ class LoginOAuthTokenMixin(object):
"""
def
setUp
(
self
):
s
elf
.
client
=
Client
()
s
uper
(
LoginOAuthTokenMixin
,
self
)
.
setUp
()
self
.
url
=
reverse
(
login_oauth_token
,
kwargs
=
{
"backend"
:
self
.
BACKEND
})
self
.
social_uid
=
"social_uid"
self
.
user
=
UserFactory
()
UserSocialAuth
.
objects
.
create
(
user
=
self
.
user
,
provider
=
self
.
BACKEND
,
uid
=
self
.
social_uid
)
def
_setup_user_response
(
self
,
success
):
"""
Register a mock response for the third party user information endpoint;
success indicates whether the response status code should be 200 or 400
"""
if
success
:
status
=
200
body
=
json
.
dumps
({
self
.
UID_FIELD
:
self
.
social_uid
})
else
:
status
=
400
body
=
json
.
dumps
({})
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
USER_URL
,
body
=
body
,
status
=
status
,
content_type
=
"application/json"
)
def
_assert_error
(
self
,
response
,
status_code
,
error
):
"""Assert that the given response was a 400 with the given error code"""
...
...
@@ -480,13 +462,13 @@ class LoginOAuthTokenMixin(object):
self
.
assertNotIn
(
"partial_pipeline"
,
self
.
client
.
session
)
def
test_success
(
self
):
self
.
_setup_
us
er_response
(
success
=
True
)
self
.
_setup_
provid
er_response
(
success
=
True
)
response
=
self
.
client
.
post
(
self
.
url
,
{
"access_token"
:
"dummy"
})
self
.
assertEqual
(
response
.
status_code
,
204
)
self
.
assertEqual
(
self
.
client
.
session
[
'_auth_user_id'
],
self
.
user
.
id
)
# pylint: disable=no-member
def
test_invalid_token
(
self
):
self
.
_setup_
us
er_response
(
success
=
False
)
self
.
_setup_
provid
er_response
(
success
=
False
)
response
=
self
.
client
.
post
(
self
.
url
,
{
"access_token"
:
"dummy"
})
self
.
_assert_error
(
response
,
401
,
"invalid_token"
)
...
...
@@ -496,7 +478,7 @@ class LoginOAuthTokenMixin(object):
def
test_unlinked_user
(
self
):
UserSocialAuth
.
objects
.
all
()
.
delete
()
self
.
_setup_
us
er_response
(
success
=
True
)
self
.
_setup_
provid
er_response
(
success
=
True
)
response
=
self
.
client
.
post
(
self
.
url
,
{
"access_token"
:
"dummy"
})
self
.
_assert_error
(
response
,
401
,
"invalid_token"
)
...
...
@@ -507,17 +489,13 @@ class LoginOAuthTokenMixin(object):
# This is necessary because cms does not implement third party auth
@unittest.skipUnless
(
settings
.
FEATURES
.
get
(
"ENABLE_THIRD_PARTY_AUTH"
),
"third party auth not enabled"
)
class
LoginOAuthTokenTestFacebook
(
LoginOAuthTokenMixin
,
TestCase
):
class
LoginOAuthTokenTestFacebook
(
LoginOAuthTokenMixin
,
T
hirdPartyOAuthTestMixinFacebook
,
T
estCase
):
"""Tests login_oauth_token with the Facebook backend"""
BACKEND
=
"facebook"
USER_URL
=
"https://graph.facebook.com/me"
UID_FIELD
=
"id"
pass
# This is necessary because cms does not implement third party auth
@unittest.skipUnless
(
settings
.
FEATURES
.
get
(
"ENABLE_THIRD_PARTY_AUTH"
),
"third party auth not enabled"
)
class
LoginOAuthTokenTestGoogle
(
LoginOAuthTokenMixin
,
TestCase
):
class
LoginOAuthTokenTestGoogle
(
LoginOAuthTokenMixin
,
T
hirdPartyOAuthTestMixinGoogle
,
T
estCase
):
"""Tests login_oauth_token with the Google backend"""
BACKEND
=
"google-oauth2"
USER_URL
=
"https://www.googleapis.com/oauth2/v1/userinfo"
UID_FIELD
=
"email"
pass
common/djangoapps/student/views.py
View file @
289469dd
...
...
@@ -6,6 +6,7 @@ import logging
import
uuid
import
time
import
json
import
warnings
from
collections
import
defaultdict
from
pytz
import
UTC
from
ipware.ip
import
get_ip
...
...
@@ -43,6 +44,7 @@ from requests import HTTPError
from
social.apps.django_app
import
utils
as
social_utils
from
social.backends
import
oauth
as
social_oauth
from
social.exceptions
import
AuthException
,
AuthAlreadyAssociated
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
...
...
@@ -1168,11 +1170,13 @@ def login_oauth_token(request, backend):
retrieve information from a third party and matching that information to an
existing user.
"""
warnings
.
warn
(
"Please use AccessTokenExchangeView instead."
,
DeprecationWarning
)
backend
=
request
.
social_strategy
.
backend
if
isinstance
(
backend
,
social_oauth
.
BaseOAuth1
)
or
isinstance
(
backend
,
social_oauth
.
BaseOAuth2
):
if
"access_token"
in
request
.
POST
:
# Tell third party auth pipeline that this is an API call
request
.
session
[
pipeline
.
AUTH_ENTRY_KEY
]
=
pipeline
.
AUTH_ENTRY_API
request
.
session
[
pipeline
.
AUTH_ENTRY_KEY
]
=
pipeline
.
AUTH_ENTRY_
LOGIN_
API
user
=
None
try
:
user
=
backend
.
do_auth
(
request
.
POST
[
"access_token"
])
...
...
@@ -1417,7 +1421,14 @@ def create_account_with_params(request, params):
getattr
(
settings
,
'REGISTRATION_EXTRA_FIELDS'
,
{})
)
if
third_party_auth
.
is_enabled
()
and
pipeline
.
running
(
request
):
# Boolean of whether a 3rd party auth provider and credentials were provided in
# the API so the newly created account can link with the 3rd party account.
#
# Note: this is orthogonal to the 3rd party authentication pipeline that occurs
# when the account is created via the browser and redirect URLs.
should_link_with_social_auth
=
third_party_auth
.
is_enabled
()
and
'provider'
in
params
if
should_link_with_social_auth
or
(
third_party_auth
.
is_enabled
()
and
pipeline
.
running
(
request
)):
params
[
"password"
]
=
pipeline
.
make_random_password
()
# if doing signup for an external authorization, then get email, password, name from the eamap
...
...
@@ -1458,13 +1469,38 @@ def create_account_with_params(request, params):
extended_profile_fields
=
extended_profile_fields
,
enforce_username_neq_password
=
True
,
enforce_password_policy
=
enforce_password_policy
,
tos_required
=
tos_required
tos_required
=
tos_required
,
)
with
transaction
.
commit_on_success
():
ret
=
_do_create_account
(
form
)
(
user
,
profile
,
registration
)
=
ret
# first, create the account
(
user
,
profile
,
registration
)
=
_do_create_account
(
form
)
# next, link the account with social auth, if provided
if
should_link_with_social_auth
:
request
.
social_strategy
=
social_utils
.
load_strategy
(
backend
=
params
[
'provider'
],
request
=
request
)
social_access_token
=
params
.
get
(
'access_token'
)
if
not
social_access_token
:
raise
ValidationError
({
'access_token'
:
[
_
(
"An access_token is required when passing value ({}) for provider."
)
.
format
(
params
[
'provider'
]
)
]
})
request
.
session
[
pipeline
.
AUTH_ENTRY_KEY
]
=
pipeline
.
AUTH_ENTRY_REGISTER_API
pipeline_user
=
None
error_message
=
""
try
:
pipeline_user
=
request
.
social_strategy
.
backend
.
do_auth
(
social_access_token
,
user
=
user
)
except
AuthAlreadyAssociated
:
error_message
=
_
(
"The provided access_token is already associated with another user."
)
except
(
HTTPError
,
AuthException
):
error_message
=
_
(
"The provided access_token is not valid."
)
if
not
pipeline_user
or
not
isinstance
(
pipeline_user
,
User
):
# Ensure user does not re-enter the pipeline
request
.
social_strategy
.
clean_partial_pipeline
()
raise
ValidationError
({
'access_token'
:
[
error_message
]})
if
settings
.
FEATURES
.
get
(
'ENABLE_DISCUSSION_EMAIL_DIGEST'
):
try
:
...
...
@@ -1598,6 +1634,8 @@ def create_account(request, post_override=None):
JSON call to create new edX account.
Used by form in signup_modal.html, which is included into navigation.html
"""
warnings
.
warn
(
"Please use RegistrationView instead."
,
DeprecationWarning
)
try
:
create_account_with_params
(
request
,
post_override
or
request
.
POST
)
except
AccountValidationError
as
exc
:
...
...
common/djangoapps/third_party_auth/pipeline.py
View file @
289469dd
This diff is collapsed.
Click to expand it.
common/djangoapps/third_party_auth/settings.py
View file @
289469dd
...
...
@@ -103,6 +103,7 @@ def _set_global_settings(django_settings):
'social.pipeline.social_auth.social_uid'
,
'social.pipeline.social_auth.auth_allowed'
,
'social.pipeline.social_auth.social_user'
,
'third_party_auth.pipeline.associate_by_email_if_login_api'
,
'social.pipeline.user.get_username'
,
'third_party_auth.pipeline.ensure_user_information'
,
'social.pipeline.user.create_user'
,
...
...
common/djangoapps/third_party_auth/tests/test_change_enrollment.py
View file @
289469dd
...
...
@@ -147,7 +147,7 @@ class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
# Simulate completing the pipeline from the student dashboard's
# "link account" button.
result
=
pipeline
.
change_enrollment
(
strategy
,
1
,
user
=
self
.
user
,
is_dashboard
=
True
)
# pylint: disable=assignment-from-no-return,redundant-keyword-arg
result
=
pipeline
.
change_enrollment
(
strategy
,
1
,
user
=
self
.
user
,
auth_entry
=
pipeline
.
AUTH_ENTRY_DASHBOARD
)
# pylint: disable=assignment-from-no-return,redundant-keyword-arg
# Verify that we were NOT enrolled
self
.
assertEqual
(
result
,
{})
...
...
@@ -165,7 +165,7 @@ class PipelineEnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
details
=
None
,
response
=
None
,
uid
=
None
,
is_register
=
True
,
auth_entry
=
pipeline
.
AUTH_ENTRY_REGISTER
,
backend
=
backend
)
self
.
assertIsNotNone
(
response
)
...
...
common/djangoapps/third_party_auth/tests/utils.py
0 → 100644
View file @
289469dd
"""Common utility for testing third party oauth2 features."""
import
json
import
httpretty
from
provider.constants
import
PUBLIC
from
provider.oauth2.models
import
Client
from
social.apps.django_app.default.models
import
UserSocialAuth
from
student.tests.factories
import
UserFactory
@httpretty.activate
class
ThirdPartyOAuthTestMixin
(
object
):
"""
Mixin with tests for third party oauth views. A TestCase that includes
this must define the following:
BACKEND: The name of the backend from python-social-auth
USER_URL: The URL of the endpoint that the backend retrieves user data from
UID_FIELD: The field in the user data that the backend uses as the user id
"""
def
setUp
(
self
,
create_user
=
True
):
super
(
ThirdPartyOAuthTestMixin
,
self
)
.
setUp
()
self
.
social_uid
=
"test_social_uid"
self
.
access_token
=
"test_access_token"
self
.
client_id
=
"test_client_id"
self
.
oauth_client
=
Client
.
objects
.
create
(
client_id
=
self
.
client_id
,
client_type
=
PUBLIC
)
if
create_user
:
self
.
user
=
UserFactory
()
UserSocialAuth
.
objects
.
create
(
user
=
self
.
user
,
provider
=
self
.
BACKEND
,
uid
=
self
.
social_uid
)
def
_setup_provider_response
(
self
,
success
=
False
,
email
=
''
):
"""
Register a mock response for the third party user information endpoint;
success indicates whether the response status code should be 200 or 400
"""
if
success
:
status
=
200
response
=
{
self
.
UID_FIELD
:
self
.
social_uid
}
if
email
:
response
.
update
({
'email'
:
email
})
body
=
json
.
dumps
(
response
)
else
:
status
=
400
body
=
json
.
dumps
({})
self
.
_setup_provider_response_with_body
(
status
,
body
)
def
_setup_provider_response_with_body
(
self
,
status
,
body
):
"""
Register a mock response for the third party user information endpoint with given status and body.
"""
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
USER_URL
,
body
=
body
,
status
=
status
,
content_type
=
"application/json"
)
class
ThirdPartyOAuthTestMixinFacebook
(
object
):
"""Tests oauth with the Facebook backend"""
BACKEND
=
"facebook"
USER_URL
=
"https://graph.facebook.com/me"
# In facebook responses, the "id" field is used as the user's identifier
UID_FIELD
=
"id"
class
ThirdPartyOAuthTestMixinGoogle
(
object
):
"""Tests oauth with the Google backend"""
BACKEND
=
"google-oauth2"
USER_URL
=
"https://www.googleapis.com/oauth2/v1/userinfo"
# In google-oauth2 responses, the "email" field is used as the user's identifier
UID_FIELD
=
"email"
lms/urls.py
View file @
289469dd
...
...
@@ -596,6 +596,8 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
oauth_exchange
.
views
.
AccessTokenExchangeView
.
as_view
(),
name
=
"exchange_access_token"
),
# NOTE: The following login_oauth_token endpoint is DEPRECATED.
# Please use the exchange_access_token endpoint instead.
url
(
r'^login_oauth_token/(?P<backend>[^/]+)/$'
,
'student.views.login_oauth_token'
),
)
...
...
openedx/core/djangoapps/user_api/tests/test_views.py
View file @
289469dd
...
...
@@ -4,26 +4,33 @@ import datetime
import
base64
import
json
import
re
from
unittest
import
skipUnless
,
SkipTest
import
ddt
import
httpretty
from
pytz
import
UTC
import
mock
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core
import
mail
from
django.contrib.auth.models
import
User
from
django.test
import
TestCase
from
django.test.testcases
import
TransactionTestCase
from
django.test.utils
import
override_settings
from
unittest
import
skipUnless
import
ddt
from
pytz
import
UTC
import
mock
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
student.tests.factories
import
UserFactory
from
unittest
import
SkipTest
from
django_comment_common
import
models
from
social.apps.django_app.default.models
import
UserSocialAuth
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
third_party_auth.tests.testutil
import
simulate_running_pipeline
from
django_comment_common
import
models
from
student.tests.factories
import
UserFactory
from
third_party_auth.tests.testutil
import
simulate_running_pipeline
from
third_party_auth.tests.utils
import
(
ThirdPartyOAuthTestMixin
,
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
..accounts.api
import
get_account_settings
from
..accounts
import
(
NAME_MAX_LENGTH
,
EMAIL_MIN_LENGTH
,
EMAIL_MAX_LENGTH
,
PASSWORD_MIN_LENGTH
,
PASSWORD_MAX_LENGTH
,
...
...
@@ -1546,6 +1553,148 @@ class RegistrationViewTest(ApiTestCase):
)
@httpretty.activate
@ddt.ddt
class
ThirdPartyRegistrationTestMixin
(
ThirdPartyOAuthTestMixin
):
"""
Tests for the User API registration endpoint with 3rd party authentication.
"""
def
setUp
(
self
):
super
(
ThirdPartyRegistrationTestMixin
,
self
)
.
setUp
(
create_user
=
False
)
self
.
url
=
reverse
(
'user_api_registration'
)
def
data
(
self
,
user
=
None
):
"""Returns the request data for the endpoint."""
return
{
"provider"
:
self
.
BACKEND
,
"access_token"
:
self
.
access_token
,
"client_id"
:
self
.
client_id
,
"honor_code"
:
"true"
,
"country"
:
"US"
,
"username"
:
user
.
username
if
user
else
"test_username"
,
"name"
:
user
.
first_name
if
user
else
"test name"
,
"email"
:
user
.
email
if
user
else
"test@test.com"
,
}
def
_assert_existing_user_error
(
self
,
response
):
"""Assert that the given response was an error with the given status_code and error code."""
self
.
assertEqual
(
response
.
status_code
,
409
)
errors
=
json
.
loads
(
response
.
content
)
for
conflict_attribute
in
[
"username"
,
"email"
]:
self
.
assertIn
(
conflict_attribute
,
errors
)
self
.
assertIn
(
"belongs to an existing account"
,
errors
[
conflict_attribute
][
0
][
"user_message"
])
self
.
assertNotIn
(
"partial_pipeline"
,
self
.
client
.
session
)
def
_assert_access_token_error
(
self
,
response
,
expected_error_message
):
"""Assert that the given response was an error for the access_token field with the given error message."""
self
.
assertEqual
(
response
.
status_code
,
400
)
response_json
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
response_json
,
{
"access_token"
:
[{
"user_message"
:
expected_error_message
}]}
)
self
.
assertNotIn
(
"partial_pipeline"
,
self
.
client
.
session
)
def
_verify_user_existence
(
self
,
user_exists
,
social_link_exists
,
user_is_active
=
None
,
username
=
None
):
"""Verifies whether the user object exists."""
users
=
User
.
objects
.
filter
(
username
=
(
username
if
username
else
"test_username"
))
self
.
assertEquals
(
users
.
exists
(),
user_exists
)
if
user_exists
:
self
.
assertEquals
(
users
[
0
]
.
is_active
,
user_is_active
)
self
.
assertEqual
(
UserSocialAuth
.
objects
.
filter
(
user
=
users
[
0
],
provider
=
self
.
BACKEND
)
.
exists
(),
social_link_exists
)
else
:
self
.
assertEquals
(
UserSocialAuth
.
objects
.
count
(),
0
)
def
test_success
(
self
):
self
.
_verify_user_existence
(
user_exists
=
False
,
social_link_exists
=
False
)
self
.
_setup_provider_response
(
success
=
True
)
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
())
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
_verify_user_existence
(
user_exists
=
True
,
social_link_exists
=
True
,
user_is_active
=
False
)
def
test_unlinked_active_user
(
self
):
user
=
UserFactory
()
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
(
user
))
self
.
_assert_existing_user_error
(
response
)
self
.
_verify_user_existence
(
user_exists
=
True
,
social_link_exists
=
False
,
user_is_active
=
True
,
username
=
user
.
username
)
def
test_unlinked_inactive_user
(
self
):
user
=
UserFactory
(
is_active
=
False
)
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
(
user
))
self
.
_assert_existing_user_error
(
response
)
self
.
_verify_user_existence
(
user_exists
=
True
,
social_link_exists
=
False
,
user_is_active
=
False
,
username
=
user
.
username
)
def
test_user_already_registered
(
self
):
self
.
_setup_provider_response
(
success
=
True
)
user
=
UserFactory
()
UserSocialAuth
.
objects
.
create
(
user
=
user
,
provider
=
self
.
BACKEND
,
uid
=
self
.
social_uid
)
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
(
user
))
self
.
_assert_existing_user_error
(
response
)
self
.
_verify_user_existence
(
user_exists
=
True
,
social_link_exists
=
True
,
user_is_active
=
True
,
username
=
user
.
username
)
def
test_social_user_conflict
(
self
):
self
.
_setup_provider_response
(
success
=
True
)
user
=
UserFactory
()
UserSocialAuth
.
objects
.
create
(
user
=
user
,
provider
=
self
.
BACKEND
,
uid
=
self
.
social_uid
)
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
())
self
.
_assert_access_token_error
(
response
,
"The provided access_token is already associated with another user."
)
self
.
_verify_user_existence
(
user_exists
=
True
,
social_link_exists
=
True
,
user_is_active
=
True
,
username
=
user
.
username
)
def
test_invalid_token
(
self
):
self
.
_setup_provider_response
(
success
=
False
)
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
())
self
.
_assert_access_token_error
(
response
,
"The provided access_token is not valid."
)
self
.
_verify_user_existence
(
user_exists
=
False
,
social_link_exists
=
False
)
def
test_missing_token
(
self
):
data
=
self
.
data
()
data
.
pop
(
"access_token"
)
response
=
self
.
client
.
post
(
self
.
url
,
data
)
self
.
_assert_access_token_error
(
response
,
"An access_token is required when passing value ({}) for provider."
.
format
(
self
.
BACKEND
)
)
self
.
_verify_user_existence
(
user_exists
=
False
,
social_link_exists
=
False
)
@skipUnless
(
settings
.
FEATURES
.
get
(
"ENABLE_THIRD_PARTY_AUTH"
),
"third party auth not enabled"
)
class
TestFacebookRegistrationView
(
ThirdPartyRegistrationTestMixin
,
ThirdPartyOAuthTestMixinFacebook
,
TransactionTestCase
):
"""Tests the User API registration endpoint with Facebook authentication."""
def
test_social_auth_exception
(
self
):
"""
According to the do_auth method in social.backends.facebook.py,
the Facebook API sometimes responds back a JSON with just False as value.
"""
self
.
_setup_provider_response_with_body
(
200
,
json
.
dumps
(
"false"
))
response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
())
self
.
_assert_access_token_error
(
response
,
"The provided access_token is not valid."
)
self
.
_verify_user_existence
(
user_exists
=
False
,
social_link_exists
=
False
)
@skipUnless
(
settings
.
FEATURES
.
get
(
"ENABLE_THIRD_PARTY_AUTH"
),
"third party auth not enabled"
)
class
TestGoogleRegistrationView
(
ThirdPartyRegistrationTestMixin
,
ThirdPartyOAuthTestMixinGoogle
,
TransactionTestCase
):
"""Tests the User API registration endpoint with Google authentication."""
pass
@ddt.ddt
class
UpdateEmailOptInTestCase
(
ApiTestCase
,
ModuleStoreTestCase
):
"""Tests the UpdateEmailOptInPreference view. """
...
...
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