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
16120efa
Commit
16120efa
authored
Sep 12, 2017
by
zubair-arbi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initialize enterprise api client with provided user
ENT-624
parent
d62e2498
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
155 additions
and
57 deletions
+155
-57
common/djangoapps/enrollment/views.py
+4
-4
openedx/features/enterprise_support/api.py
+54
-27
openedx/features/enterprise_support/tests/test_api.py
+97
-26
No files found.
common/djangoapps/enrollment/views.py
View file @
16120efa
...
...
@@ -30,8 +30,8 @@ from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeade
from
openedx.core.lib.exceptions
import
CourseNotFoundError
from
openedx.core.lib.log_utils
import
audit_log
from
openedx.features.enterprise_support.api
import
(
ConsentApiClient
,
EnterpriseApiClient
,
ConsentApi
Service
Client
,
EnterpriseApi
Service
Client
,
EnterpriseApiException
,
enterprise_enabled
)
...
...
@@ -598,8 +598,8 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
enterprise_course_consent
=
request
.
data
.
get
(
'enterprise_course_consent'
)
explicit_linked_enterprise
=
request
.
data
.
get
(
'linked_enterprise_customer'
)
if
(
enterprise_course_consent
or
explicit_linked_enterprise
)
and
has_api_key_permissions
and
enterprise_enabled
():
enterprise_api_client
=
EnterpriseApiClient
()
consent_client
=
ConsentApiClient
()
enterprise_api_client
=
EnterpriseApi
Service
Client
()
consent_client
=
ConsentApi
Service
Client
()
# We received an explicitly-linked EnterpriseCustomer for the enrollment
if
explicit_linked_enterprise
is
not
None
:
try
:
...
...
openedx/features/enterprise_support/api.py
View file @
16120efa
"""
APIs providing support for enterprise functionality.
"""
import
hashlib
import
logging
from
functools
import
wraps
import
six
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.core.cache
import
cache
from
django.core.urlresolvers
import
reverse
from
django.shortcuts
import
redirect
from
django.template.loader
import
render_to_string
from
django.utils.http
import
urlencode
from
django.utils.translation
import
ugettext
as
_
from
edx_rest_api_client.client
import
EdxRestApiClient
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
HttpClientError
,
HttpNotFoundError
,
HttpServerError
,
SlumberBaseException
from
openedx.core.djangoapps.catalog.models
import
CatalogIntegration
from
openedx.core.djangoapps.catalog.utils
import
create_catalog_api_client
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.lib.token_utils
import
JwtBuilder
from
third_party_auth.pipeline
import
get
as
get_partial_pipeline
...
...
@@ -30,6 +24,7 @@ try:
except
ImportError
:
pass
CONSENT_FAILED_PARAMETER
=
'consent_failed'
LOGGER
=
logging
.
getLogger
(
"edx.enterprise_helpers"
)
...
...
@@ -46,12 +41,12 @@ class ConsentApiClient(object):
Class for producing an Enterprise Consent service API client
"""
def
__init__
(
self
):
def
__init__
(
self
,
user
):
"""
Initialize a consent service API client, authenticated using the Enterprise worker username.
Initialize an authenticated Consent service API client by using the
provided user.
"""
self
.
user
=
User
.
objects
.
get
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
)
jwt
=
JwtBuilder
(
self
.
user
)
.
build_token
([])
jwt
=
JwtBuilder
(
user
)
.
build_token
([])
url
=
configuration_helpers
.
get_value
(
'ENTERPRISE_CONSENT_API_URL'
,
settings
.
ENTERPRISE_CONSENT_API_URL
)
self
.
client
=
EdxRestApiClient
(
url
,
...
...
@@ -97,17 +92,38 @@ class ConsentApiClient(object):
return
response
[
'consent_required'
]
class
EnterpriseServiceClientMixin
(
object
):
"""
Class for initializing an Enterprise API clients with service user.
"""
def
__init__
(
self
):
"""
Initialize an authenticated Enterprise API client by using the
Enterprise worker user by default.
"""
user
=
User
.
objects
.
get
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
)
super
(
EnterpriseServiceClientMixin
,
self
)
.
__init__
(
user
)
class
ConsentApiServiceClient
(
EnterpriseServiceClientMixin
,
ConsentApiClient
):
"""
Class for producing an Enterprise Consent API client with service user.
"""
pass
class
EnterpriseApiClient
(
object
):
"""
Class for producing an Enterprise service API client.
"""
def
__init__
(
self
):
def
__init__
(
self
,
user
):
"""
Initialize an Enterprise service API client, authenticated using the Enterprise worker username.
Initialize an authenticated Enterprise service API client by using the
provided user.
"""
self
.
user
=
User
.
objects
.
get
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
)
jwt
=
JwtBuilder
(
self
.
user
)
.
build_token
([])
jwt
=
JwtBuilder
(
user
)
.
build_token
([])
self
.
client
=
EdxRestApiClient
(
configuration_helpers
.
get_value
(
'ENTERPRISE_API_URL'
,
settings
.
ENTERPRISE_API_URL
),
jwt
=
jwt
...
...
@@ -241,6 +257,13 @@ class EnterpriseApiClient(object):
return
response
class
EnterpriseApiServiceClient
(
EnterpriseServiceClientMixin
,
EnterpriseApiClient
):
"""
Class for producing an Enterprise service API client with service user.
"""
pass
def
data_sharing_consent_required
(
view_func
):
"""
Decorator which makes a view method redirect to the Data Sharing Consent form if:
...
...
@@ -294,7 +317,7 @@ def enterprise_customer_for_request(request):
if
not
enterprise_enabled
():
return
None
e
c
=
None
e
nterprise_customer
=
None
sso_provider_id
=
request
.
GET
.
get
(
'tpa_hint'
)
running_pipeline
=
get_partial_pipeline
(
request
)
...
...
@@ -311,34 +334,38 @@ def enterprise_customer_for_request(request):
# Check if there's an Enterprise Customer such that the linked SSO provider
# has an ID equal to the ID we got from the running pipeline or from the
# request tpa_hint URL parameter.
e
c
_uuid
=
EnterpriseCustomer
.
objects
.
get
(
e
nterprise_customer
_uuid
=
EnterpriseCustomer
.
objects
.
get
(
enterprise_customer_identity_provider__provider_id
=
sso_provider_id
)
.
uuid
except
EnterpriseCustomer
.
DoesNotExist
:
# If there is not an EnterpriseCustomer linked to this SSO provider, set
# the UUID variable to be null.
e
c
_uuid
=
None
e
nterprise_customer
_uuid
=
None
else
:
# Check if we got an Enterprise UUID passed directly as either a query
# parameter, or as a value in the Enterprise cookie.
ec_uuid
=
request
.
GET
.
get
(
'enterprise_customer'
)
or
request
.
COOKIES
.
get
(
settings
.
ENTERPRISE_CUSTOMER_COOKIE_NAME
)
enterprise_customer_uuid
=
request
.
GET
.
get
(
'enterprise_customer'
)
or
request
.
COOKIES
.
get
(
settings
.
ENTERPRISE_CUSTOMER_COOKIE_NAME
)
if
not
e
c
_uuid
and
request
.
user
.
is_authenticated
():
if
not
e
nterprise_customer
_uuid
and
request
.
user
.
is_authenticated
():
# If there's no way to get an Enterprise UUID for the request, check to see
# if there's already an Enterprise attached to the requesting user on the backend.
learner_data
=
get_enterprise_learner_data
(
request
.
site
,
request
.
user
)
if
learner_data
:
e
c
_uuid
=
learner_data
[
0
][
'enterprise_customer'
][
'uuid'
]
if
e
c
_uuid
:
e
nterprise_customer
_uuid
=
learner_data
[
0
][
'enterprise_customer'
][
'uuid'
]
if
e
nterprise_customer
_uuid
:
# If we were able to obtain an EnterpriseCustomer UUID, go ahead
# and use it to attempt to retrieve EnterpriseCustomer details
# from the EnterpriseCustomer API.
try
:
ec
=
EnterpriseApiClient
()
.
get_enterprise_customer
(
ec_uuid
)
enterprise_customer
=
EnterpriseApiClient
(
user
=
request
.
user
)
.
get_enterprise_customer
(
enterprise_customer_uuid
)
except
HttpNotFoundError
:
e
c
=
None
e
nterprise_customer
=
None
return
e
c
return
e
nterprise_customer
def
consent_needed_for_course
(
request
,
user
,
course_id
,
enrollment_exists
=
False
):
...
...
@@ -358,7 +385,7 @@ def consent_needed_for_course(request, user, course_id, enrollment_exists=False)
if
not
enterprise_learner_details
:
consent_needed
=
False
else
:
client
=
ConsentApiClient
()
client
=
ConsentApiClient
(
user
=
request
.
user
)
consent_needed
=
any
(
client
.
consent_required
(
username
=
user
.
username
,
...
...
@@ -426,7 +453,7 @@ def get_enterprise_learner_data(site, user):
if
not
enterprise_enabled
():
return
None
enterprise_learner_data
=
EnterpriseApiClient
()
.
fetch_enterprise_learner_data
(
site
=
site
,
user
=
user
)
enterprise_learner_data
=
EnterpriseApiClient
(
user
=
user
)
.
fetch_enterprise_learner_data
(
site
=
site
,
user
=
user
)
if
enterprise_learner_data
:
return
enterprise_learner_data
[
'results'
]
...
...
@@ -461,7 +488,7 @@ def get_dashboard_consent_notification(request, user, course_enrollments):
enrollment
=
course_enrollment
break
client
=
ConsentApiClient
()
client
=
ConsentApiClient
(
user
=
request
.
user
)
consent_needed
=
client
.
consent_required
(
enterprise_customer_uuid
=
enterprise_customer
[
'uuid'
],
username
=
user
.
username
,
...
...
openedx/features/enterprise_support/tests/test_api.py
View file @
16120efa
...
...
@@ -8,16 +8,20 @@ import ddt
import
httpretty
import
mock
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponseRedirect
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
openedx.features.enterprise_support.api
import
(
ConsentApiClient
,
ConsentApiServiceClient
,
consent_needed_for_course
,
data_sharing_consent_required
,
EnterpriseApiClient
,
EnterpriseApiServiceClient
,
enterprise_customer_for_request
,
enterprise_enabled
,
get_dashboard_consent_notification
,
get_enterprise_consent_url
,
)
...
...
@@ -44,21 +48,86 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, TestCase):
"""
@classmethod
def
setUpTestData
(
cls
):
UserFactory
.
create
(
username
=
'enterprise_worker'
,
cls
.
user
=
UserFactory
.
create
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
,
email
=
'ent_worker@example.com'
,
password
=
'password123'
,
)
super
(
TestEnterpriseApi
,
cls
)
.
setUpTestData
()
def
_assert_api_service_client
(
self
,
api_client
,
mocked_jwt_builder
):
"""
Verify that the provided api client uses the enterprise service user to generate
JWT token for auth.
"""
mocked_jwt_builder
.
return_value
.
build_token
.
return_value
=
'test-token'
enterprise_service_user
=
User
.
objects
.
get
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
)
enterprise_api_service_client
=
api_client
()
mocked_jwt_builder
.
assert_called_once_with
(
enterprise_service_user
)
# pylint: disable=protected-access
self
.
assertEqual
(
enterprise_api_service_client
.
client
.
_store
[
'session'
]
.
auth
.
token
,
'test-token'
)
def
_assert_api_client_with_user
(
self
,
api_client
,
mocked_jwt_builder
):
"""
Verify that the provided api client uses the expected user to generate
JWT token for auth.
"""
mocked_jwt_builder
.
return_value
.
build_token
.
return_value
=
'test-token'
dummy_enterprise_user
=
UserFactory
.
create
(
username
=
'dummy-enterprise-user'
,
email
=
'dummy-enterprise-user@example.com'
,
password
=
'password123'
,
)
enterprise_api_service_client
=
api_client
(
dummy_enterprise_user
)
mocked_jwt_builder
.
assert_called_once_with
(
dummy_enterprise_user
)
# pylint: disable=protected-access
self
.
assertEqual
(
enterprise_api_service_client
.
client
.
_store
[
'session'
]
.
auth
.
token
,
'test-token'
)
@httpretty.activate
@mock.patch
(
'openedx.features.enterprise_support.api.JwtBuilder'
)
def
test_enterprise_api_client_with_service_user
(
self
,
mock_jwt_builder
):
"""
Verify that enterprise API service client uses enterprise service user
by default to authenticate and access enterprise API.
"""
self
.
_assert_api_service_client
(
EnterpriseApiServiceClient
,
mock_jwt_builder
)
@httpretty.activate
@mock.patch
(
'openedx.features.enterprise_support.api.JwtBuilder'
)
def
test_enterprise_api_client_with_user
(
self
,
mock_jwt_builder
):
"""
Verify that enterprise API client uses the provided user to
authenticate and access enterprise API.
"""
self
.
_assert_api_client_with_user
(
EnterpriseApiClient
,
mock_jwt_builder
)
@httpretty.activate
@mock.patch
(
'openedx.features.enterprise_support.api.JwtBuilder'
)
def
test_enterprise_consent_api_client_with_service_user
(
self
,
mock_jwt_builder
):
"""
Verify that enterprise API consent service client uses enterprise
service user by default to authenticate and access enterprise API.
"""
self
.
_assert_api_service_client
(
ConsentApiServiceClient
,
mock_jwt_builder
)
@httpretty.activate
@mock.patch
(
'openedx.features.enterprise_support.api.JwtBuilder'
)
def
test_enterprise_consent_api_client_with_user
(
self
,
mock_jwt_builder
):
"""
Verify that enterprise API consent service client uses the provided
user to authenticate and access enterprise API.
"""
self
.
_assert_api_client_with_user
(
ConsentApiClient
,
mock_jwt_builder
)
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
def
test_consent_needed_for_course
(
self
):
user
=
mock
.
MagicMock
(
username
=
'janedoe'
,
is_authenticated
=
lambda
:
True
,
)
request
=
mock
.
MagicMock
(
session
=
{})
request
=
mock
.
MagicMock
(
session
=
{}
,
user
=
user
)
self
.
mock_enterprise_learner_api
()
self
.
mock_consent_missing
(
user
.
username
,
'fake-course'
,
'cf246b88-d5f6-4908-a522-fc307e0b0c59'
)
self
.
assertTrue
(
consent_needed_for_course
(
request
,
user
,
'fake-course'
))
...
...
@@ -74,63 +143,65 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, TestCase):
@mock.patch
(
'openedx.features.enterprise_support.api.EnterpriseCustomer'
)
@mock.patch
(
'openedx.features.enterprise_support.api.get_partial_pipeline'
)
@mock.patch
(
'openedx.features.enterprise_support.api.Registry'
)
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
def
test_enterprise_customer_for_request
(
self
,
mock_registry
,
mock_partial
,
mock_e
c
_model
,
mock_e
nterprise_customer
_model
,
mock_get_el_data
):
def
mock_get_e
c
(
**
kwargs
):
def
mock_get_e
nterprise_customer
(
**
kwargs
):
uuid
=
kwargs
.
get
(
'enterprise_customer_identity_provider__provider_id'
)
if
uuid
:
return
mock
.
MagicMock
(
uuid
=
uuid
)
return
mock
.
MagicMock
(
uuid
=
uuid
,
user
=
self
.
user
)
raise
Exception
mock_ec_model
.
objects
.
get
.
side_effect
=
mock_get_ec
mock_ec_model
.
DoesNotExist
=
Exception
dummy_request
=
mock
.
MagicMock
(
session
=
{},
user
=
self
.
user
)
mock_enterprise_customer_model
.
objects
.
get
.
side_effect
=
mock_get_enterprise_customer
mock_enterprise_customer_model
.
DoesNotExist
=
Exception
mock_partial
.
return_value
=
True
mock_registry
.
get_from_pipeline
.
return_value
.
provider_id
=
'real-ent-uuid'
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
"real"
:
"enterprisecustomer"
},
200
)
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
'real'
:
'enterprisecustomer'
},
200
)
e
c
=
enterprise_customer_for_request
(
mock
.
MagicMock
()
)
e
nterprise_customer
=
enterprise_customer_for_request
(
dummy_request
)
self
.
assertEqual
(
e
c
,
{
"real"
:
"enterprisecustomer"
})
self
.
assertEqual
(
e
nterprise_customer
,
{
'real'
:
'enterprisecustomer'
})
httpretty
.
reset
()
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
"detail"
:
"Not found."
},
404
)
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
'detail'
:
'Not found.'
},
404
)
e
c
=
enterprise_customer_for_request
(
mock
.
MagicMock
()
)
e
nterprise_customer
=
enterprise_customer_for_request
(
dummy_request
)
self
.
assertIsNone
(
e
c
)
self
.
assertIsNone
(
e
nterprise_customer
)
mock_registry
.
get_from_pipeline
.
return_value
.
provider_id
=
None
httpretty
.
reset
()
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
"real"
:
"enterprisecustomer"
},
200
)
self
.
mock_get_enterprise_customer
(
'real-ent-uuid'
,
{
'real'
:
'enterprisecustomer'
},
200
)
ec
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{
"enterprise_customer"
:
'real-ent-uuid'
}))
enterprise_customer
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{
'enterprise_customer'
:
'real-ent-uuid'
},
user
=
self
.
user
)
)
self
.
assertEqual
(
e
c
,
{
"real"
:
"enterprisecustomer"
})
self
.
assertEqual
(
e
nterprise_customer
,
{
'real'
:
'enterprisecustomer'
})
e
c
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{},
COOKIES
=
{
settings
.
ENTERPRISE_CUSTOMER_COOKIE_NAME
:
'real-ent-uuid'
})
e
nterprise_customer
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{},
COOKIES
=
{
settings
.
ENTERPRISE_CUSTOMER_COOKIE_NAME
:
'real-ent-uuid'
}
,
user
=
self
.
user
)
)
self
.
assertEqual
(
e
c
,
{
"real"
:
"enterprisecustomer"
})
self
.
assertEqual
(
e
nterprise_customer
,
{
'real'
:
'enterprisecustomer'
})
mock_get_el_data
.
return_value
=
[{
'enterprise_customer'
:
{
'uuid'
:
'real-ent-uuid'
}}]
e
c
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{},
COOKIES
=
{},
user
=
mock
.
MagicMock
(
is_authenticated
=
lambda
:
True
)
,
site
=
1
)
e
nterprise_customer
=
enterprise_customer_for_request
(
mock
.
MagicMock
(
GET
=
{},
COOKIES
=
{},
user
=
self
.
user
,
site
=
1
)
)
self
.
assertEqual
(
e
c
,
{
"real"
:
"enterprisecustomer"
})
self
.
assertEqual
(
e
nterprise_customer
,
{
'real'
:
'enterprisecustomer'
})
def
check_data_sharing_consent
(
self
,
consent_required
=
False
,
consent_url
=
None
):
"""
...
...
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