Commit 6ddbbf41 by Jesse Shapiro Committed by Jesse Shapiro

New consent API

parent d4f5c78d
...@@ -409,6 +409,10 @@ class SiteConfiguration(models.Model): ...@@ -409,6 +409,10 @@ class SiteConfiguration(models.Model):
return EdxRestApiClient(self.enterprise_api_url, jwt=self.access_token) return EdxRestApiClient(self.enterprise_api_url, jwt=self.access_token)
@cached_property @cached_property
def consent_api_client(self):
return EdxRestApiClient(self.build_lms_url('/consent/api/v1/'), jwt=self.access_token, append_slash=False)
@cached_property
def user_api_client(self): def user_api_client(self):
""" """
Returns the API client to access the user API endpoint on LMS. Returns the API client to access the user API endpoint on LMS.
......
...@@ -465,8 +465,14 @@ class CouponRedeemViewTests(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin, En ...@@ -465,8 +465,14 @@ class CouponRedeemViewTests(CouponMixin, DiscoveryTestMixin, LmsApiMockMixin, En
catalog=catalog catalog=catalog
) )
self.request.user = self.user self.request.user = self.user
self.mock_enterprise_learner_api(consent_enabled=consent_enabled, consent_provided=consent_provided) self.mock_consent_response(
self.mock_enterprise_course_enrollment_api(results_present=False) self.user.username,
course_id,
ENTERPRISE_CUSTOMER,
granted=consent_provided,
required=(consent_enabled and not consent_provided),
exists=(not consent_provided),
)
self.mock_account_api(self.request, self.user.username, data={'is_active': True}) self.mock_account_api(self.request, self.user.username, data={'is_active': True})
self.mock_access_token_response() self.mock_access_token_response()
self.mock_specific_enterprise_customer_api(uuid=ENTERPRISE_CUSTOMER, consent_enabled=consent_enabled) self.mock_specific_enterprise_customer_api(uuid=ENTERPRISE_CUSTOMER, consent_enabled=consent_enabled)
......
...@@ -420,3 +420,57 @@ class EnterpriseServiceMockMixin(object): ...@@ -420,3 +420,57 @@ class EnterpriseServiceMockMixin(object):
body=enterprise_enrollment_api_response_json, body=enterprise_enrollment_api_response_json,
content_type='application/json' content_type='application/json'
) )
def mock_consent_response(
self,
username,
course_id,
ec_uuid,
method=httpretty.GET,
granted=True,
required=False,
exists=True,
response_code=None
):
response_body = {
'username': username,
'course_id': course_id,
'enterprise_customer_uuid': ec_uuid,
'consent_provided': granted,
'consent_required': required,
'exists': exists,
}
httpretty.register_uri(
method=method,
uri=self.site.siteconfiguration.build_lms_url('/consent/api/v1/data_sharing_consent'),
content_type='application/json',
body=json.dumps(response_body),
status=response_code or 200,
)
def mock_consent_get(self, username, course_id, ec_uuid):
self.mock_consent_response(
username,
course_id,
ec_uuid
)
def mock_consent_missing(self, username, course_id, ec_uuid):
self.mock_consent_response(
username,
course_id,
ec_uuid,
exists=False,
granted=False,
required=True,
)
def mock_consent_not_required(self, username, course_id, ec_uuid):
self.mock_consent_response(
username,
course_id,
ec_uuid,
exists=False,
granted=False,
required=False,
)
...@@ -87,65 +87,26 @@ class EnterpriseUtilsTests(EnterpriseServiceMockMixin, TestCase): ...@@ -87,65 +87,26 @@ class EnterpriseUtilsTests(EnterpriseServiceMockMixin, TestCase):
self.assertDictContainsSubset(expected_return, response) self.assertDictContainsSubset(expected_return, response)
@ddt.data( @httpretty.activate
(True, True), def test_ecu_needs_consent(self):
(False, False), opts = {
) 'ec_uuid': 'fake-uuid',
@ddt.unpack 'course_id': 'course-v1:real+course+id',
def test_ecu_needs_consent_no_link(self, ec_consent_enabled, expected_consent_requirement): 'username': 'johnsmith',
""" }
Test that when there's no EnterpriseCustomerUser, the consent requirement comes down kw = {
to whether the EnterpriseCustomer wants consent. 'enterprise_customer_uuid': 'fake-uuid',
""" 'course_id': 'course-v1:real+course+id',
self.mock_access_token_response() 'username': 'johnsmith',
self.mock_enterprise_learner_api_for_learner_with_no_enterprise() 'site': self.site
enterprise_customer_uuid = TEST_ENTERPRISE_CUSTOMER_UUID }
self.mock_specific_enterprise_customer_api(enterprise_customer_uuid, consent_enabled=ec_consent_enabled)
consent_needed = enterprise_customer_user_needs_consent(
self.site,
TEST_ENTERPRISE_CUSTOMER_UUID,
'course-v1:edX+DemoX+Demo_Course',
'admin'
)
self.assertEqual(consent_needed, expected_consent_requirement)
@ddt.data(
(True, False, True, True, False),
(False, True, True, True, False),
(False, False, True, True, True),
(False, False, True, False, True),
(False, False, False, True, False),
(False, False, False, False, False),
)
@ddt.unpack
def test_ecu_needs_consent_link_exists(
self,
account_consent_provided,
course_consent_provided,
consent_enabled,
results_present,
expected_consent_requirement
):
self.mock_access_token_response() self.mock_access_token_response()
enterprise_customer_uuid = TEST_ENTERPRISE_CUSTOMER_UUID self.mock_consent_get(**opts)
self.mock_enterprise_learner_api( self.assertEqual(enterprise_customer_user_needs_consent(**kw), False)
enterprise_customer_uuid=enterprise_customer_uuid, self.mock_consent_missing(**opts)
consent_provided=account_consent_provided, self.assertEqual(enterprise_customer_user_needs_consent(**kw), True)
consent_enabled=consent_enabled, self.mock_consent_not_required(**opts)
) self.assertEqual(enterprise_customer_user_needs_consent(**kw), False)
self.mock_enterprise_course_enrollment_api(
consent_granted=course_consent_provided,
results_present=results_present,
)
consent_needed = enterprise_customer_user_needs_consent(
self.site,
TEST_ENTERPRISE_CUSTOMER_UUID,
'course-v1:edX+DemoX+Demo_Course',
'admin'
)
self.assertEqual(consent_needed, expected_consent_requirement)
def test_get_enterprise_customer_uuid(self): def test_get_enterprise_customer_uuid(self):
""" """
......
...@@ -158,60 +158,6 @@ def get_or_create_enterprise_customer_user(site, enterprise_customer_uuid, usern ...@@ -158,60 +158,6 @@ def get_or_create_enterprise_customer_user(site, enterprise_customer_uuid, usern
return response return response
def enterprise_customer_needs_consent(enterprise_customer_data):
"""
Determine if consent should be prompted for on this enterprise customer.
Args:
enterprise_customer_data: A dictionary isomorphic with the EnterpriseCustomer
object returned by various endpoints of the Enterprise API.
Returns:
bool: Whether, in general, a user must provide consent to use offers provided by this EnterpriseCustomer.
"""
if not enterprise_customer_data['enable_data_sharing_consent']:
return False
return enterprise_customer_data['enforce_data_sharing_consent'] in ('at_login', 'at_enrollment')
def enterprise_customer_user_consent_provided(ec_user_data):
"""
Determine if the EnterpriseCustomerUser has provided consent at an account level.
Args:
ec_user_data: A dictionary isomorphic with the EnterpriseCustomerUser
object returned by various endpoints of the Enterprise API.
"""
return ec_user_data['data_sharing_consent'] and ec_user_data['data_sharing_consent'][0]['enabled']
def get_enterprise_customer_user(site, username, enterprise_customer_uuid):
"""
Get the EnterpriseCustomerUser with a particular username and linked to a particular
EnterpriseCustomer if it exists; otherwise, return None.
Args:
site (Site): The site which is handling the current request
username (str): The username of the user in the LMS
enterprise_customer_uuid (str): The UUID of the EnterpriseCustomer in the LMS
Returns:
dict: The single EnterpriseCustomerUser structure provided by the API
NoneType: Returns None if no EnterpriseCustomerUser is found
"""
api = site.siteconfiguration.enterprise_api_client
api_resource = 'enterprise-learner'
endpoint = getattr(api, api_resource)
response = endpoint.get(
enterprise_customer=str(enterprise_customer_uuid),
username=str(username),
)
results = response.get('results')
return results[0] if results else None
def get_enterprise_course_enrollment(site, enterprise_customer_user, course_id): def get_enterprise_course_enrollment(site, enterprise_customer_user, course_id):
""" """
Get the EnterpriseCourseEnrollment between a particular EnterpriseCustomerUser and Get the EnterpriseCourseEnrollment between a particular EnterpriseCustomerUser and
...@@ -254,29 +200,13 @@ def enterprise_customer_user_needs_consent(site, enterprise_customer_uuid, cours ...@@ -254,29 +200,13 @@ def enterprise_customer_user_needs_consent(site, enterprise_customer_uuid, cours
that the EnterpriseCustomer specified by the enterprise_customer_uuid that the EnterpriseCustomer specified by the enterprise_customer_uuid
argument provides for the course specified by the course_id argument. argument provides for the course specified by the course_id argument.
""" """
account_consent_provided = False consent_client = site.siteconfiguration.consent_api_client
course_consent_provided = False endpoint = consent_client.data_sharing_consent
return endpoint.get(
ec_user = get_enterprise_customer_user(site, username, enterprise_customer_uuid) username=username,
enterprise_customer_uuid=enterprise_customer_uuid,
if ec_user: course_id=course_id
account_consent_provided = enterprise_customer_user_consent_provided(ec_user) )['consent_required']
enterprise_customer = ec_user['enterprise_customer']
else:
enterprise_customer = get_enterprise_customer(site, enterprise_customer_uuid)
consent_needed = enterprise_customer_needs_consent(enterprise_customer)
if consent_needed and not account_consent_provided and ec_user:
existing_course_enrollment = get_enterprise_course_enrollment(
site,
course_id=str(course_id),
enterprise_customer_user=ec_user['id'],
)
if existing_course_enrollment:
course_consent_provided = existing_course_enrollment.get('consent_granted', False)
return consent_needed and not (account_consent_provided or course_consent_provided)
def get_enterprise_customer_from_voucher(site, voucher): def get_enterprise_customer_from_voucher(site, voucher):
......
...@@ -147,7 +147,7 @@ class EnrollmentFulfillmentModule(BaseFulfillmentModule): ...@@ -147,7 +147,7 @@ class EnrollmentFulfillmentModule(BaseFulfillmentModule):
pass pass
if enterprise_customer_uuid is not None: if enterprise_customer_uuid is not None:
data['enterprise_course_consent'] = True data['linked_enterprise_customer'] = str(enterprise_customer_uuid)
break break
# If an EnterpriseCustomer UUID is associated with the coupon, create an EnterpriseCustomerUser # If an EnterpriseCustomer UUID is associated with the coupon, create an EnterpriseCustomerUser
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment