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
8aae4023
Commit
8aae4023
authored
May 19, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MA-738 OAuth2 token exchange for session cookie.
parent
0a43ebf1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
152 additions
and
49 deletions
+152
-49
common/djangoapps/auth_exchange/__init__.py
+0
-0
common/djangoapps/auth_exchange/forms.py
+6
-0
common/djangoapps/auth_exchange/models.py
+0
-0
common/djangoapps/auth_exchange/tests/__init__.py
+0
-0
common/djangoapps/auth_exchange/tests/test_forms.py
+3
-2
common/djangoapps/auth_exchange/tests/test_views.py
+43
-3
common/djangoapps/auth_exchange/tests/utils.py
+1
-1
common/djangoapps/auth_exchange/views.py
+85
-0
common/djangoapps/oauth_exchange/views.py
+0
-37
lms/envs/common.py
+1
-1
lms/urls.py
+13
-5
No files found.
common/djangoapps/
o
auth_exchange/__init__.py
→
common/djangoapps/auth_exchange/__init__.py
View file @
8aae4023
File moved
common/djangoapps/
o
auth_exchange/forms.py
→
common/djangoapps/auth_exchange/forms.py
View file @
8aae4023
...
...
@@ -39,9 +39,15 @@ class AccessTokenExchangeForm(ScopeMixin, OAuthForm):
return
field_val
def
clean_access_token
(
self
):
"""
Validates and returns the "access_token" field.
"""
return
self
.
_require_oauth_field
(
"access_token"
)
def
clean_client_id
(
self
):
"""
Validates and returns the "client_id" field.
"""
return
self
.
_require_oauth_field
(
"client_id"
)
def
clean
(
self
):
...
...
common/djangoapps/
o
auth_exchange/models.py
→
common/djangoapps/auth_exchange/models.py
View file @
8aae4023
File moved
common/djangoapps/
o
auth_exchange/tests/__init__.py
→
common/djangoapps/auth_exchange/tests/__init__.py
View file @
8aae4023
File moved
common/djangoapps/
o
auth_exchange/tests/test_forms.py
→
common/djangoapps/auth_exchange/tests/test_forms.py
View file @
8aae4023
# pylint: disable=no-member
"""
Tests for OAuth token exchange forms
"""
...
...
@@ -11,8 +12,8 @@ import httpretty
from
provider
import
scope
import
social.apps.django_app.utils
as
social_utils
from
o
auth_exchange.forms
import
AccessTokenExchangeForm
from
o
auth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
auth_exchange.forms
import
AccessTokenExchangeForm
from
auth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
third_party_auth.tests.utils
import
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
...
...
common/djangoapps/
o
auth_exchange/tests/test_views.py
→
common/djangoapps/auth_exchange/tests/test_views.py
View file @
8aae4023
# pylint: disable=no-member
"""
Tests for OAuth token exchange views
"""
...
...
@@ -12,9 +13,10 @@ from django.test import TestCase
import
httpretty
import
provider.constants
from
provider
import
scope
from
provider.oauth2.models
import
AccessToken
from
provider.oauth2.models
import
AccessToken
,
Client
from
oauth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
auth_exchange.tests.utils
import
AccessTokenExchangeTestMixin
from
student.tests.factories
import
UserFactory
from
third_party_auth.tests.utils
import
ThirdPartyOAuthTestMixinFacebook
,
ThirdPartyOAuthTestMixinGoogle
...
...
@@ -55,12 +57,15 @@ class AccessTokenExchangeViewTest(AccessTokenExchangeTestMixin):
def
test_single_access_token
(
self
):
def
extract_token
(
response
):
"""
Returns the access token from the response payload.
"""
return
json
.
loads
(
response
.
content
)[
"access_token"
]
self
.
_setup_provider_response
(
success
=
True
)
for
single_access_token
in
[
True
,
False
]:
with
mock
.
patch
(
"
o
auth_exchange.views.constants.SINGLE_ACCESS_TOKEN"
,
"auth_exchange.views.constants.SINGLE_ACCESS_TOKEN"
,
single_access_token
):
first_response
=
self
.
client
.
post
(
self
.
url
,
self
.
data
)
...
...
@@ -113,3 +118,38 @@ class AccessTokenExchangeViewTestGoogle(
Tests for AccessTokenExchangeView used with Google
"""
pass
@unittest.skipUnless
(
settings
.
FEATURES
.
get
(
"ENABLE_OAUTH2_PROVIDER"
),
"OAuth2 not enabled"
)
class
TestLoginWithAccessTokenView
(
TestCase
):
"""
Tests for LoginWithAccessTokenView
"""
def
setUp
(
self
):
super
(
TestLoginWithAccessTokenView
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
self
.
oauth2_client
=
Client
.
objects
.
create
(
client_type
=
provider
.
constants
.
CONFIDENTIAL
)
def
_verify_response
(
self
,
access_token
,
expected_status_code
,
expected_num_cookies
):
"""
Calls the login_with_access_token endpoint and verifies the response given the expected values.
"""
url
=
reverse
(
"login_with_access_token"
)
response
=
self
.
client
.
post
(
url
,
HTTP_AUTHORIZATION
=
"Bearer {0}"
.
format
(
access_token
))
self
.
assertEqual
(
response
.
status_code
,
expected_status_code
)
self
.
assertEqual
(
len
(
response
.
cookies
),
expected_num_cookies
)
def
test_success
(
self
):
access_token
=
AccessToken
.
objects
.
create
(
token
=
"test_access_token"
,
client
=
self
.
oauth2_client
,
user
=
self
.
user
,
)
self
.
_verify_response
(
access_token
,
expected_status_code
=
204
,
expected_num_cookies
=
1
)
self
.
assertEqual
(
len
(
self
.
client
.
cookies
),
1
)
self
.
assertEqual
(
self
.
client
.
session
[
'_auth_user_id'
],
self
.
user
.
id
)
def
test_unauthenticated
(
self
):
self
.
_verify_response
(
"invalid_token"
,
expected_status_code
=
401
,
expected_num_cookies
=
0
)
self
.
assertEqual
(
len
(
self
.
client
.
cookies
),
0
)
self
.
assertNotIn
(
"session_key"
,
self
.
client
.
session
)
common/djangoapps/
o
auth_exchange/tests/utils.py
→
common/djangoapps/auth_exchange/tests/utils.py
View file @
8aae4023
...
...
@@ -14,7 +14,7 @@ class AccessTokenExchangeTestMixin(ThirdPartyOAuthTestMixin):
* _assert_error(data, expected_error, expected_error_description)
* _assert_success(data, expected_scopes)
"""
def
setUp
(
self
):
def
setUp
(
self
):
# pylint: disable=arguments-differ
super
(
AccessTokenExchangeTestMixin
,
self
)
.
setUp
()
# Initialize to minimal data
...
...
common/djangoapps/auth_exchange/views.py
0 → 100644
View file @
8aae4023
# pylint: disable=abstract-method
"""
Views to support exchange of authentication credentials.
The following are currently implemented:
1. AccessTokenExchangeView:
3rd party (social-auth) OAuth 2.0 access token -> 1st party (open-edx) OAuth 2.0 access token
2. LoginWithAccessTokenView:
1st party (open-edx) OAuth 2.0 access token -> session cookie
"""
from
django.conf
import
settings
from
django.contrib.auth
import
login
import
django.contrib.auth
as
auth
from
django.http
import
HttpResponse
from
django.utils.decorators
import
method_decorator
from
django.views.decorators.csrf
import
csrf_exempt
from
provider
import
constants
from
provider.oauth2.views
import
AccessTokenView
as
AccessTokenView
from
rest_framework
import
permissions
from
rest_framework.views
import
APIView
import
social.apps.django_app.utils
as
social_utils
from
auth_exchange.forms
import
AccessTokenExchangeForm
from
openedx.core.lib.api.authentication
import
OAuth2AuthenticationAllowInactiveUser
class
AccessTokenExchangeView
(
AccessTokenView
):
"""
View for token exchange from 3rd party OAuth access token to 1st party OAuth access token
"""
@method_decorator
(
csrf_exempt
)
@method_decorator
(
social_utils
.
strategy
(
"social:complete"
))
def
dispatch
(
self
,
*
args
,
**
kwargs
):
return
super
(
AccessTokenExchangeView
,
self
)
.
dispatch
(
*
args
,
**
kwargs
)
def
get
(
self
,
request
,
_backend
):
# pylint: disable=arguments-differ
return
super
(
AccessTokenExchangeView
,
self
)
.
get
(
request
)
def
post
(
self
,
request
,
_backend
):
# pylint: disable=arguments-differ
form
=
AccessTokenExchangeForm
(
request
=
request
,
data
=
request
.
POST
)
if
not
form
.
is_valid
():
return
self
.
error_response
(
form
.
errors
)
user
=
form
.
cleaned_data
[
"user"
]
scope
=
form
.
cleaned_data
[
"scope"
]
client
=
form
.
cleaned_data
[
"client"
]
if
constants
.
SINGLE_ACCESS_TOKEN
:
edx_access_token
=
self
.
get_access_token
(
request
,
user
,
scope
,
client
)
else
:
edx_access_token
=
self
.
create_access_token
(
request
,
user
,
scope
,
client
)
return
self
.
access_token_response
(
edx_access_token
)
class
LoginWithAccessTokenView
(
APIView
):
"""
View for exchanging an access token for session cookies
"""
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,)
permission_classes
=
(
permissions
.
IsAuthenticated
,)
@staticmethod
def
_get_path_of_arbitrary_backend_for_user
(
user
):
"""
Return the path to the first found authentication backend that recognizes the given user.
"""
for
backend_path
in
settings
.
AUTHENTICATION_BACKENDS
:
backend
=
auth
.
load_backend
(
backend_path
)
if
backend
.
get_user
(
user
.
id
):
return
backend_path
@method_decorator
(
csrf_exempt
)
def
post
(
self
,
request
):
"""
Handler for the POST method to this view.
"""
# The django login method stores the user's id in request.session[SESSION_KEY] and the
# path to the user's authentication backend in request.session[BACKEND_SESSION_KEY].
# The login method assumes the backend path had been previously stored in request.user.backend
# in the 'authenticate' call. However, not all authentication providers do so.
# So we explicitly populate the request.user.backend field here.
if
not
hasattr
(
request
.
user
,
'backend'
):
request
.
user
.
backend
=
self
.
_get_path_of_arbitrary_backend_for_user
(
request
.
user
)
login
(
request
,
request
.
user
)
# login generates and stores the user's cookies in the session
return
HttpResponse
(
status
=
204
)
# cookies stored in the session are returned with the response
common/djangoapps/oauth_exchange/views.py
deleted
100644 → 0
View file @
0a43ebf1
"""
Views to support third-party to first-party OAuth 2.0 access token exchange
"""
from
django.utils.decorators
import
method_decorator
from
django.views.decorators.csrf
import
csrf_exempt
from
provider
import
constants
from
provider.oauth2.views
import
AccessTokenView
as
AccessTokenView
import
social.apps.django_app.utils
as
social_utils
from
oauth_exchange.forms
import
AccessTokenExchangeForm
class
AccessTokenExchangeView
(
AccessTokenView
):
"""View for access token exchange"""
@method_decorator
(
csrf_exempt
)
@method_decorator
(
social_utils
.
strategy
(
"social:complete"
))
def
dispatch
(
self
,
*
args
,
**
kwargs
):
return
super
(
AccessTokenExchangeView
,
self
)
.
dispatch
(
*
args
,
**
kwargs
)
def
get
(
self
,
request
,
_backend
):
return
super
(
AccessTokenExchangeView
,
self
)
.
get
(
request
)
def
post
(
self
,
request
,
_backend
):
form
=
AccessTokenExchangeForm
(
request
=
request
,
data
=
request
.
POST
)
if
not
form
.
is_valid
():
return
self
.
error_response
(
form
.
errors
)
user
=
form
.
cleaned_data
[
"user"
]
scope
=
form
.
cleaned_data
[
"scope"
]
client
=
form
.
cleaned_data
[
"client"
]
if
constants
.
SINGLE_ACCESS_TOKEN
:
edx_access_token
=
self
.
get_access_token
(
request
,
user
,
scope
,
client
)
else
:
edx_access_token
=
self
.
create_access_token
(
request
,
user
,
scope
,
client
)
return
self
.
access_token_response
(
edx_access_token
)
lms/envs/common.py
View file @
8aae4023
...
...
@@ -1665,7 +1665,7 @@ INSTALLED_APPS = (
'provider.oauth2'
,
'oauth2_provider'
,
'
o
auth_exchange'
,
'auth_exchange'
,
# For the wiki
'wiki'
,
# The new django-wiki from benjaoming
...
...
lms/urls.py
View file @
8aae4023
...
...
@@ -5,7 +5,7 @@ from django.conf.urls.static import static
import
django.contrib.auth.views
from
microsite_configuration
import
microsite
import
o
auth_exchange.views
import
auth_exchange.views
# Uncomment the next two lines to enable the admin:
if
settings
.
DEBUG
or
settings
.
FEATURES
.
get
(
'ENABLE_DJANGO_ADMIN_SITE'
):
...
...
@@ -611,12 +611,20 @@ if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
)
# OAuth token exchange
if
settings
.
FEATURES
.
get
(
'ENABLE_THIRD_PARTY_AUTH'
)
and
settings
.
FEATURES
.
get
(
'ENABLE_OAUTH2_PROVIDER'
):
if
settings
.
FEATURES
.
get
(
'ENABLE_OAUTH2_PROVIDER'
):
if
settings
.
FEATURES
.
get
(
'ENABLE_THIRD_PARTY_AUTH'
):
urlpatterns
+=
(
url
(
r'^oauth2/exchange_access_token/(?P<backend>[^/]+)/$'
,
auth_exchange
.
views
.
AccessTokenExchangeView
.
as_view
(),
name
=
"exchange_access_token"
),
)
urlpatterns
+=
(
url
(
r'^oauth2/
exchange_access_token/(?P<backend>[^/]+)
/$'
,
oauth_exchange
.
views
.
AccessTokenExchange
View
.
as_view
(),
name
=
"
exchange
_access_token"
r'^oauth2/
login
/$'
,
auth_exchange
.
views
.
LoginWithAccessToken
View
.
as_view
(),
name
=
"
login_with
_access_token"
),
)
...
...
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