Skip to content
Snippets Groups Projects
Commit 55aa6ee8 authored by Benjamin Ledel's avatar Benjamin Ledel
Browse files

Merge branch 'feat/system_user' into 'main'

#6: xAPI basierte Systeminformationen - Schnittstellen zum Speichern und Abrufen

See merge request polaris/rights-engine!7
parents a00eb201 58d9fefa
No related branches found
No related tags found
No related merge requests found
......@@ -335,6 +335,52 @@ class CheckAnalyticsTokenNameAvailable(APIView):
)
def get_system_statement_query(providers=[]):
"""
:param providers: optional list of providers to filter system statements by
:return: query object which filters for system statements and optionally the supplied providers
"""
query = {"$and": [
{"actor.mbox": {"$exists": True}}
]}
if len(providers) > 0:
provider_filters = []
for provider in providers:
provider_filters.append("system:" + str(provider.id))
query["$and"].append({"actor.mbox": {"$in": provider_filters}})
else:
query["$and"].append({"actor.mbox": {"$regex": "^system"}})
return query
def get_system_statements(collection, providers=[]):
"""
Filters statements in LRS db by special system user and optionally a list of providers.
:param providers: optional list of provider objects used for selecting relevant system statements
:param collection: lrs database containing xapi statements
:return: all relevant statements
"""
system_statement_query = get_system_statement_query(providers)
return collection.find(system_statement_query)
def replace_provider_id(statement, providers):
if statement.get("actor", {}).get("mbox", "").startswith("system:"):
provider_id = int((statement.get("actor", {}).get("mbox", "").split("system:"))[1])
provider = next((x for x in providers if x.id == provider_id), None)
if provider:
statement["actor"]["mbox"] = "system:" + provider.name
else:
# fallback - return unmodified statement
return statement
else:
return statement
class GetProviderData(APIView):
"""
Endpoint that allows an analytics engine to obtain provider statements from the lrs.
......@@ -403,7 +449,7 @@ class GetProviderData(APIView):
anon_verbs.append(verb)
# selects for anonymized statements of which there are enough different actors
# or explicitly non-anonymized statements
# or explicitly non-anonymized statements / system statements
anon_query = {"$or": [
# current statement is anonymized and
# enough anonymized statements for this verb exist
......@@ -418,7 +464,9 @@ class GetProviderData(APIView):
{"$and": [
{"actor.mbox": {"$exists": False}},
{"actor.mbox": {"$regex": "^mailto"}},
]}
]},
# also query for system statements added by relevant providers
get_system_statement_query(providers)
]}
]}
......@@ -443,7 +491,7 @@ class GetProviderData(APIView):
cursor = collection.find(query).limit(page_size)
data = {
"verbs": list(set(active_verbs)),
"statements": list(cursor),
"statements": list(map(replace_provider_id, list(cursor))),
"page_size": page_size,
}
......
......@@ -2,6 +2,7 @@ import json
import os
from datetime import datetime
from io import StringIO
from unittest import mock
from unittest.mock import patch
from django.core.exceptions import ObjectDoesNotExist
......@@ -173,6 +174,43 @@ class XAPITestCase(TestCase):
# )
# self.assertEqual(response.status_code, 403)
@patch("xapi.views.store_in_db", mock_store_in_lrs)
def test_system_user(self):
try:
provider = Provider.objects.order_by("id").first()
except ObjectDoesNotExist:
self.assertTrue(False) # provider was not created when uploading schema
keys = [
auth.key for auth in ProviderAuthorization.objects.filter(provider=provider)
]
self.assertTrue(len(keys) > 0) # keys were not created
key = keys[0]
provider_client = APIClient()
provider_client.credentials(HTTP_AUTHORIZATION="Basic " + key)
response = provider_client.post(
"/xapi/statements",
{
"actor": {"mbox": f"system:{provider.id}"},
"verb": {"id": "some_id"},
"object": {
"id": "some_other_id",
"objectType": "Activity",
"definition": "object_definition",
},
"timestamp": datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
},
format="json",
)
self.assertEqual(response.status_code, 200)
# {'message': 'processed', 'provider': 'H5P'}
self.assertEqual(
response.json()["message"], "xAPI statements successfully stored in LRS"
)
class TestxAPIWithDataRecordingPause(BaseTestCase):
@patch("xapi.views.store_in_db", mock_store_in_lrs)
......
......@@ -56,6 +56,13 @@ def process_statement(x_api_statement, provider, latest_schema):
except ValidationError as e:
return {"valid": False, "accepted": False, "reason": e.message}
# system statements are directly stored without checking for consent
if x_api_statement.get("actor", {}).get("mbox", "").startswith("system:"):
if int((x_api_statement.get("actor", {}).get("mbox", "").split("system:"))[1]) == provider.id:
return {"valid": True, "accepted": True}
else:
return {"valid": False, "accepted": False, "reason": "Wrong provider ID"}
mbox = dict(
enumerate(x_api_statement.get("actor", {}).get("mbox", "").split("mailto:"))
).get(1)
......@@ -85,7 +92,7 @@ def process_statement(x_api_statement, provider, latest_schema):
# essential verbs do not require consent
if not verb in [verb["id"] for verb in latest_schema.essential_verbs]:
anon_verbs = [verb["id"] for verblist in [group["verbs"] for group in latest_schema.groups] for verb in verblist if verb.get("allowAnonymizedCollection", False)]
# has the user paused data collection altogether?
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment