From 066f394910403d2aaede9c7141660be36387bc97 Mon Sep 17 00:00:00 2001 From: John Szinger Date: Thu, 12 Jan 2023 13:44:44 -0500 Subject: [PATCH 1/4] status_callback --- README.md | 4 +- .../migrations/0011_coveredbusiness_cb_id.py | 18 ++++++ .../0012_coveredbusiness_status_callback.py | 18 ++++++ ..._remove_coveredbusiness_status_callback.py | 17 +++++ drp_aa_mvp/covered_business/models.py | 1 + drp_aa_mvp/data_rights_request/models.py | 2 +- .../data_rights_request/data_rights.json | 2 +- .../templates/drp_aa_mvp/index.html | 4 +- drp_aa_mvp/data_rights_request/views.py | 63 +++++++++++++++---- .../reporting/migrations/0001_initial.py | 37 +++++++++++ .../reporting/templates/reporting/index.html | 4 +- drp_aa_mvp/reporting/views.py | 6 +- 12 files changed, 153 insertions(+), 23 deletions(-) create mode 100644 drp_aa_mvp/covered_business/migrations/0011_coveredbusiness_cb_id.py create mode 100644 drp_aa_mvp/covered_business/migrations/0012_coveredbusiness_status_callback.py create mode 100644 drp_aa_mvp/covered_business/migrations/0013_remove_coveredbusiness_status_callback.py create mode 100644 drp_aa_mvp/reporting/migrations/0001_initial.py diff --git a/README.md b/README.md index 712fdf5..7da2a4f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # OSIRAA - Open Source Implementers’ Reference Authorized Agent -Version 0.5.1 - Updated December 2022 +Version 0.6.0 - Updated January 2023 ## How to Use this App: OSIRAA (Open Source Implementers’ Reference Authorized Agent) is a test suite designed to simulate the role of an Authorized Agent in a Data Rights Protocol (DRP) environment. The application tests for the availability, correctness and completeness of API endpoints of a Privacy Infrastructure Provider (PIP) or Covered Business (CB) partner application. See https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md for more info on DRP system roles and API specification. ## Admin Tool -A user may model a Privacy Infrastructure Provider (PIP) or Covered Business (CB) in the Admin Tool, along with any number of users. This is a standard Python app, so you must first create an admin superuser before you can administer data configurations. For version 0.5, a Covered Business requires a Discovery Endpoint, API Secret and Auth Bearer Token, to be supplied by the PIP/CB partner. +A user may model a Privacy Infrastructure Provider (PIP) or Covered Business (CB) in the Admin Tool, along with any number of users. This is a standard Python app, so you must first create an admin superuser before you can administer data configurations. For version 0.6, a Covered Business requires a Discovery Endpoint, API Secret and Auth Bearer Token, to be supplied by the PIP/CB partner. ## Cert Tests Definitions The Data Rights Protocol is centered on a set of API calls between an Authorized Agent (AA) and a Privacy Infrastructure Provider or Covered Business, on behalf of a User exercising his or her data rights. diff --git a/drp_aa_mvp/covered_business/migrations/0011_coveredbusiness_cb_id.py b/drp_aa_mvp/covered_business/migrations/0011_coveredbusiness_cb_id.py new file mode 100644 index 0000000..25dd3c9 --- /dev/null +++ b/drp_aa_mvp/covered_business/migrations/0011_coveredbusiness_cb_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-12-20 19:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('covered_business', '0010_resize_auth_bearer_token'), + ] + + operations = [ + migrations.AddField( + model_name='coveredbusiness', + name='cb_id', + field=models.CharField(blank=True, default='', max_length=63), + ), + ] diff --git a/drp_aa_mvp/covered_business/migrations/0012_coveredbusiness_status_callback.py b/drp_aa_mvp/covered_business/migrations/0012_coveredbusiness_status_callback.py new file mode 100644 index 0000000..3d94e31 --- /dev/null +++ b/drp_aa_mvp/covered_business/migrations/0012_coveredbusiness_status_callback.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2023-01-11 21:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('covered_business', '0011_coveredbusiness_cb_id'), + ] + + operations = [ + migrations.AddField( + model_name='coveredbusiness', + name='status_callback', + field=models.URLField(blank=True, default='', max_length=127), + ), + ] diff --git a/drp_aa_mvp/covered_business/migrations/0013_remove_coveredbusiness_status_callback.py b/drp_aa_mvp/covered_business/migrations/0013_remove_coveredbusiness_status_callback.py new file mode 100644 index 0000000..b70ce62 --- /dev/null +++ b/drp_aa_mvp/covered_business/migrations/0013_remove_coveredbusiness_status_callback.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2023-01-11 22:47 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('covered_business', '0012_coveredbusiness_status_callback'), + ] + + operations = [ + migrations.RemoveField( + model_name='coveredbusiness', + name='status_callback', + ), + ] diff --git a/drp_aa_mvp/covered_business/models.py b/drp_aa_mvp/covered_business/models.py index c8f5632..c1860d0 100644 --- a/drp_aa_mvp/covered_business/models.py +++ b/drp_aa_mvp/covered_business/models.py @@ -28,6 +28,7 @@ class CoveredBusiness(models.Model): name = models.CharField(max_length=63, blank=True, default='') brand_name = models.CharField(max_length=63, blank=True, default='') + cb_id = models.CharField(max_length=63, blank=True, default='') logo = models.ImageField('Logo Image', upload_to='company-logos', blank=True) logo_thumbnail = models.ImageField(upload_to='company-logos/thumbnails', blank=True) subtitle_description = models.TextField(blank=True) diff --git a/drp_aa_mvp/data_rights_request/models.py b/drp_aa_mvp/data_rights_request/models.py index ba0e96a..4f2b875 100644 --- a/drp_aa_mvp/data_rights_request/models.py +++ b/drp_aa_mvp/data_rights_request/models.py @@ -40,7 +40,7 @@ """ class RequestMetaData(): - version = "0.5" + version = "0.6" """ diff --git a/drp_aa_mvp/data_rights_request/templates/data_rights_request/data_rights.json b/drp_aa_mvp/data_rights_request/templates/data_rights_request/data_rights.json index 2a6d279..5cc376d 100644 --- a/drp_aa_mvp/data_rights_request/templates/data_rights_request/data_rights.json +++ b/drp_aa_mvp/data_rights_request/templates/data_rights_request/data_rights.json @@ -1,5 +1,5 @@ { - "version": "0.5", + "version": "0.6", "api_base": "https://example.com/data-rights", "actions": ["sale:opt-out", "sale:opt-in", "access", "deletion"], "user_relationships": [ ] diff --git a/drp_aa_mvp/data_rights_request/templates/drp_aa_mvp/index.html b/drp_aa_mvp/data_rights_request/templates/drp_aa_mvp/index.html index 46ef8cb..c9b703b 100644 --- a/drp_aa_mvp/data_rights_request/templates/drp_aa_mvp/index.html +++ b/drp_aa_mvp/data_rights_request/templates/drp_aa_mvp/index.html @@ -2,13 +2,13 @@

OSIRAA - Open Source Implementer's Reference Authorized Agent


-

Version 0.5.1 - Updated December 2022

+

Version 0.6.0 - Updated January 2023

How to Use this App:
OSIRAA (Open Source Implementer's Reference Authorized Agent) is test suite designed to simulate the role of an Authorized Agent in a Digital Rights Protocol (DRP) environment. The application tests for the availability, correctness and completeness of API endpoints of a Privacy Infrastructure Provider (PIP) or Covered Business (CB) partner application. See https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md for more info on DRP system roles and API specification.

Admin Tool
-A user may model a PIP or Covered Business in the Admin Tool, along with any number of users. This is a standard Python app, so you must first create an admin superuser in the usual way before you can administer data configurations. For version 0.5, a covered business requires a Discovery Endpoint, Api Secret and Auth Bearer Token, to be supplied by the PIP/CB parter. +A user may model a PIP or Covered Business in the Admin Tool, along with any number of users. This is a standard Python app, so you must first create an admin superuser in the usual way before you can administer data configurations. For version 0.6, a covered business requires a Discovery Endpoint, Api Secret and Auth Bearer Token, to be supplied by the PIP/CB parter.

Cert Tests Definitions
diff --git a/drp_aa_mvp/data_rights_request/views.py b/drp_aa_mvp/data_rights_request/views.py index 043e400..dbb04ad 100644 --- a/drp_aa_mvp/data_rights_request/views.py +++ b/drp_aa_mvp/data_rights_request/views.py @@ -1,3 +1,4 @@ +from datetime import datetime from django.core import serializers from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render @@ -13,6 +14,7 @@ from reporting.views import test_discovery_endpoint, test_excercise_endpoint, test_status_endpoint +auth_agent_drp_id = 'CR_AA_DRP_ID_001' selected_covered_biz: CoveredBusiness = None @@ -86,17 +88,19 @@ def send_request_excercise_rights(request): user_identity = IdentityUser.objects.get(pk=user_id_id) request_action = request.POST.get('request_action') covered_regime = request.POST.get('covered_regime') - request_url = covered_biz.api_root_endpoint + "/exercise" + + request_url = covered_biz.api_root_endpoint + f"/exercise?kid={auth_agent_drp_id}" bearer_token = covered_biz.auth_bearer_token - # todo: a missing param in the request_json could cause trouble ... + # todo: a missing param in the request_jwt could cause trouble ... #print('** send_request_excercise_rights(): request_action = ' + request_action) request_json = create_excercise_request_json(user_identity, covered_biz, request_action, covered_regime) + request_jwt = create_jwt(request_json) if (validators.url(request_url)): - response = post_exercise_rights(request_url, bearer_token, request_json) + response = post_exercise_rights(request_url, bearer_token, request_jwt) try: json.loads(response.text) @@ -121,10 +125,10 @@ def send_request_excercise_rights(request): request_sent_context = { 'covered_biz': covered_biz, - 'request_url': request_url, - 'response_code': response.status_code, + 'request_url': request_url, + 'response_code': response.status_code, 'response_payload': response.text, - 'test_results': excercise_test_results + 'test_results': excercise_test_results } else: @@ -302,6 +306,35 @@ def get_request_actions_form_display (covered_biz): def create_excercise_request_json(user_identity, covered_biz, request_action, covered_regime): + + issued_time = datetime.datetime.now() + expires_time = issued_time + datetime.timedelta(days=45) + + # New for 0.6 - A Data Rights Exercise request SHALL contain a JWT-encoded message body containing the following fields: + request_json = { + # 1 + "iss": auth_agent_drp_id, + "aud": covered_biz.cb_id, + "exp": expires_time, + "iat": issued_time, + + # 2 + "drp.version": "0.6", + "exercise": request_action, + "regime": covered_regime, + "relationships": [ ], + # callbackk url for the AA that the CB can hit to provide status updates, NYI + "status_callback": "https://dsr-agent.example.com/update_status", + + # 3 + # claims in IANA JSON Web Token Claims page + # https://www.iana.org/assignments/jwt/jwt.xhtml#claims + } + + # todo: - where does identity payload fit into the new scheme ... ? + + # Old (0.5) + """ jwt = create_jwt(user_identity, covered_biz) request_json = { @@ -316,6 +349,7 @@ def create_excercise_request_json(user_identity, covered_biz, request_action, co "identity": jwt, "status_callback": "https://dsr-agent.example.com/update_status" } + """ return request_json @@ -332,6 +366,8 @@ def create_jwt(user_identity, covered_biz): ) +# for 0.5, now depricated ... +""" def create_id_payload (user_identity, covered_biz): id_payload = { "iss": "https://consumerreports.com/", # will match an entry in DB of trusted partners ... @@ -344,7 +380,7 @@ def create_id_payload (user_identity, covered_biz): } return id_payload - +""" def create_revoke_request_json(request_id, reason): request_json = { @@ -433,7 +469,7 @@ def get_well_known(discovery_url): """ { - "version": "0.5", + "version": "0.6", "api_base": "https://example.com/data-rights", "actions": ["sale:opt-out", "sale:opt-in", "access", "deletion"], "user_relationships": [ ] @@ -444,17 +480,20 @@ def get_well_known(discovery_url): #POST /exercise -def post_exercise_rights(request_url, bearer_token, request_json): +def post_exercise_rights(request_url, bearer_token, request_jwt): + + # Now for 0.6: POST /exercise?kid={aa-id} + """ curl -X 'POST' 'http://localhost:8080/api/v1/drp/excercise' - -H 'accept: application/json' + -H 'accept: application/jwt' -H 'Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..Y2pZOkoc445kyqOZAKGjzw.-scnX7wxH2MeTohuCPPThCUFp7qUoyRA2hJlOsSUix6RmYRT2uz2sPUZnT-07-jYz3-32G07aIEyk30JeoHHsg3SMUa7V8Y2OlBI0WMaZ3QUscEuVZa7s3RuVVfvtePD195MMmH5w3RQkgxMrP8Lj8KubiYnRYw2n_rVt0crtP75s0NSnIF2ThWCAiq5gaGAdYSjzHwMWi9OAZekEuxNmFaTa2tu_j9Hi8uLbvjsp9-z5IjmGGsiTPNbPj5JgCWOxy-E-Ub6KMWMcCIxoLAki_Qfo5d1JwffvDQsEJT4zefm3HSdpFphv579KpgStLVhIh4r_5OnLl-w-ueHfDO3iCMgw8KIw7p5GtiXWggCejhJCohcM_g2msfG9OFeMd-7vLiFUuk4d4dzIbXXdOHGcv-lL3EyQUnRzQRXVGV3wHnxvN3Xyli4YQUqCk_qkJ1yf8LSJejqTklaCwovwuMWEu6ZwrXVq9OorMphHtnoRW-Ngw4oYa8SIME0YF3vdchCaglbNDhMVVjFkUkKsNBHfqUiZWLyXlNCluhQpMKORW5Uqk0mLtgLX_U5BlkibjcR9440UZvZoT_LBpiT21nLLtCdidHfW7bEgH9-bBMtoEwBeBM_RmxT1ysRKrdJ0NZCZgyU3FMijV-XFmIt2aZDaD2fnDJDBP1q0Aw1tVfucESZJHKUQtVKp6Q.EMaYOKnSqk2ApwP-uss3CA' """ request_headers = {'Authorization': f"Bearer {bearer_token}"} - response = requests.post(request_url, json=request_json, headers=request_headers) + response = requests.post(request_url, headers=request_headers, json=request_jwt) return response @@ -464,7 +503,7 @@ def get_status(request_url, bearer_token, request_id): """ curl -X 'GET' 'http://localhost:8080/api/v1/drp/status' - -H 'accept: application/json' + -H 'accept: application/jwt' -H 'Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..Y2pZOkoc445kyqOZAKGjzw.-scnX7wxH2MeTohuCPPThCUFp7qUoyRA2hJlOsSUix6RmYRT2uz2sPUZnT-07-jYz3-32G07aIEyk30JeoHHsg3SMUa7V8Y2OlBI0WMaZ3QUscEuVZa7s3RuVVfvtePD195MMmH5w3RQkgxMrP8Lj8KubiYnRYw2n_rVt0crtP75s0NSnIF2ThWCAiq5gaGAdYSjzHwMWi9OAZekEuxNmFaTa2tu_j9Hi8uLbvjsp9-z5IjmGGsiTPNbPj5JgCWOxy-E-Ub6KMWMcCIxoLAki_Qfo5d1JwffvDQsEJT4zefm3HSdpFphv579KpgStLVhIh4r_5OnLl-w-ueHfDO3iCMgw8KIw7p5GtiXWggCejhJCohcM_g2msfG9OFeMd-7vLiFUuk4d4dzIbXXdOHGcv-lL3EyQUnRzQRXVGV3wHnxvN3Xyli4YQUqCk_qkJ1yf8LSJejqTklaCwovwuMWEu6ZwrXVq9OorMphHtnoRW-Ngw4oYa8SIME0YF3vdchCaglbNDhMVVjFkUkKsNBHfqUiZWLyXlNCluhQpMKORW5Uqk0mLtgLX_U5BlkibjcR9440UZvZoT_LBpiT21nLLtCdidHfW7bEgH9-bBMtoEwBeBM_RmxT1ysRKrdJ0NZCZgyU3FMijV-XFmIt2aZDaD2fnDJDBP1q0Aw1tVfucESZJHKUQtVKp6Q.EMaYOKnSqk2ApwP-uss3CA' """ diff --git a/drp_aa_mvp/reporting/migrations/0001_initial.py b/drp_aa_mvp/reporting/migrations/0001_initial.py new file mode 100644 index 0000000..a4c26b2 --- /dev/null +++ b/drp_aa_mvp/reporting/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# Generated by Django 3.2.12 on 2022-12-20 19:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('user_identity', '0006_auto_20220901_2024'), + ('covered_business', '0011_coveredbusiness_cb_id'), + ] + + operations = [ + migrations.CreateModel( + name='ReportEntry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('request_type', models.CharField(blank=True, choices=[('get-rights', 'get_rights'), ('exercise', 'excercise'), ('get_status', 'get_status')], default='', max_length=10)), + ('user_first_name', models.CharField(blank=True, default='', max_length=63)), + ('user_last_name', models.CharField(blank=True, default='', max_length=63)), + ('covered_biz_name', models.CharField(blank=True, default='', max_length=127)), + ('is_pip', models.BooleanField(default=False)), + ('request_action', models.CharField(blank=True, choices=[('access', 'access'), ('deletion', 'deletion')], default='', max_length=10)), + ('covered_regime', models.CharField(blank=True, choices=[('ccpa', 'ccpa'), ('voluntary', 'voluntary')], default='', max_length=31)), + ('request_date_time', models.DateTimeField(auto_now=True)), + ('request_url', models.URLField(blank=True, default='', max_length=127)), + ('response_code', models.IntegerField(blank=True, default='')), + ('response_payload', models.CharField(blank=True, default='', max_length=2027)), + ('is_success', models.BooleanField(default=False)), + ('covered_biz_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='covered_business.coveredbusiness')), + ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='user_identity.identityuser')), + ], + ), + ] diff --git a/drp_aa_mvp/reporting/templates/reporting/index.html b/drp_aa_mvp/reporting/templates/reporting/index.html index d30de82..ff8a35f 100644 --- a/drp_aa_mvp/reporting/templates/reporting/index.html +++ b/drp_aa_mvp/reporting/templates/reporting/index.html @@ -4,7 +4,7 @@

OSIRAA - Open Source Implementer's Reference Authorized Agent

DRP Cert Test Suite

-

Version 0.5.1 - Updated December 2022

+

Version 0.6.0 - Updated January 2023

See also https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md

@@ -14,7 +14,7 @@

DRP Cert Test Suite