Skip to content

Commit

Permalink
Merge pull request #7 from consumer-reports-digital-lab/OSIRAA-0.6
Browse files Browse the repository at this point in the history
OSIRAA-0.6
  • Loading branch information
JohnSzingerCR authored Jan 18, 2023
2 parents eed68a4 + 43a6e78 commit d97e467
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 87 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# OSIRAA - Open Source Implementers’ Reference Authorized Agent

Version 0.5.1 - Updated December 2022
Version 0.6.0 - Updated January 2023

## There is a live demo version of OSIRAA available at [https://osiraa.datarightsprotocol.org/](https://osiraa.datarightsprotocol.org/).

## 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 <a href="https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md" target="blank">https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md</a> 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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),
),
]
Original file line number Diff line number Diff line change
@@ -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',
),
]
1 change: 1 addition & 0 deletions drp_aa_mvp/covered_business/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion drp_aa_mvp/data_rights_request/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

"""
class RequestMetaData():
version = "0.5"
version = "0.6"
"""


Expand Down
Original file line number Diff line number Diff line change
@@ -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": [ ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<h2>OSIRAA - Open Source Implementer's Reference Authorized Agent</h2>
<br/>

<h3>Version 0.5.1 - Updated December 2022</h3>
<h3>Version 0.6.0 - Updated January 2023</h3>

<p><b>How to Use this App:</b><br/>
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 <a href="https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md" target="blank">https://github.com/consumer-reports-digital-lab/data-rights-protocol/blob/main/data-rights-protocol.md</a> for more info on DRP system roles and API specification.</p>

<p><b><a href="/admin/">Admin Tool</a></b><br/>
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.
</p>

<p><a href="/reporting/"><b>Cert Tests Definitions</b></a><br/>
Expand Down
135 changes: 62 additions & 73 deletions drp_aa_mvp/data_rights_request/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from django.core import serializers
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
Expand All @@ -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


Expand Down Expand Up @@ -91,17 +93,18 @@ 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)
Expand All @@ -126,10 +129,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:
Expand All @@ -150,15 +153,15 @@ def send_request_get_status(request):
user_id_id = request.POST.get('user_identity')
user_identity = IdentityUser.objects.get(pk=user_id_id)
request_url = covered_biz.api_root_endpoint + "/status"
bearer_token = covered_biz.auth_bearer_token

# todo: might not find a request id, handle this ...
# todo: might not find a request id, need to handle this case ...
request_id = get_request_id (covered_biz, user_identity)

if (validators.url(request_url)):
response = get_status(request_url, bearer_token, request_id)
# Data Rights Status requests SHALL be made without Authorization headers
response = get_status(request_url, request_id)

# todo: log request to DB, setup status ping ...
# todo: log request to DB, setup status callback ...

status_test_results = test_status_endpoint(request_url, response)

Expand Down Expand Up @@ -187,14 +190,16 @@ def send_request_revoke(request):
cov_biz_id = request.POST.get('covered_business')
user_identity = IdentityUser.objects.get(pk=user_id_id)
covered_biz = CoveredBusiness.objects.get(pk=cov_biz_id)
request_url = covered_biz.api_root_endpoint + "/revoke"
request_url = covered_biz.api_root_endpoint + f"/revoke?kid={auth_agent_drp_id}"
bearer_token = covered_biz.auth_bearer_token
request_id = "pri_5e9f3775-549b-42ba-8d9f-c94a2e640f50" #"c789ff35-7644-4ceb-9981-4b35c264aac3"
reason = "I don't want my account deleted."
reqest_json = create_revoke_request_json(request_id, reason)


request_json = create_revoke_request_json(request_id, reason)
request_jwt = create_jwt(request_json)

if (validators.url(request_url)):
response = post_revoke(request_url, bearer_token, reqest_json)
response = post_revoke(request_url, bearer_token, request_jwt)

# todo: log request to DB, stop status ping ...

Expand Down Expand Up @@ -241,8 +246,6 @@ def data_rights_request_sent_return(request):
#-------------------------------------------------------------------------------------------------#

def set_covered_biz_well_known_params(covered_biz, response):

# set api_root and supported_actions for covered_biz
try:
json.loads(response.text)
except ValueError as e:
Expand Down Expand Up @@ -274,12 +277,14 @@ def get_covered_biz_form_display(covered_businesses, selected_biz):

return covered_businesses_form_display


def covered_biz_has_supported_action(covered_biz, action):
if action in covered_biz.supported_actions:
return '' # indicates NOT disabled

return 'disabled'


def get_request_actions_form_display (covered_biz):
if (covered_biz is None):
request_actions = [
Expand Down Expand Up @@ -311,19 +316,32 @@ def get_request_actions_form_display (covered_biz):


def create_excercise_request_json(user_identity, covered_biz, request_action, covered_regime):
jwt = create_jwt(user_identity, covered_biz)
issued_time = datetime.datetime.now()
expires_time = issued_time + datetime.timedelta(days=45)

# 0.6 - A Data Rights Exercise request SHALL contain a JWT-encoded message body containing the following fields:
request_json = {
"meta": {
"version": "0.5"
},
# 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,
"exercise": [
request_action
],
"relationships": [],
"identity": jwt,
"status_callback": "https://dsr-agent.example.com/update_status"
"relationships": [ ],
# callback url for the AA that the CB can hit to provide status updates, NYI
"status_callback": "https://dsr-agent.pslip.com/update_status",

# 3
# claims in IANA JSON Web Token Claims page
# see https://www.iana.org/assignments/jwt/jwt.xhtml#claims for details
"name": (user_identity.last_name + ", " + user_identity.first_name),
"email": user_identity.email,
"phone_number": user_identity.phone_number,
"address": user_identity.address1,
}

return request_json
Expand All @@ -341,20 +359,6 @@ def create_jwt(user_identity, covered_biz):
)


def create_id_payload (user_identity, covered_biz):
id_payload = {
"iss": "https://consumerreports.com/", # will match an entry in DB of trusted partners ...
#"aud": covered_biz.name, # skip for now, not yet supported on PIP side ...
"sub": user_identity.email, # identifier at the issuer, e.g. id of the user ...
"name": (user_identity.last_name + ", " + user_identity.first_name),
"email": user_identity.email,
"phone_number": user_identity.phone_number,
"address": user_identity.address1
}

return id_payload


def create_revoke_request_json(request_id, reason):
request_json = {
"request_id": request_id,
Expand All @@ -366,12 +370,12 @@ def create_revoke_request_json(request_id, reason):

#-------------------------------------------------------------------------------------------------#

def create_drp_request_transaction(user_identity, covered_biz, reqest_json, response_json):

def create_drp_request_transaction(user_identity, covered_biz, request_json, response_json):
identity_payload = IdentityPayload.objects.create(
#issuer =
#audience =
#subject =
issuer = request_json.iss,
audience = request_json.aud,
expires_time = request_json.exp,
issued_time = request_json.iat,
name = user_identity.first_name, #user_identityfull_name,
email = user_identity.email,
email_verified = user_identity.email_verified,
Expand All @@ -384,12 +388,12 @@ def create_drp_request_transaction(user_identity, covered_biz, reqest_json, resp

data_rights_request = DataRightsRequest.objects.create(
#request_id not sent on /excercise call
#meta = reqest_json['meta'],
relationships = reqest_json['relationships'],
status_callback = reqest_json['status_callback'],
regime = reqest_json['regime'],
exercise = reqest_json['exercise'],
#identity = reqest_json['identity'],
#meta = request_json['meta'],
relationships = request_json['relationships'],
status_callback = request_json['status_callback'],
regime = request_json['regime'],
exercise = request_json['exercise'],
#identity = request_json['identity'],
)

data_rights_status = DataRightsStatus.objects.create(
Expand Down Expand Up @@ -446,7 +450,7 @@ def get_well_known(discovery_url, bearer_token=""):

"""
{
"version": "0.5",
"version": "0.6",
"api_base": "https://example.com/data-rights",
"actions": ["sale:opt-out", "sale:opt-in", "access", "deletion"],
"user_relationships": [ ]
Expand All @@ -456,40 +460,25 @@ def get_well_known(discovery_url, bearer_token=""):
return response


#POST /exercise
def post_exercise_rights(request_url, bearer_token, request_json):
"""
curl -X 'POST'
'http://localhost:8080/api/v1/drp/excercise'
-H 'accept: application/json'
-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'
"""

#POST /exercise?kid={aa-id}
def post_exercise_rights(request_url, bearer_token, request_jwt):
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


# GET /status?request_id=c789ff35-7644-4ceb-9981-4b35c264aac3
def get_status(request_url, bearer_token, request_id):
"""
curl -X 'GET'
'http://localhost:8080/api/v1/drp/status'
-H 'accept: application/json'
-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'
"""

def get_status(request_url, request_id):
status_request_url = request_url + "?request_id=" + request_id
request_headers = {'Authorization': f"Bearer {bearer_token}"}

response = requests.get(status_request_url, headers=request_headers)
response = requests.get(status_request_url)

return response



#POST /revoke?kid={aa-id}
def post_revoke(request_url, bearer_token, request_json):
request_headers = {'Authorization': f"Bearer {bearer_token}"}

Expand Down
Loading

0 comments on commit d97e467

Please sign in to comment.