diff --git a/src/consents/tests/tests_consent_operations.py b/src/consents/tests/tests_consent_operations.py index 926b613218c6d0cf70d420e4a0664f2c46f227b1..13c22c4927d1f8422a821c2c39a2dc2878d452f4 100644 --- a/src/consents/tests/tests_consent_operations.py +++ b/src/consents/tests/tests_consent_operations.py @@ -786,46 +786,14 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase): self.assertEqual(response.status_code, 200) group_id = ProviderVerbGroup.objects.latest('id').id # Create user consent for first provider schema version - first_provider_schema_consent = [ - { - "providerId": Provider.objects.latest('id').id, - "providerSchemaId": ProviderSchema.objects.latest('id').id, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": False, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/attempted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/interacted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/answered", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]', - }, - ], - } - ] response = self.user_client.post( - "/api/v1/consents/user/save", first_provider_schema_consent, format="json" + "/api/v1/consents/user/save", {"groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 200) user_consents = UserConsents.objects.filter(user__email=self.test_user_email) self.assertEqual(len(user_consents), 4) - self.assertFalse( + self.assertTrue( UserConsents.objects.get( verb__verb_id="http://h5p.example.com/expapi/verbs/experienced", active=True @@ -874,44 +842,22 @@ class TestUserConsentSaveUpdatedProviderSchema(BaseTestCase): self.assertEqual(response.status_code, 200) group_id = ProviderVerbGroup.objects.latest('id').id - second_provider_schema_consent = [ - { - "providerId": Provider.objects.latest('id').id, - "providerSchemaId": ProviderSchema.objects.latest('id').id, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/attempted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]', - }, - ], - } - ] response = self.user_client.post( - "/api/v1/consents/user/save", second_provider_schema_consent, format="json" + "/api/v1/consents/user/save", {"groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 200) - user_consents = UserConsents.objects.filter(user__email=self.test_user_email, active=True) - self.assertEqual(len(user_consents), 2) self.assertTrue( - UserConsents.objects.get( + UserConsents.objects.filter( verb__verb_id="http://h5p.example.com/expapi/verbs/experienced", active=True - ).consented + ).first().consented ) self.assertTrue( - UserConsents.objects.get( + UserConsents.objects.filter( verb__verb_id="http://h5p.example.com/expapi/verbs/attempted", active=True - ).consented + ).first().consented ) @@ -1028,13 +974,13 @@ class TestMultiUserConsent(BaseTestCase): "/api/v1/consents/user/save", data=user_consent_1, format="json" ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["message"], "user consent saved") + self.assertEqual(response.json()["message"], "user consents saved") response = self.user_client_2.post( "/api/v1/consents/user/save", data=user_consent_2, format="json" ) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["message"], "user consent saved") + self.assertEqual(response.json()["message"], "user consents saved") self.assertTrue( UserConsents.objects.get( @@ -1042,7 +988,7 @@ class TestMultiUserConsent(BaseTestCase): verb__verb_id="http://h5p.example.com/expapi/verbs/experienced", ).consented ) - self.assertFalse( + self.assertTrue( UserConsents.objects.get( user__email=self.user_email_2, verb__verb_id="http://h5p.example.com/expapi/verbs/experienced", diff --git a/src/consents/tests/tests_paused_data_recording.py b/src/consents/tests/tests_paused_data_recording.py index 3f1aaeb1ed2005110f53c78ff27e93430212ca24..a8a3548f199d0e357b8497187d81e9f0d6399f7d 100644 --- a/src/consents/tests/tests_paused_data_recording.py +++ b/src/consents/tests/tests_paused_data_recording.py @@ -114,40 +114,8 @@ class TestPauseDataRecording(BaseTestCase): self.assertEqual(response.status_code, 200) group_id = ProviderVerbGroup.objects.latest('id').id - user_consent = [ - { - "providerId": 1, - "providerSchemaId": 1, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": False, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/attempted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/interacted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/answered", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]', - }, - ], - } - ] # Attempt to save user consent response = self.user_client.post( - "/api/v1/consents/user/save", user_consent, format="json" + "/api/v1/consents/user/save", {"groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 200) \ No newline at end of file diff --git a/src/consents/tests/tests_third_party.py b/src/consents/tests/tests_third_party.py index ac44d8ecf7b3703667ab22eedc6478d1ad6be24a..a1aae6a90770af88a329196e4a36cda57fd7aaa1 100644 --- a/src/consents/tests/tests_third_party.py +++ b/src/consents/tests/tests_third_party.py @@ -129,40 +129,8 @@ class TestThirdPartyGetUserStatus(BaseTestCase): def test_user_status_none_empty(self): group_id = ProviderVerbGroup.objects.latest('id').id # Create user consent for first provider schema version - first_provider_schema_consent = [ - { - "providerId": 1, - "providerSchemaId": 1, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": False, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":false}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/attempted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/interacted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/answered", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]', - }, - ], - } - ] response = self.user_client.post( - "/api/v1/consents/user/save", first_provider_schema_consent, format="json" + "/api/v1/consents/user/save", {"groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 200) @@ -175,9 +143,6 @@ class TestThirdPartyGetUserStatus(BaseTestCase): responseDict = json.loads(str(response.content, encoding="utf8")) self.assertEqual(responseDict["consent"]["id"], 1) self.assertEqual(responseDict["paused_data_recording"], False) - self.assertEqual( - responseDict["consent"]["groups"][0]["verbs"][0]["consented"], False - ) self.assertEqual( responseDict["consent"]["groups"][0]["verbs"][1]["consented"], True ) @@ -271,39 +236,9 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase): """ provider = ProviderAuthorization.objects.get(provider__name="H5P") group_id = ProviderVerbGroup.objects.latest('id').id - payload = { - "user_id": self.test_user_email, - "provider_schema_id": provider.id, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": False, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/attempted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/interacted", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]', - }, - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/answered", - "consented": True, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":true}]', - }, - ], - } response = self.third_party_client.post( - "/api/v1/consents/user/save/third-party", data=payload, format="json" + "/api/v1/consents/user/save/third-party", data={"user_id": self.test_user_email, "groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 200) @@ -316,9 +251,6 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase): responseDict = json.loads(str(response.content, encoding="utf8")) self.assertEqual(responseDict["consent"]["id"], 1) self.assertEqual(responseDict["paused_data_recording"], False) - self.assertEqual( - responseDict["consent"]["groups"][0]["verbs"][0]["consented"], False - ) self.assertEqual( responseDict["consent"]["groups"][0]["verbs"][1]["consented"], True ) @@ -336,20 +268,12 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase): group_id = ProviderVerbGroup.objects.latest('id').id payload = { "user_id": self.test_user_email, - "provider_schema_id": 1, - "verbs": [ - { - "group_id": group_id, - "id": "http://h5p.example.com/expapi/verbs/experienced", - "consented": False, - "objects": '[{"id":"http://h5p.example.com/expapi/activity/QKGPPiIhI4zx9YAZZksLKigqyf7yW4WF","label":"1.1.1 Funktionen","defaultConsent":true,"definition":{"name":{"enUS":"1.1.1 Funktionen"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/attempted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/VeH7S8NeGlCRM1myYRDBjHMCknLqDLgm","label":"2.3.1 Funktion Zirkulationsleitung","defaultConsent":true,"definition":{"name":{"enUS":"2.3.1 Funktion Zirkulationsleitung"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/interacted","consented":true,"objects":"[{"id":"http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46","label":"1.2.3 Kappenventil","defaultConsent":true,"definition":{"name":{"enUS":"1.2.3 Kappenventil"}},"consented":true}]"},{"provider":1,"id":"http://h5p.example.com/expapi/verbs/answered","consented":false,"objects":"[{"id":"http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b","label":"7.2.1 Ventil Basics","defaultConsent":false,"definition":{"name":{"enUS":"7.2.1 Ventil Basics"}},"consented":false}]', - } - ], + "groups": [group_id] } client = APIClient() response = client.post( - "/api/v1/consents/user/save/third-party", json.dumps(payload), format="json" + "/api/v1/consents/user/save/third-party", {"groups": [group_id]}, format="json" ) self.assertEqual(response.status_code, 401) self.assertJSONEqual( diff --git a/src/consents/views.py b/src/consents/views.py index c13013651d77ec21b6cfff78da2c872db309b804..96182392d5c4d39e9aeaf04f2a2b99a3ac71410b 100644 --- a/src/consents/views.py +++ b/src/consents/views.py @@ -80,7 +80,7 @@ def merge_consents(provider_schema: ProviderSchema, user_verbs): user_verb = find_user_verb(verb["id"], user_verbs) if user_verb: verb.update({"consented": user_verb.consented}) - verb.update({"objects": json.loads(user_verb.object)}) + verb.update({"objects": json.loads(user_verb.object) if isinstance(user_verb.object, str) else user_verb.object}) all_consented = all_consented and user_verb.consented else: all_consented = False group["consented"] = all_consented @@ -840,8 +840,6 @@ class GetUsersConsentsThirdPartyView(APIView): class SaveUserConsentThirdPartyView(APIView): """ Saves a user consent, with authentication taking place via application tokens. - The provider schema id for each provider must always refer to the latest schema version of the respective provider, - otherwise the request will be rejected. """ def post(self, request): @@ -861,32 +859,30 @@ class SaveUserConsentThirdPartyView(APIView): email = shib_connector_resolver(email=email, provider=provider) user = CustomUser.objects.filter(email=email).first() + for group_id in serializer.validated_data["groups"]: + group = ProviderVerbGroup.objects.filter(pk=group_id).first() + for verb in group.verbs.all(): + object_consent_data = [] + for obj in verb.verbobject_set.all(): + object_consent_data.append({ + "id": obj.object_id, + "label": obj.label, + "defaultConsent": verb.default_consent, + "matching": obj.matching, + "definition": obj.definition, + "consented": True + }) + consent = UserConsents.objects.create( + user=user, + consented=True, + provider=group.provider, + verb_group=group, + verb=verb, + object=object_consent_data, + active=True + ) - # Validate user provider setting refers to the latest provider schema id - latest_provider_schema = ProviderSchema.objects.get( - provider=provider.provider, superseded_by=None - ) - - if ( - serializer.validated_data["provider_schema_id"].id - != latest_provider_schema.id - ): - return JsonResponse( - {"message": "provider schema id isn't latest provider schema id"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - save_user_consent( - user, - serializer.validated_data["provider_schema_id"], - serializer.validated_data["verbs"], - ) - - # Add provider schema id to accepted ones for user - user.accepted_provider_schemas.add( - serializer.validated_data["provider_schema_id"] - ) - user.save() return JsonResponse( - {"message": "user consent saved"}, status=status.HTTP_200_OK + {"message": "user consents saved"}, + status=status.HTTP_200_OK, ) diff --git a/src/providers/serializers.py b/src/providers/serializers.py index 18a5270b58ee771d1c1176b7fa6d586cbebd1a23..08f560f200713ceba3e90e24769b7007e8cc8ec5 100644 --- a/src/providers/serializers.py +++ b/src/providers/serializers.py @@ -144,10 +144,16 @@ class GetUsersConsentsThirdPartySerializer(serializers.Serializer): class ConsentUserVerbThirdPartySerializer(serializers.Serializer): user_id = serializers.CharField() - #user_id = serializers.SlugRelatedField( - # queryset=CustomUser.objects.all(), slug_field="email" - #) - provider_schema_id = serializers.PrimaryKeyRelatedField( - queryset=ProviderSchema.objects.all() - ) - verbs = serializers.ListSerializer(child=ConsentVerbSerializer()) + + def validate(self, data): + """ + Validate existence of submitted groups. + """ + for group_id in data["groups"]: + if not ProviderVerbGroup.objects.filter(id=group_id): + raise serializers.ValidationError( + f"Group with id {group_id} does not exist." + ) + return data + + groups = serializers.ListSerializer(child=serializers.IntegerField()) diff --git a/src/xapi/views.py b/src/xapi/views.py index e5420efb80c71da0327ff17e64d3fbdb33b377cf..f7a5b29c9c053b7a97e180e246fa5a4a34266461 100644 --- a/src/xapi/views.py +++ b/src/xapi/views.py @@ -231,7 +231,8 @@ def process_statement(x_api_statement, provider, latest_schema): # has the user given consent to the object at hand? object_consent = False - verb_objects = json.loads(user_consent.object) + verb_objects = json.loads(user_consent.object) if isinstance(user_consent.object, str) else user_consent.object + object_statement = x_api_statement["object"]["definition"] if not verb_objects or len(verb_objects) == 0: # no objects defined, "consent" by default object_consent = True