Skip to content

Commit 50081de

Browse files
bearer
1 parent 8c25f04 commit 50081de

4 files changed

Lines changed: 152 additions & 14 deletions

File tree

infrastructure/terraform/components/api/README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ No requirements.
1212
| <a name="input_aws_account_id"></a> [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
1313
| <a name="input_ca_pem_filename"></a> [ca\_pem\_filename](#input\_ca\_pem\_filename) | Filename for the CA truststore file within the s3 bucket | `string` | `null` | no |
1414
| <a name="input_component"></a> [component](#input\_component) | The variable encapsulating the name of this component | `string` | `"supapi"` | no |
15+
| <a name="input_core_account_id"></a> [core\_account\_id](#input\_core\_account\_id) | AWS Account ID for Core | `string` | `"000000000000"` | no |
16+
| <a name="input_core_environment"></a> [core\_environment](#input\_core\_environment) | Environment of Core | `string` | `"prod"` | no |
1517
| <a name="input_default_tags"></a> [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no |
1618
| <a name="input_enable_backups"></a> [enable\_backups](#input\_enable\_backups) | Enable backups | `bool` | `false` | no |
1719
| <a name="input_environment"></a> [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes |
@@ -34,26 +36,26 @@ No requirements.
3436

3537
| Name | Source | Version |
3638
|------|--------|---------|
37-
| <a name="module_authorizer_lambda"></a> [authorizer\_lambda](#module\_authorizer\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
39+
| <a name="module_authorizer_lambda"></a> [authorizer\_lambda](#module\_authorizer\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
3840
| <a name="module_domain_truststore"></a> [domain\_truststore](#module\_domain\_truststore) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
3941
| <a name="module_eventpub"></a> [eventpub](#module\_eventpub) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-eventpub.zip | n/a |
4042
| <a name="module_eventsub"></a> [eventsub](#module\_eventsub) | ../../modules/eventsub | n/a |
41-
| <a name="module_get_letter"></a> [get\_letter](#module\_get\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
42-
| <a name="module_get_letter_data"></a> [get\_letter\_data](#module\_get\_letter\_data) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
43-
| <a name="module_get_letters"></a> [get\_letters](#module\_get\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
44-
| <a name="module_get_status"></a> [get\_status](#module\_get\_status) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
43+
| <a name="module_get_letter"></a> [get\_letter](#module\_get\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
44+
| <a name="module_get_letter_data"></a> [get\_letter\_data](#module\_get\_letter\_data) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
45+
| <a name="module_get_letters"></a> [get\_letters](#module\_get\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
46+
| <a name="module_get_status"></a> [get\_status](#module\_get\_status) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4547
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-kms.zip | n/a |
46-
| <a name="module_letter_status_update"></a> [letter\_status\_update](#module\_letter\_status\_update) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
48+
| <a name="module_letter_status_update"></a> [letter\_status\_update](#module\_letter\_status\_update) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4749
| <a name="module_letter_status_updates_queue"></a> [letter\_status\_updates\_queue](#module\_letter\_status\_updates\_queue) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-sqs.zip | n/a |
48-
| <a name="module_letter_updates_transformer"></a> [letter\_updates\_transformer](#module\_letter\_updates\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
50+
| <a name="module_letter_updates_transformer"></a> [letter\_updates\_transformer](#module\_letter\_updates\_transformer) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
4951
| <a name="module_logging_bucket"></a> [logging\_bucket](#module\_logging\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
50-
| <a name="module_patch_letter"></a> [patch\_letter](#module\_patch\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
51-
| <a name="module_post_letters"></a> [post\_letters](#module\_post\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.24/terraform-lambda.zip | n/a |
52-
| <a name="module_post_mi"></a> [post\_mi](#module\_post\_mi) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
52+
| <a name="module_patch_letter"></a> [patch\_letter](#module\_patch\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
53+
| <a name="module_post_letters"></a> [post\_letters](#module\_post\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
54+
| <a name="module_post_mi"></a> [post\_mi](#module\_post\_mi) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
5355
| <a name="module_s3bucket_test_letters"></a> [s3bucket\_test\_letters](#module\_s3bucket\_test\_letters) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-s3bucket.zip | n/a |
5456
| <a name="module_sqs_letter_updates"></a> [sqs\_letter\_updates](#module\_sqs\_letter\_updates) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-sqs.zip | n/a |
5557
| <a name="module_supplier_ssl"></a> [supplier\_ssl](#module\_supplier\_ssl) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-ssl.zip | n/a |
56-
| <a name="module_upsert_letter"></a> [upsert\_letter](#module\_upsert\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.26/terraform-lambda.zip | n/a |
58+
| <a name="module_upsert_letter"></a> [upsert\_letter](#module\_upsert\_letter) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
5759
## Outputs
5860

5961
| Name | Description |
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import requests
22
import pytest
33
from lib.fixtures import * # NOSONAR
4-
from lib.constants import DEFAULT_CONTENT_TYPE, VALID_ENDPOINTS
4+
from lib.constants import LETTERS_ENDPOINT
55

66
@pytest.mark.test
77
@pytest.mark.devtest
@@ -10,9 +10,7 @@
1010
def test_406(url, bearer_token):
1111

1212
headers = Generators.generate_valid_headers(bearer_token)
13-
data = Generators.generate_valid_create_message_body(url)
1413

1514
get_message_response = requests.get(f"{url}/letters/status", headers=headers)
1615

17-
1816
assert get_message_response.status_code == 200
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import uuid
2+
from time import time, sleep
3+
import os
4+
import jwt
5+
import requests
6+
import json
7+
from .secret import Secret
8+
9+
10+
class AuthenticationCache():
11+
def __init__(self):
12+
self.tokens = {}
13+
14+
# Number of consecutive authentication tests before considering it worked.
15+
# This is required because apigee might be using load balancing.
16+
self.consecutive_tests = 4
17+
18+
# Number of seconds before trying a test again
19+
self.time_between_tests = 10
20+
21+
# Number of attempts before giving up. It can take up to 5 minutes for the
22+
# addition of a product to an application to take effect.
23+
# time_between_tests * max_tests = 300 seconds = 5 minutes.
24+
self.max_tests = 30
25+
26+
# How long the token will stay valid
27+
self.token_validity = 180
28+
29+
def generate_authentication(self, env, base_url):
30+
31+
# For the test_url, note that we don't need a message_id that actually exists in
32+
# the backend. The test will only check that the API doesn't return a 401,
33+
# a 404 response means the authentication is working.
34+
test_url = f"{base_url}/v1/messages/message_id"
35+
36+
if env in ["internal-dev", "ref"]:
37+
api_key = os.environ["NON_PROD_API_KEY"]
38+
private_key = os.environ["NON_PROD_PRIVATE_KEY"]
39+
url = "https://internal-dev.api.service.nhs.uk/oauth2/token"
40+
kid = "local"
41+
elif env == "int":
42+
api_key = os.environ.get("INTEGRATION_API_KEY")
43+
private_key = os.environ.get("INTEGRATION_PRIVATE_KEY")
44+
url = "https://int.api.service.nhs.uk/oauth2/token"
45+
kid = "local"
46+
elif env == "prod":
47+
api_key = os.environ.get("PRODUCTION_API_KEY")
48+
private_key = os.environ.get("PRODUCTION_PRIVATE_KEY")
49+
url = "https://api.service.nhs.uk/oauth2/token"
50+
kid = "prod-1"
51+
else:
52+
raise ValueError("Unknown value: ", env)
53+
54+
_, latest_token_expiry = self.tokens.get(env, (None, 0))
55+
56+
# Generate new token if latest token will expire in 15 seconds
57+
if env not in self.tokens or latest_token_expiry < int(time()) + 15:
58+
self.tokens[env] = self.generate_and_test_new_token(api_key, private_key, url, kid, test_url)
59+
60+
bearer_token = self.tokens[env][0]
61+
return Secret(bearer_token)
62+
63+
def generate_and_test_new_token(self, api_key, private_key, url, kid, test_url):
64+
new_token = None
65+
valid_auth = False
66+
67+
for i in range(self.max_tests):
68+
print(f"Testing new token, attemp #{i+1}")
69+
if new_token is None:
70+
new_token = self.generate_new_token(api_key, private_key, url, kid)
71+
time_since_new_token = int(time())
72+
73+
if self.test_token(test_url, new_token[0]):
74+
valid_auth = True
75+
break
76+
77+
# The test failed, give apigee some time to update its cache.
78+
sleep(self.time_between_tests)
79+
80+
if int(time()) - time_since_new_token > (self.token_validity / 2):
81+
# Token about to expire, generate a new one
82+
new_token = None
83+
84+
if valid_auth:
85+
print("Token generated successfully")
86+
return new_token
87+
88+
print("Could not generate token")
89+
raise RuntimeError("Could not generate token")
90+
91+
def generate_new_token(self, api_key, private_key, url, kid):
92+
pk_pem = None
93+
with open(private_key, "r") as f:
94+
pk_pem = f.read()
95+
96+
token_expiry = int(time()) + self.token_validity
97+
98+
claims = {
99+
"sub": api_key,
100+
"iss": api_key,
101+
"jti": str(uuid.uuid4()),
102+
"aud": url,
103+
"exp": token_expiry,
104+
}
105+
additional_headers = {"kid": kid}
106+
107+
j = jwt.encode(
108+
claims, pk_pem, algorithm="RS512", headers=additional_headers
109+
)
110+
111+
resp = requests.post(url, headers={
112+
"Content-Type": "application/x-www-form-urlencoded"
113+
}, data={
114+
"grant_type": "client_credentials",
115+
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
116+
"client_assertion": j
117+
}
118+
)
119+
details = json.loads(resp.content)
120+
121+
return (f"Bearer {details.get('access_token')}", token_expiry)
122+
123+
def test_token(self, test_url, token):
124+
for _ in range(self.consecutive_tests):
125+
resp = requests.get(
126+
test_url,
127+
headers={
128+
"Authorization": token,
129+
"Accept": "*/*",
130+
"Content-Type": "application/json"
131+
},
132+
)
133+
if resp.status_code == 401:
134+
return False
135+
136+
return True

tests/e2e-tests/lib/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
VALID_ENDPOINTS = ["/letters"]
22
METHODS = ["get", "post"]
33
DEFAULT_CONTENT_TYPE = "application/vnd.api+json"
4+
LETTERS_ENDPOINT = "/letters"
5+
MI_ENDPOINT = "mi"

0 commit comments

Comments
 (0)