diff --git a/docs/docs/provider_schema.md b/docs/docs/provider_schema.md index 6c06b6eefc98af4e10f782544693e7b7f4c699de..b121e2e80d7fcaa3ebdcef543cfbb639fc725dc5 100644 --- a/docs/docs/provider_schema.md +++ b/docs/docs/provider_schema.md @@ -186,8 +186,31 @@ Essential verbs are verbs that are always collected. The user can not disagree t ``` The configuration of the verbs is identical to that of other verbs within the schema. You can also restrict the verbs to specific verbs. That allows the essential data collection for example for specific objects using the id-based filtering approach. +### Additional LRS +Additional LRS can be supplied to forward incoming xAPI statements for the given provider to further LRS, in addition to the global LRS set for the application. Statements are forwarded without further filtering. + +```json +{ + "id": "h5p-0", + "name": "H5P", + "description": "Open-source content collaboration framework", + "groups": [ + ... + ], + "essentialVerbs": [ + ... + ], + "additionalLrs": [ + { + "url": "https://other-lrs.example.com/xapi/statements", + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMyNzE5..." + } + ] +} +``` + ## Validate Schema -It is possible to validate the schema againts the json schema from the repository of the rights engine (`src/static/provider_schema.schema.json`). You can use multiple online tools like https://www.jsonschemavalidator.net/ to validated the schema before uploading it to Polaris. +It is possible to validate the schema againts the json schema from the repository of the rights engine (`src/static/provider_schema.schema.json`). You can use multiple online tools like https://www.jsonschemavalidator.net/ to validate the schema before uploading it to Polaris. ## Update Schema @@ -304,7 +327,8 @@ You have the option to retain the existing schema, translate specific properties "isDefault": true } ], - "essentialVerbs": [] + "essentialVerbs": [], + "additionalLrs": [] } diff --git a/src/consents/serializers.py b/src/consents/serializers.py index e6c07494e1b57d3c0d318a399d2826e314517791..59886d527222e3286aada33527f9f0479430448b 100644 --- a/src/consents/serializers.py +++ b/src/consents/serializers.py @@ -15,7 +15,7 @@ class ProviderSchemaSerializer(serializers.ModelSerializer): "name": obj.provider.name, "description": obj.provider.description, "groups": obj.groups, - "essential_verbs": obj.essential_verbs, + "essential_verbs": obj.essential_verbs } class Meta: diff --git a/src/consents/views.py b/src/consents/views.py index 90ccf7aea1002751132adbca9c5d0133273b8f1e..a2e359e58ea0d7edb9ed41ca6d524a67bbc2ab93 100644 --- a/src/consents/views.py +++ b/src/consents/views.py @@ -261,6 +261,7 @@ class CreateProviderConsentView(APIView): provider=providerModel, groups=provider_schema["groups"], essential_verbs=provider_schema["essentialVerbs"], + additional_lrs=provider_schema["additionalLrs"] ) precedingProviderSchema.superseded_by = providerSchemaModel precedingProviderSchema.save() @@ -275,6 +276,7 @@ class CreateProviderConsentView(APIView): provider=providerModel, groups=provider_schema["groups"], essential_verbs=provider_schema["essentialVerbs"], + additional_lrs=provider_schema["additionalLrs"] ) return JsonResponse( diff --git a/src/fixtures/initial_db.json b/src/fixtures/initial_db.json index 531c54a121d5727d6b3c1cb82d1264c9faed1e67..abbeecdc65c2ac4b81314a1dd9f29190398e336e 100644 --- a/src/fixtures/initial_db.json +++ b/src/fixtures/initial_db.json @@ -4511,6 +4511,7 @@ } ], "essential_verbs": [], + "additional_lrs": [], "updated": "2023-03-16T09:02:08.898Z", "created": "2023-03-16T09:02:08.898Z" } diff --git a/src/providers/migrations/0007_providerschema_additional_lrs.py b/src/providers/migrations/0007_providerschema_additional_lrs.py new file mode 100644 index 0000000000000000000000000000000000000000..1c5fed368907626884727ff3793fc5ebfa1fd051 --- /dev/null +++ b/src/providers/migrations/0007_providerschema_additional_lrs.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2024-11-27 15:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('providers', '0006_analyticstoken_description_analyticstoken_image_path'), + ] + + operations = [ + migrations.AddField( + model_name='providerschema', + name='additional_lrs', + field=models.JSONField(default=list), + ), + ] diff --git a/src/providers/models.py b/src/providers/models.py index e14fb36ca2fe55a03d64baf263cc35c761044564..e344a2be57396240144e7183b4d39fa1eab40e71 100644 --- a/src/providers/models.py +++ b/src/providers/models.py @@ -22,6 +22,7 @@ class ProviderSchema(models.Model): superseded_by = models.ForeignKey("self", null=True, on_delete=models.SET_NULL) groups = models.JSONField() essential_verbs = models.JSONField() + additional_lrs = models.JSONField(default=list) updated = models.DateTimeField(auto_now=True) created = models.DateTimeField(auto_now_add=True) diff --git a/src/static/provider_schema.schema.json b/src/static/provider_schema.schema.json index c017ac54326b3b5a4465838267bed23f82f0123d..a51b518fca7e24146af6ae17d994439a9570b3e8 100644 --- a/src/static/provider_schema.schema.json +++ b/src/static/provider_schema.schema.json @@ -133,6 +133,25 @@ "required": ["id", "label", "description", "defaultConsent", "objects"] } ] + }, + "additionalLrs": { + "type": "array", + "items": [ + { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the store-endpoint for an additional LRS" + }, + "token": { + "type": "string", + "description": "token to authenticate with" + } + }, + "required": ["url", "token"] + } + ] } }, "required": ["id", "name", "description", "groups", "essentialVerbs"] diff --git a/src/xapi/views.py b/src/xapi/views.py index 382224e8e34186a2a1e938c7f15f53799977a92d..18e028a8e59be2b46e601a75ef9e0b423ff7409f 100644 --- a/src/xapi/views.py +++ b/src/xapi/views.py @@ -315,6 +315,17 @@ class CreateStatement(APIView): request.data if isinstance(request.data, list) else [request.data] ) + # forward to other LRS without validation etc., if given + if latest_schema.additional_lrs and isinstance(latest_schema.additional_lrs, list) and len(latest_schema.additional_lrs) > 0: + for additional_lrs in latest_schema.additional_lrs: + headers = {"Authorization": "Bearer " + additional_lrs["token"]} + for stmt in x_api_statements: + res = requests.post(additional_lrs["url"], json=stmt, headers=headers) + if not res and settings.DEBUG: + print("Could not forward to ", additional_lrs["url"], ":", res.reason, "({})".format(res.status_code)) + elif res and settings.DEBUG: + print("Forwarded statement to ", additional_lrs["url"], ":", res.reason, "({})".format(res.status_code)) + if settings.SHOW_XAPI_STATEMENTS: print(x_api_statements)