Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exploring how to make bearer token optional #1590

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
"console": "integratedTerminal",
"justMyCode": false
},
{
"name": "Python: Flask",
Expand Down Expand Up @@ -62,7 +63,8 @@
"--headless"
],
"gevent": true,
"console": "integratedTerminal"
"console": "integratedTerminal",
"justMyCode": false,
}
]
}
17 changes: 13 additions & 4 deletions app/celery/service_callback_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,24 @@ def send_complaint_to_service(self, complaint_data):

def _send_data_to_service_callback_api(self, data, service_callback_url, token, function_name):
notification_id = data["notification_id"] if "notification_id" in data else data["id"]

def _build_headers(token: str):
if token:
return {
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(token),
}
else:
return {
"Content-Type": "application/json",
}

try:
response = request(
method="POST",
url=service_callback_url,
data=json.dumps(data),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(token),
},
headers=_build_headers(token),
timeout=60,
)
current_app.logger.info(
Expand Down
2 changes: 1 addition & 1 deletion app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ class ServiceCallbackApi(BaseModel, Versioned):
service = db.relationship("Service", backref="service_callback_api")
url = db.Column(db.String(), nullable=False)
callback_type = db.Column(db.String(), db.ForeignKey("service_callback_type.name"), nullable=True)
_bearer_token = db.Column("bearer_token", db.String(), nullable=False)
_bearer_token = db.Column("bearer_token", db.String(), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, nullable=True)
updated_by = db.relationship("User")
Expand Down
20 changes: 20 additions & 0 deletions migrations/versions/0422_bearer_token_is_nullable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""

Revision ID: 0422_bearer_token_is_nullable
Revises: 0421_add_sms_daily_limit
Create Date: 2022-09-07 16:00:00

"""
import sqlalchemy as sa
from alembic import op

revision = "0422_bearer_token_is_nullable"
down_revision = "0421_add_sms_daily_limit"


def upgrade():
op.alter_column("service_callback_api", "bearer_token", nullable=True)


def downgrade():
op.alter_column("service_callback_api", "bearer_token", nullable=False, server_default='')
92 changes: 66 additions & 26 deletions tests/app/celery/test_service_callback_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,44 @@ def test_send_delivery_status_to_service_post_https_request_to_service_with_sign
assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token)


def test_send_complaint_to_service_posts_https_request_to_service_with_signed_data(
notify_db_session,
):
with freeze_time("2001-01-01T12:00:00"):
callback_api, template = _set_up_test_data("email", "complaint")
@pytest.mark.parametrize("notification_type", ["email", "letter", "sms"])
def test__send_data_to_service_callback_api_with_no_bearer_token(notify_db_session, mocker, notification_type):
callback_api, template = _set_up_test_data(notification_type, "delivery_status", None)

notification = create_notification(template=template)
complaint = create_complaint(service=template.service, notification=notification)
complaint_data = _set_up_data_for_complaint(callback_api, complaint, notification)
with requests_mock.Mocker() as request_mock:
request_mock.post(callback_api.url, json={}, status_code=200)
send_complaint_to_service(complaint_data)
datestr = datetime(2017, 6, 20)

mock_data = {
"notification_id": str(notification.id),
"complaint_id": str(complaint.id),
"reference": notification.client_reference,
"to": notification.to,
"complaint_date": datetime.utcnow().strftime(DATETIME_FORMAT),
}
notification = save_notification(
create_notification(
template=template,
created_at=datestr,
updated_at=datestr,
sent_at=datestr,
status="sent",
)
)
signed_status_update = _set_up_data_for_status_update(callback_api, notification)
with requests_mock.Mocker() as request_mock:
request_mock.post(callback_api.url, json={}, status_code=200)
send_delivery_status_to_service(notification.id, signed_status_update=signed_status_update)

assert request_mock.call_count == 1
assert request_mock.request_history[0].url == callback_api.url
assert request_mock.request_history[0].method == "POST"
assert request_mock.request_history[0].text == json.dumps(mock_data)
assert request_mock.request_history[0].headers["Content-type"] == "application/json"
assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token)
mock_data = {
"id": str(notification.id),
"reference": notification.client_reference,
"to": notification.to,
"status": notification.status,
"provider_response": notification.provider_response,
"created_at": datestr.strftime(DATETIME_FORMAT),
"completed_at": datestr.strftime(DATETIME_FORMAT),
"sent_at": datestr.strftime(DATETIME_FORMAT),
"notification_type": notification_type,
}

assert request_mock.call_count == 1
assert request_mock.request_history[0].url == callback_api.url
assert request_mock.request_history[0].method == "POST"
assert request_mock.request_history[0].text == json.dumps(mock_data)
assert request_mock.request_history[0].headers["Content-type"] == "application/json"
assert request_mock.request_history[0].headers.get("Authorization") is None


@pytest.mark.parametrize("notification_type", ["email", "letter", "sms"])
Expand Down Expand Up @@ -159,13 +170,42 @@ def test_send_delivery_status_to_service_succeeds_if_sent_at_is_none(notify_db_s
assert mocked.call_count == 0


def _set_up_test_data(notification_type, callback_type):
def test_send_complaint_to_service_posts_https_request_to_service_with_signed_data(
notify_db_session,
):
with freeze_time("2001-01-01T12:00:00"):
callback_api, template = _set_up_test_data("email", "complaint")

notification = create_notification(template=template)
complaint = create_complaint(service=template.service, notification=notification)
complaint_data = _set_up_data_for_complaint(callback_api, complaint, notification)
with requests_mock.Mocker() as request_mock:
request_mock.post(callback_api.url, json={}, status_code=200)
send_complaint_to_service(complaint_data)

mock_data = {
"notification_id": str(notification.id),
"complaint_id": str(complaint.id),
"reference": notification.client_reference,
"to": notification.to,
"complaint_date": datetime.utcnow().strftime(DATETIME_FORMAT),
}

assert request_mock.call_count == 1
assert request_mock.request_history[0].url == callback_api.url
assert request_mock.request_history[0].method == "POST"
assert request_mock.request_history[0].text == json.dumps(mock_data)
assert request_mock.request_history[0].headers["Content-type"] == "application/json"
assert request_mock.request_history[0].headers["Authorization"] == "Bearer {}".format(callback_api.bearer_token)


def _set_up_test_data(notification_type, callback_type, token="something_unique"):
service = create_service(restricted=True)
template = create_template(service=service, template_type=notification_type, subject="Hello")
callback_api = create_service_callback_api(
service=service,
url="https://some.service.gov.uk/",
bearer_token="something_unique",
bearer_token=token,
callback_type=callback_type,
)
return callback_api, template
Expand Down