Skip to content
Snippets Groups Projects
Commit 240a33c1 authored by Benjamin Ledel's avatar Benjamin Ledel
Browse files
parents e347c260 55aa6ee8
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