Skip to content
Snippets Groups Projects
Commit 2fa1b878 authored by Lennard Strohmeyer's avatar Lennard Strohmeyer :penguin:
Browse files

rework to use groups as main model for consents, implemented control center

parent be37674a
No related branches found
Tags v1.0.1.rc8
No related merge requests found
Pipeline #1615065 failed
Showing
with 1770 additions and 861 deletions
from django.db import migrations, models
import django.db.models.deletion
def migrate_user_consent_to_verb(apps, schema_editor):
UserConsents = apps.get_model('consents', 'UserConsents')
Verb = apps.get_model('providers', 'Verb')
for consent in UserConsents.objects.all():
schema = consent.provider_schema
verb_id = consent.verb
verb = Verb.objects.get(verb_id=verb_id, provider_schema=schema)
consent.verb = verb.id
group = verb.providerverbgroup_set.first()
consent.verb_group_id = group.id
consent.save()
class Migration(migrations.Migration):
dependencies = [
('providers', '0008_verb_remove_providerschema_essential_verbs_and_more'),
('consents', '0004_userconsents_active'),
]
operations = [
migrations.AddField(
model_name='userconsents',
name='verb_group',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE,
to='providers.providerverbgroup'),
),
migrations.RunPython(migrate_user_consent_to_verb, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='userconsents',
name='verb',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='providers.verb'),
),
migrations.RemoveField(
model_name='userconsents',
name='provider_schema',
),
migrations.AlterField( # now we can remove the null default
model_name='userconsents',
name='verb_group',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='providers.providerverbgroup'),
)
]
......@@ -3,19 +3,19 @@ from email.policy import default
from django.conf import settings
from django.db import models
from providers.models import Provider, ProviderSchema
from providers.models import Provider, ProviderVerbGroup, Verb
class UserConsents(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
consented = models.BooleanField()
provider_schema = models.ForeignKey(ProviderSchema, on_delete=models.CASCADE)
provider = models.ForeignKey(Provider, on_delete=models.CASCADE)
verb = models.CharField(db_index=True, max_length=500)
verb_group = models.ForeignKey(ProviderVerbGroup, default=None, on_delete=models.CASCADE)
verb = models.ForeignKey(Verb, on_delete=models.CASCADE)
object = models.JSONField()
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
def __str__(self):
return "User consent: " + self.verb
return "User consent for verb " + str(self.verb)
import json
from typing import Dict
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers
from providers.models import AnalyticsToken, Provider, ProviderSchema
from providers.models import AnalyticsToken, Provider, ProviderSchema, Verb, VerbObject, ProviderVerbGroup
class ProviderSchemaSerializer(serializers.ModelSerializer):
......@@ -14,8 +15,8 @@ class ProviderSchemaSerializer(serializers.ModelSerializer):
"id": obj.provider.definition_id,
"name": obj.provider.name,
"description": obj.provider.description,
"groups": obj.groups,
"essential_verbs": obj.essential_verbs
"verbs": VerbSerializer(obj.verbs(), many=True).data,
"essential_verbs": VerbSerializer(obj.essential_verbs(), many=True).data
}
class Meta:
......@@ -23,21 +24,94 @@ class ProviderSchemaSerializer(serializers.ModelSerializer):
fields = "__all__"
class VerbSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField("_id")
label = serializers.SerializerMethodField("_label")
description = serializers.SerializerMethodField("_description")
defaultConsent = serializers.SerializerMethodField("_defaultConsent")
objects = serializers.SerializerMethodField("_objects")
def _id(self, obj: Verb):
return obj.verb_id
def _label(self, obj: Verb):
return obj.label
def _description(self, obj: Verb):
return obj.description
def _defaultConsent(self, obj: Verb):
return obj.default_consent
def _objects(self, obj: Verb):
return VerbObjectSerializer(VerbObject.objects.filter(verb=obj), many=True).data
class Meta:
model = Verb
exclude = ["verb_id", "default_consent", "provider", "provider_schema", "active",
"essential", "allow_anonymized_collection"]
class VerbObjectSerializer(serializers.ModelSerializer):
id = serializers.SerializerMethodField("_id")
objectType = serializers.SerializerMethodField("_objectType")
defaultConsent = serializers.SerializerMethodField("_defaultConsent")
definition = serializers.SerializerMethodField("_definition")
def _id(self, obj: VerbObject):
return obj.object_id
def _objectType(self, obj: VerbObject):
return obj.object_type
def _definition(self, obj: VerbObject):
return obj.definition
def _defaultConsent(self, obj: VerbObject):
return obj.verb.default_consent
class Meta:
model = VerbObject
exclude = ["object_id", "verb"]
class ProviderVerbGroupSerializer(serializers.ModelSerializer):
verbs = serializers.SerializerMethodField("_verbs")
purposeOfCollection = serializers.SerializerMethodField("_purposeOfCollection")
def _verbs(self, obj):
return VerbSerializer(obj.verbs.all(), many=True).data
def _purposeOfCollection(self, obj):
return obj.purpose_of_collection
class Meta:
model = ProviderVerbGroup
exclude = ["purpose_of_collection"]
class ProvidersSerializer(serializers.ModelSerializer):
versions = serializers.SerializerMethodField("_versions")
groups = serializers.SerializerMethodField("_groups")
def _versions(self, obj):
schemas = ProviderSchema.objects.filter(provider=obj).order_by("-created")
serializer = ProviderSchemaSerializer(schemas, many=True)
return serializer.data
def _groups(self, obj):
groups = ProviderVerbGroup.objects.filter(provider=obj).order_by("-created")
serializer = ProviderVerbGroupSerializer(groups, many=True)
return serializer.data
class Meta:
model = Provider
fields = ["id", "name", "versions"]
fields = ["id", "name", "groups", "versions"]
class UserVerbSerializer(serializers.Serializer):
id = serializers.CharField()
group_id = serializers.PrimaryKeyRelatedField(
queryset=ProviderVerbGroup.objects.all()
)
consented = serializers.BooleanField()
objects = serializers.CharField()
......@@ -89,6 +163,40 @@ class SaveUserConsentSerializer(serializers.Serializer):
verbs = serializers.ListSerializer(child=UserVerbSerializer())
class GroupVerbSerializer(serializers.Serializer):
id = serializers.CharField()
class CreateProviderVerbGroupSerializer(serializers.Serializer):
def __init__(self, provider, **kwargs):
super().__init__(**kwargs)
self.provider = provider
def validate(self, data):
"""
Validate the existence of verbs associated with the new / updated group
and check if the chosen group ID is available.
"""
# new or updated groups have to use the newest schema
provider_schema = ProviderSchema.objects.get(provider=self.provider, superseded_by__isnull=True)
for verb_data in data["verbs"]:
try:
# we just validate verb IDs here since verbs are retrieved via the verb ID later
verb = Verb.objects.filter(verb_id=verb_data["id"], active=True,
provider=self.provider,
provider_schema=provider_schema)
except ObjectDoesNotExist:
raise serializers.ValidationError(
f"Verb {verb['id']} not found in newest schema."
)
return data
id = serializers.CharField(allow_blank=True)
label = serializers.CharField()
description = serializers.CharField()
purposeOfCollection = serializers.CharField()
requiresConsent = serializers.BooleanField()
verbs = serializers.ListSerializer(child=GroupVerbSerializer())
class CreateUserSerializer(serializers.Serializer):
email = serializers.CharField()
first_name = serializers.CharField()
......
This diff is collapsed.
......@@ -6,95 +6,101 @@ from providers.models import Provider
class TestProviderSchemaCreation(BaseTestCase):
def test_create_provider_schema_fails(self):
def test_create_provider_verb_groups(self):
"""
Ensure provider schema with duplicate group ids fails.
Test verb group creation and update.
"""
provider_schema = {
"id": "h5p-0",
"name": "H5P",
"description": "Open-source content collaboration framework",
"groups": [
"verbs": [
{
"id": "default_group",
"label": "Default group",
"description": "default",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"verbs": [
"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
"label": "Unlocked",
"description": "Actor unlocked an object",
"defaultConsent": True,
"objects": [
{
"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked",
"label": "Unlocked",
"description": "Actor unlocked an object",
"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
"label": "Course",
"defaultConsent": True,
"objects": [
{
"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/my-random-object-id",
"label": "Course",
"defaultConsent": True,
"matching": "id",
"definition": {
"type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
"name": {
"enUS": "A course within an LMS. Contains learning materials and activities"
},
},
"matching": "id",
"definition": {
"type": "https://xapi.elearn.rwth-aachen.de/definitions/lms/activities/course",
"name": {
"enUS": "A course within an LMS. Contains learning materials and activities"
},
],
}
},
},
],
"isDefault": True,
},
{
"id": "default_group",
"label": "Group 2",
"id": "http://h5p.example.com/expapi/verbs/interacted",
"label": "Interacted",
"description": "Lorem ipsum",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"verbs": [
"defaultConsent": True,
"objects": [
{
"id": "http://h5p.example.com/expapi/verbs/interacted",
"label": "Interacted",
"description": "Lorem ipsum",
"id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
"label": "1.2.3 Kappenventil",
"defaultConsent": True,
"objects": [
{
"id": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
"label": "1.2.3 Kappenventil",
"defaultConsent": True,
"matching": "definitionType",
"definition": {
"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
"name": {"enUS": "1.2.3 Kappenventil"},
},
}
],
},
"matching": "definitionType",
"definition": {
"type": "http://h5p.example.com/expapi/activity/ofN2ODcnLRaVu30lUpzrWPqF2AcG7g46",
"name": {"enUS": "1.2.3 Kappenventil"},
},
}
],
},
{
"id": "http://h5p.example.com/expapi/verbs/answered",
"label": "Answered",
"description": "lorem ipsum",
"defaultConsent": False,
"objects": [
{
"id": "http://h5p.example.com/expapi/verbs/answered",
"label": "Answered",
"description": "lorem ipsum",
"id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
"label": "7.2.1 Ventil Basics",
"defaultConsent": False,
"objects": [
{
"id": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
"label": "7.2.1 Ventil Basics",
"defaultConsent": False,
"matching": "definitionType",
"definition": {
"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
"name": {"enUS": "7.2.1 Ventil Basics"},
},
}
],
},
"matching": "definitionType",
"definition": {
"type": "http://h5p.example.com/expapi/activity/K34IszYvGE4R0cC72Ean6msLfLCJtQ8b",
"name": {"enUS": "7.2.1 Ventil Basics"},
},
}
],
"isDefault": False,
},
}
],
"essentialVerbs": [],
}
verb_groups = [
{
"id": "default_group",
"label": "Default group",
"description": "default",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"requiresConsent": True,
"verbs": [
{"id": "https://xapi.elearn.rwth-aachen.de/definitions/lms/verbs/unlocked"}
]
},
{
"id": "default_group",
"label": "Group 2",
"description": "Lorem ipsum",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"requiresConsent": True,
"verbs": [
{"id": "http://h5p.example.com/expapi/verbs/interacted"},
{"id": "http://h5p.example.com/expapi/verbs/answered"},
]
}
]
# Upload provider schema
with StringIO(json.dumps(provider_schema)) as fp:
response = self.provider_client.put(
......@@ -102,7 +108,22 @@ class TestProviderSchemaCreation(BaseTestCase):
{"provider-schema": fp},
format="multipart",
)
self.assertEqual(response.status_code, 400)
self.assertEqual(
response.json()["message"], "Provider schema contains ambiguous group ids."
self.assertEqual(response.status_code, 201)
# create some verb groups
for group in verb_groups:
group["provider_id"] = Provider.objects.latest('id').id
response = self.provider_client.post(
"/api/v1/consents/provider/create-verb-group",
group,
format="json",
)
self.assertEqual(response.status_code, 200)
# one more time to simulate update
for group in verb_groups:
group["provider_id"] = Provider.objects.latest('id').id
response = self.provider_client.post(
"/api/v1/consents/provider/create-verb-group",
group,
format="json",
)
self.assertEqual(response.status_code, 200)
\ No newline at end of file
......@@ -2,7 +2,7 @@ import json
from io import StringIO
from consents.tests.tests_consent_operations import BaseTestCase
from providers.models import Provider
from providers.models import Provider, ProviderVerbGroup
class TestPauseDataRecording(BaseTestCase):
......@@ -32,7 +32,7 @@ class TestPauseDataRecording(BaseTestCase):
def test_pause_data_recording(self):
"""
Ensure data recording can be turn on and off.
Ensure data recording can be turned on and off.
"""
# Create provider H5P
with StringIO(json.dumps(self.h5p_provider_schema)) as fp:
......@@ -97,31 +97,48 @@ class TestPauseDataRecording(BaseTestCase):
{"consent": None, "paused_data_recording": True}
)
# create a verb group
group = {"provider_id": Provider.objects.latest('id').id, "id": "default_group", "label": "Group 2",
"description": "Lorem ipsum", "showVerbDetails": True, "purposeOfCollection": "Lorem Ipsum",
"requiresConsent": True, "verbs": [
{"id": "http://h5p.example.com/expapi/verbs/experienced"},
{"id": "http://h5p.example.com/expapi/verbs/attempted"},
{"id": "http://h5p.example.com/expapi/verbs/interacted"},
{"id": "http://h5p.example.com/expapi/verbs/answered"},
]}
response = self.provider_client.post(
"/api/v1/consents/provider/create-verb-group",
group,
format="json",
)
self.assertEqual(response.status_code, 200)
group_id = ProviderVerbGroup.objects.latest('id').id
user_consent = [
{
"providerId": 1,
"providerSchemaId": 1,
"verbs": [
{
"provider": 1,
"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,
"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}]',
},
{
"provider": 1,
"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}]',
},
{
"provider": 1,
"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}]',
......
......@@ -6,13 +6,30 @@ from rest_framework.test import APIClient
from rolepermissions.roles import assign_role
from consents.tests.tests_consent_operations import BaseTestCase
from providers.models import ProviderAuthorization
from providers.models import ProviderAuthorization, Provider, ProviderVerbGroup, ProviderSchema
from users.models import CustomUser
PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
class TestThirdPartyGetUserStatus(BaseTestCase):
verb_groups = [
{
"provider_id": 1,
"id": "default_group",
"label": "Default group",
"description": "default",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"requiresConsent": True,
"verbs": [
{"id": "http://h5p.example.com/expapi/verbs/experienced"},
{"id": "http://h5p.example.com/expapi/verbs/attempted"},
{"id": "http://h5p.example.com/expapi/verbs/interacted"},
{"id": "http://h5p.example.com/expapi/verbs/answered"}
]
},
]
def setUp(self):
normal_user = CustomUser.objects.create_user(
self.test_user_email, self.test_user_password
......@@ -69,6 +86,16 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
HTTP_AUTHORIZATION="Basic " + self.application_token
)
# create some verb groups
for group in self.verb_groups:
group["provider_id"] = Provider.objects.latest('id').id # database id is 2 on second go bc autoincrement
response = self.provider_client.post(
"/api/v1/consents/provider/create-verb-group",
group,
format="json",
)
self.assertEqual(response.status_code, 200)
def test_user_status_401(self):
"""
Ensure requests including an invalid application token are rejected.
......@@ -101,6 +128,7 @@ 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 = [
{
......@@ -108,25 +136,25 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
"providerSchemaId": 1,
"verbs": [
{
"provider": 1,
"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}]',
},
{
"provider": 1,
"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}]',
},
{
"provider": 1,
"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}]',
},
{
"provider": 1,
"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}]',
......@@ -157,6 +185,23 @@ class TestThirdPartyGetUserStatus(BaseTestCase):
class TestThirdPartyUserConsentUpdate(BaseTestCase):
verb_groups = [
{
"provider_id": 1,
"id": "default_group",
"label": "Default group",
"description": "default",
"showVerbDetails": True,
"purposeOfCollection": "Lorem Ipsum",
"requiresConsent": True,
"verbs": [
{"id": "http://h5p.example.com/expapi/verbs/experienced"},
{"id": "http://h5p.example.com/expapi/verbs/attempted"},
{"id": "http://h5p.example.com/expapi/verbs/interacted"},
{"id": "http://h5p.example.com/expapi/verbs/answered"}
]
},
]
def setUp(self):
normal_user = CustomUser.objects.create_user(
self.test_user_email, self.test_user_password
......@@ -212,37 +257,46 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
self.third_party_client.credentials(
HTTP_AUTHORIZATION="Basic " + self.application_token
)
# create some verb groups
for group in self.verb_groups:
group["provider_id"] = Provider.objects.latest('id').id # database id is 2 on second go bc autoincrement
response = self.provider_client.post(
"/api/v1/consents/provider/create-verb-group",
group,
format="json",
)
self.assertEqual(response.status_code, 200)
def test_update_user_consent(self):
"""
Ensure user consent gets updated via third party endpoint.
"""
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": [
{
"provider": 1,
"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,
"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}]',
},
{
"provider": 1,
"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}]',
},
{
"provider": 1,
"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}]',
......@@ -271,22 +325,23 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
responseDict["consent"]["groups"][0]["verbs"][1]["consented"], True
)
self.assertEqual(
responseDict["consent"]["groups"][1]["verbs"][0]["consented"], True
responseDict["consent"]["groups"][0]["verbs"][2]["consented"], True
)
self.assertEqual(
responseDict["consent"]["groups"][1]["verbs"][0]["consented"], True
responseDict["consent"]["groups"][0]["verbs"][3]["consented"], True
)
def test_update_user_consent_fails_without_access_token(self):
"""
Ensure unauthenticated request gets rejected.
"""
group_id = ProviderVerbGroup.objects.latest('id').id
payload = {
"user_id": self.test_user_email,
"provider_schema_id": 1,
"verbs": [
{
"provider": 1,
"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}]',
......@@ -306,14 +361,15 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
def test_update_on_none_existing_provider_fails(self):
"""
Ensure attempt to update verb on none existing provider fails.
Ensure attempt to update verb on non-existing provider fails.
"""
group_id = ProviderVerbGroup.objects.latest('id').id
payload = {
"user_id": self.test_user_email,
"provider_schema_id": 1,
"provider_schema_id": 2,
"verbs": [
{
"provider": 2,
"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}]',
......@@ -325,121 +381,3 @@ class TestThirdPartyUserConsentUpdate(BaseTestCase):
"/api/v1/consents/user/save/third-party", data=payload, format="json"
)
self.assertEqual(response.status_code, 400)
def test_update_other_provider_fails(self):
"""
Ensure you are not authorized to access providers other than your own.
"""
# Create provider Moodle
with StringIO(json.dumps(self.moodle_schema)) as fp:
response = self.provider_client.put(
"/api/v1/consents/provider/create",
{"provider-schema": fp},
format="multipart",
)
self.assertEqual(response.status_code, 201)
moodle_provider_id = ProviderAuthorization.objects.get(
provider__name="Moodle"
).provider.id
payload = {
"user_id": self.test_user_email,
"provider_schema_id": 1,
"verbs": [
{
"provider": moodle_provider_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}]',
}
],
}
response = self.third_party_client.post(
"/api/v1/consents/user/save/third-party", data=payload, format="json"
)
self.assertEqual(response.status_code, 403)
self.assertJSONEqual(
str(response.content, encoding="utf8"),
{
"message": "you are not authorized to access providers other than your own"
},
)
def test_update_user_consent_fails_incomplete_verb_consents(self):
"""
Ensure user consent gets with incomplete verbs.
"""
provider = ProviderAuthorization.objects.get(provider__name="H5P")
# Create consent for one verb instead of 4 verbs
payload = {
"user_id": self.test_user_email,
"provider_schema_id": provider.id,
"verbs": [
{
"provider": 1,
"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}]',
}
],
}
response = self.third_party_client.post(
"/api/v1/consents/user/save/third-party", data=payload, format="json"
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
str(response.content, encoding="utf8"),
{"message": "user consent contains missing provider schema verb consents"},
)
def test_update_user_consent_fails_incomplete_object_consents(self):
"""
Ensure user consent gets with incomplete verb objects.
"""
provider = ProviderAuthorization.objects.get(provider__name="H5P")
# Create consent for one verb instead of 4 verbs
payload = {
"user_id": self.test_user_email,
"provider_schema_id": provider.id,
"verbs": [
{
"provider": 1,
"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": "[]",
},
{
"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": 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"
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
str(response.content, encoding="utf8"),
{"message": "user consent contains missing provider schema verb consents"},
)
......@@ -6,6 +6,8 @@ urlpatterns = [
path('provider', views.GetProviderSchemasView.as_view()),
path('provider-status/third-party', views.GetProviderStatusThirdPartyView.as_view()),
path('provider/create', views.CreateProviderConsentView.as_view()),
path('provider/<provider_id>/create-verb-group', views.CreateProviderVerbGroupView.as_view()), # TODO
path('provider/<provider_id>/verb-groups', views.GetProviderVerbGroupsView.as_view()), # TODO
path('user/save', views.SaveUserConsentView.as_view()),
path('user/create', views.CreateUserConsentView.as_view()),
path('user/create-via-connect-service', views.CreateUserConsentViaConnectServiceView.as_view()),
......
This diff is collapsed.
This diff is collapsed.
......@@ -27,6 +27,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"jwt-decode": "^3.1.2",
"ng-zorro-antd": "^14.0.0",
"ngx-markdown": "^14.0.1",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
......
<div class="analytics-engines">
<h1 i18n="Analyses | @@analysis">
Analyses
</h1>
<nz-tabset>
<nz-tab i18n-nzTitle="Aktive Analysen | @@activeAnalyses" nzTitle="Active analyses">
<div *ngIf="!activeAnalyses.length">
<h1 i18n="no analyses | @@noAnalyses">No analyses</h1>
<h1 i18n="Analyses | @@analysis">
Analyses
</h1>
<nz-tabset>
<nz-tab i18n-nzTitle="Aktive Analysen | @@activeAnalyses" nzTitle="Active analyses">
<div *ngIf="!activeAnalyses.length">
<h1 i18n="no analyses | @@noAnalyses">No analyses</h1>
</div>
<div nz-row [nzGutter]="[16, 24]">
<div nz-col [nzSpan]="6" *ngFor="let analysis of activeAnalyses">
<app-analysis-card [providers]="providers" class="card" [analysis]="analysis" [reloadList]="reloadList"></app-analysis-card>
</div>
<div nz-row [nzGutter]="[16, 24]">
<div nz-col [nzSpan]="6" *ngFor="let analysis of activeAnalyses">
<app-analysis-card [providers]="providers" class="card" [analysis]="analysis" [reloadList]="reloadList"></app-analysis-card>
</div>
</div>
</nz-tab>
<nz-tab i18n-nzTitle="Inaktive Analysen | @@inactiveAnalyses" nzTitle="Inactive analyses">
<div nz-row [nzGutter]="[16, 24]">
<div nz-col [nzSpan]="6" *ngFor="let analysis of inactiveAnalyses">
<app-analysis-card [providers]="providers" class="card" [analysis]="analysis" [reloadList]="reloadList"></app-analysis-card>
</div>
</nz-tab>
<nz-tab i18n-nzTitle="Inaktive Analysen | @@inactiveAnalyses" nzTitle="Inactive analyses">
<div nz-row [nzGutter]="[16, 24]">
<div nz-col [nzSpan]="6" *ngFor="let analysis of inactiveAnalyses">
<app-analysis-card [providers]="providers" class="card" [analysis]="analysis" [reloadList]="reloadList"></app-analysis-card>
</div>
</div>
</nz-tab>
</nz-tabset>
</div>
</div>
</nz-tab>
</nz-tabset>
.analytics-engines {
margin: 20px;
}
.card{
width : 100%
}
\ No newline at end of file
}
import { Component, OnInit } from '@angular/core';
import { AnalyticsToken, AnalyticsTokenVerb, ApiService, Provider, UserConsentResponse, Verb } from '../services/api.service';
import { AnalyticsToken, AnalyticsTokenVerb, ApiService, Provider, UserConsentResponse } from '../services/api.service';
import { ProviderSchema, UserConsent, XApiVerbConsented } from '../consent-management/consentDeclaration';
......@@ -43,12 +43,12 @@ export class AnalyticsEngineComponent implements OnInit {
userConsentProviderPairs : UserConsentProviderPair[] = [];
providerVerbConsentPairs : ProviderVerbConsentPair[] = []
constructor(private _apiService: ApiService) {
constructor(private _apiService: ApiService) {
this._apiService = _apiService
this.reloadList = this.reloadList.bind(this)
}
ngOnInit(): void {
this.loadAnalyticsTokens()
}
......@@ -61,7 +61,7 @@ export class AnalyticsEngineComponent implements OnInit {
this.providers = [];
this.userConsentProviderPairs = [];
this.providerVerbConsentPairs = []
this.loadAnalyticsTokens()
}
......@@ -72,7 +72,7 @@ export class AnalyticsEngineComponent implements OnInit {
})
.add(() => this.loadProviders())
}
loadProviders()
{
this._apiService.getProvidersUsers().subscribe((providers) => {
......@@ -80,7 +80,7 @@ export class AnalyticsEngineComponent implements OnInit {
for(let provider of providers)
this.loadConsent(provider)
})
}
loadConsent(provider : Provider)
......@@ -111,7 +111,7 @@ export class AnalyticsEngineComponent implements OnInit {
for(let group of consentGroups)
for(let verb of group.verbs)
allVerbs.push({...verb, providerId : provider.id});
this.providerVerbConsentPairs.push({provider : provider, providerSchema : providerSchema!, allVerbs : allVerbs})
}
......@@ -125,7 +125,7 @@ export class AnalyticsEngineComponent implements OnInit {
for(let analysis of this.analysisTokens){
let consentedVerbs : XApiVerbConsentedWithProvider[] = []
let notConsentedVerbs : XApiVerbConsentedWithProvider[]= []
const verbs = analysis.analyticTokenVerbs
for(let verb of verbs)
{
......@@ -145,5 +145,5 @@ export class AnalyticsEngineComponent implements OnInit {
this.activeAnalyses = active
this.inactiveAnalyses = inactive
}
}
......@@ -15,11 +15,13 @@ import { ApplicationTokensComponent } from './consent-management/application-tok
import { AnalyticsTokensComponent } from './consent-management/analytics-tokens/analytics-tokens.component'
import { AnalyticsEngineComponent } from './analytics-engine/analytics-engine.component'
import { ConsentHistoryComponent } from './consent-management/consent-history/consent-history.component'
import { ControlCenterComponent } from './control-center/control-center.component'
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'consent-management', component: ConsentManagementComponent, canActivate: [AuthGuard] },
{ path: 'consent-history', component: ConsentHistoryComponent, canActivate: [AuthGuard] },
{ path: 'control-center', component: ControlCenterComponent, canActivate: [AuthGuard]},
{ path: 'merge-actors', component: MergeDataComponent, canActivate: [AuthGuard] },
{ path: 'analytics-engines', component: AnalyticsEngineComponent, canActivate: [AuthGuard] },
{ path: 'login', component: LoginPageComponent },
......
......@@ -4,7 +4,9 @@
<div class="loading-spinner" *ngIf="loading">
<img ngSrc="assets/spinner_small.gif" alt="loading" height="100" width="100" />
</div>
<router-outlet></router-outlet>
<div class="content">
<router-outlet></router-outlet>
</div>
</nz-content>
<app-footer></app-footer>
</nz-layout>
......@@ -72,6 +72,11 @@ import { NzMessageService } from 'ng-zorro-antd/message'
import { NzSpinModule } from 'ng-zorro-antd/spin'
import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'
import { ConsentHistoryComponent } from './consent-management/consent-history/consent-history.component'
import { ControlCenterComponent } from './control-center/control-center.component'
import { VerbGroupsComponent } from './consent-management/verb-groups/verb-groups.component'
import { MarkdownModule, MarkdownService } from 'ngx-markdown'
import { CreateVerbGroupDialog } from './dialogs/create-verb-group-dialog/create-verb-group-dialog'
import { NzRadioModule } from 'ng-zorro-antd/radio'
registerLocaleData(de)
......@@ -81,6 +86,7 @@ registerLocaleData(de)
HeaderComponent,
ConsentManagementComponent,
ConsentHistoryComponent,
ControlCenterComponent,
PageNotFoundComponent,
LoginPageComponent,
WizardDialog,
......@@ -98,47 +104,51 @@ registerLocaleData(de)
SchemaChangeComponent,
ObjectChangesComponent,
CreateTokenDialog,
CreateVerbGroupDialog,
OrderByPipe,
MergeDataComponent,
AnalysisCard,
AnalyticsEngineComponent
],
imports: [
BrowserModule,
AppRoutingModule,
LayoutModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
BrowserAnimationsModule,
NzTabsModule,
NzGridModule,
NzCardModule,
NzButtonModule,
FontAwesomeModule,
NzLayoutModule,
NgOptimizedImage,
NzMenuModule,
NzAffixModule,
NzSwitchModule,
NzDropDownModule,
NzCollapseModule,
NzListModule,
NzToolTipModule,
NzSelectModule,
NzInputModule,
NzSpaceModule,
NzFormModule,
NzBadgeModule,
NzTagModule,
NzTableModule,
NzCheckboxModule,
NzProgressModule,
NzAlertModule,
NzModalModule,
NzSpinModule,
NzDatePickerModule
AnalyticsEngineComponent,
VerbGroupsComponent
],
imports: [
BrowserModule,
AppRoutingModule,
LayoutModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
BrowserAnimationsModule,
NzTabsModule,
NzGridModule,
NzCardModule,
NzButtonModule,
FontAwesomeModule,
NzLayoutModule,
NgOptimizedImage,
NzMenuModule,
NzAffixModule,
NzSwitchModule,
NzDropDownModule,
NzCollapseModule,
NzListModule,
NzToolTipModule,
NzSelectModule,
NzInputModule,
NzSpaceModule,
NzFormModule,
NzBadgeModule,
NzTagModule,
NzTableModule,
NzCheckboxModule,
NzProgressModule,
NzAlertModule,
NzModalModule,
NzSpinModule,
NzDatePickerModule,
MarkdownModule.forRoot(),
NzRadioModule
],
providers: [
{
provide: APP_INITIALIZER,
......
......@@ -18,8 +18,8 @@
<th></th>
<th>Name</th>
<th>Token</th>
<th>Created At</th>
<th>Expires</th>
<th i18n="Created At | Table column header @@createdAt">Created At</th>
<th i18n="Expires | Table column header @@expires">Expires</th>
</tr>
</thead>
<tbody>
......
<nz-card style='margin: 8px;' [nzTitle]="cardTitle">
<ng-template #cardTitle>
<h2 i18n="Consent History | Header entry @@consentHistoryHeader">Consent History</h2>
</ng-template>
<nz-collapse>
<nz-collapse-panel *ngFor="let consent of consentHistory" [nzHeader]="dateHeader">
<ng-template #dateHeader>
{{ consent.created | date }}
</ng-template>
<nz-card
*ngFor="let userConsent of consent.consents"
[nzTitle]="userConsent.description"
>
<app-provider-setting
[preview]="true"
[consentDeclaration]="userConsent"
[previousUserConsent]="null"
></app-provider-setting>
</nz-card>
<h1 i18n="Consent History | Header entry @@consentHistoryHeader">Consent Management</h1>
<p>
POLARIS verarbeitet deine persönlichen Lerndaten nur mit deiner vorherigen Zustimmung. In dieser Übersicht kannst du deine Einwilligungen zu verschiedenen Datenbündeln ansehen und bearbeiten. Du kannst deine Einwilligung widerrufen oder die Datenübertragung vorübergehend pausieren.
</p>
<p>&nbsp;</p>
<nz-collapse>
<nz-collapse-panel [nzHeader]="faqPauseHeader">
<ng-template #faqPauseHeader>
Was passiert, wenn ich die Datenübertragung pausiere?
</ng-template>
<p>
Du kannst die Übertragung deiner Lerndaten an POLARIS jederzeit pausieren. Ab diesem Zeitpunkt werden auf unbestimmte Zeit keine weiteren Lerndaten mehr an POLARIS geschickt. Anders als beim Widerruf bleiben die in POLARIS gespeicherten Daten erhalten.
</p>
<p>
Die persönlichen Statistiken, die dir auf Basis der Analyse dieser Lerndaten angezeigt wurden, werden ausgeblendet.
</p>
<p>
Die Datenübertragung wird fortgesetzt, wenn du die Pausierung wieder aufhebst. Dies kannst du unten unter „Meine Einwilligungen“ tun oder indem du auf den Button „Statistiken aktivieren“ im jeweiligen Quellsystem, z.B. Moodle oder Dynexite, klickst.
</p>
</nz-collapse-panel>
<nz-collapse-panel [nzHeader]="faqRevokeHeader">
<ng-template #faqRevokeHeader>
Was passiert, wenn ich meine Einwilligung widerrufe?
</ng-template>
<p>
Eine Einwilligung in die Verarbeitung personenbezogener Daten kannst du jederzeit widerrufen – dieses Recht ist durch die Datenschutz-Grundverordnung gesichert. Du musst dazu keine Gründe angeben und deine Entscheidung darf keine Nachteile für dich zur Folge haben. In diesem Fall können jedoch die entsprechenden Analysen nicht für Dich durchgeführt werden.
</p>
<p>
Wenn du die Zustimmung zur Verarbeitung bestimmter Lerndaten in POLARIS widerrufst, werden ab diesem Zeitpunkt keine weiteren Daten mehr an POLARIS geschickt. Außerdem werden die bis zu diesem Zeitpunkt über dich gespeicherten Lerndaten innerhalb der gesetzlichen Frist von vier Wochen unwiderruflich anonymisiert. Anonyme Daten können dir nie wieder zugeordnet werden.
</p>
<p>
Die persönlichen Statistiken, die dir auf Basis der Analyse dieser Lerndaten angezeigt wurden, werden ausgeblendet.
</p>
</nz-collapse-panel>
</nz-collapse>
<p style="padding-bottom: 88px">&nbsp;</p>
<h2>Deine Einwilligungen</h2>
<nz-collapse>
<nz-collapse-panel *ngFor="let consent of consentHistory" [nzHeader]="dateHeader">
<ng-template #dateHeader>
{{ consent.created | date }}
</ng-template>
<nz-card
*ngFor="let userConsent of consent.consents"
[nzTitle]="userConsent.description"
>
<app-provider-setting
[preview]="true"
[consentDeclaration]="userConsent"
[previousUserConsent]="null"
></app-provider-setting>
</nz-card>
</nz-collapse-panel>
</nz-collapse>
</nz-collapse-panel>
</nz-collapse>
</nz-card>
......@@ -2,6 +2,7 @@ export interface XApiObjectSchema {
definition: Object
consented?: boolean
defaultConsent: boolean
objectType: string
label: string
id: string
}
......@@ -10,18 +11,17 @@ export interface XApiObjectConsented {
definition: Object
consented: boolean
defaultConsent: boolean
objectType: string
label: string
id: string
}
interface XApiVerb {
}
export interface XApiVerbSchema {
id: string
label: string
description: string
defaultConsent: boolean
essential: boolean
objects: XApiObjectSchema[]
}
......@@ -30,18 +30,24 @@ export interface XApiVerbConsented extends XApiVerbSchema {
objects: XApiObjectConsented[]
}
export interface ConsentGroupSchema {
export interface XApiVerbGroupSchema {
id: string
label: string
description: string
purposeOfCollection: string
showVerbDetails: boolean
requiresConsent: boolean
provider_schema: number
verbs: XApiVerbSchema[]
isDefault: boolean
}
export const instanceOfXApiVerbConsented = (object: any): object is XApiObjectConsented => {
return 'consented' in object
export interface XApiVerbGroupConsented extends XApiVerbGroupSchema {
id: string
label: string
description: string
purposeOfCollection: string
showVerbDetails: boolean
verbs: XApiVerbSchema[]
isDefault: boolean
}
export interface ConsentGroupConsented {
......@@ -55,37 +61,30 @@ export interface ConsentGroupConsented {
}
interface ConsentDeclaration {
id: string
id: number
description: string
}
export interface ProviderConsent {
id: string
name: string
superseded_by: number | null
createdAt: string
definition: ConsentDeclaration[]
supersedes?: ProviderConsent
}
export interface ProviderSchemaDefinition {
id: string
name: string
description: string
groups: ConsentGroupSchema[]
verbs: XApiVerbSchema[]
essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
}
export interface ProviderSchema extends ConsentDeclaration {
export interface ProviderSchema {
id: number
description: string
superseded_by: number | null
createdAt: string
groups: ConsentGroupSchema[]
groups: XApiVerbGroupConsented[]
essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
definition: ProviderSchemaDefinition
}
export interface UserConsent extends ConsentDeclaration {
id: string
id: number
groups: ConsentGroupConsented[]
essential_verbs: XApiVerbSchema[] // verbs which can be collected without the user consent
created?: Date
......@@ -93,11 +92,12 @@ export interface UserConsent extends ConsentDeclaration {
export interface UserConsentVerbs {
providerId: ProviderId
providerSchemaId: string
providerSchemaId: ProviderSchemaId
verbs: { id: string; consented?: boolean; objects: string }[]
}
export type ProviderId = number
export type ProviderSchemaId = number
/**
* Iterates over each verb and object of a provider schema and sets the consented field to the default consent
......@@ -108,7 +108,7 @@ export const providerSchemaToUserConsent = (providerSchema: ProviderSchema): Use
return {
id: providerSchema.id,
description: providerSchema.description,
groups: providerSchema.groups.map((group) => ({
groups: providerSchema.groups? providerSchema.groups.map((group) => ({
...group,
verbs: group.verbs.map((verb) => ({
...verb,
......@@ -118,7 +118,7 @@ export const providerSchemaToUserConsent = (providerSchema: ProviderSchema): Use
consented: object.defaultConsent
})) : []
}))
})),
})) : [],
essential_verbs: providerSchema.essential_verbs
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment