Skip to content
Open
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: 5 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Release
env:
PROXYGEN_CLIENT_ID: ${{ secrets.PROXYGEN_CLIENT_ID }}
PROXYGEN_CLIENT_SECRET: ${{ secrets.PROXYGEN_CLIENT_SECRET }}
PTL_PROXYGEN_CLIENT_ID: ${{ secrets.PTL_PROXYGEN_CLIENT_ID }}
PTL_PROXYGEN_CLIENT_SECRET: ${{ secrets.PTL_PROXYGEN_CLIENT_SECRET }}

on:
push:
Expand All @@ -29,11 +31,13 @@ jobs:

- name: set poetry credentials
run: poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}

- name: template credentials
run: |
sed -i "s/PROXYGEN_CLIENT_ID = \"\"/PROXYGEN_CLIENT_ID = \"$PROXYGEN_CLIENT_ID\"/g" proxygen_cli/lib/constants.py
sed -i "s/PROXYGEN_CLIENT_SECRET = \"\"/PROXYGEN_CLIENT_SECRET = \"$PROXYGEN_CLIENT_SECRET\"/g" proxygen_cli/lib/constants.py
sed -i "s/PTL_PROXYGEN_CLIENT_ID = \"\"/PTL_PROXYGEN_CLIENT_ID = \"$PTL_PROXYGEN_CLIENT_ID\"/g" proxygen_cli/lib/constants.py
sed -i "s/PTL_PROXYGEN_CLIENT_SECRET = \"\"/PTL_PROXYGEN_CLIENT_SECRET = \"$PTL_PROXYGEN_CLIENT_SECRET\"/g" proxygen_cli/lib/constants.py

- name: build
run: poetry build
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ on:
env:
PROXYGEN_CLIENT_ID: ${{ secrets.PROXYGEN_CLIENT_ID }}
PROXYGEN_CLIENT_SECRET: ${{ secrets.PROXYGEN_CLIENT_SECRET }}
PTL_PROXYGEN_CLIENT_ID: ${{ secrets.PTL_PROXYGEN_CLIENT_ID }}
PTL_PROXYGEN_CLIENT_SECRET: ${{ secrets.PTL_PROXYGEN_CLIENT_SECRET }}

jobs:
build:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on: pull_request
env:
PROXYGEN_CLIENT_ID: ${{ secrets.PROXYGEN_CLIENT_ID }}
PROXYGEN_CLIENT_SECRET: ${{ secrets.PROXYGEN_CLIENT_SECRET }}
PTL_PROXYGEN_CLIENT_ID: ${{ secrets.PTL_PROXYGEN_CLIENT_ID }}
PTL_PROXYGEN_CLIENT_SECRET: ${{ secrets.PTL_PROXYGEN_CLIENT_SECRET }}

jobs:
check-version-bump:
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ After installation, the `proxygen` executable is available. Typing `proxygen` di
### Settings
To specify the API you are developing for, use:
```
proxygen settings set api <API-NAME>
proxygen settings set api <API-NAME> --env <prod/ptl>
```
Parameter --env is optional, if the environment is not provided then CLI will default to Prod.

Your user must have the appropriate permissions for managing instances, secrets, and specifications related to the specified API. If permissions are insufficient, commands will fail. Reach out to the `platforms-api-producer-support` channel for assistance with permissions.


Expand All @@ -31,8 +33,10 @@ If you are setting up user access for the first time you will first need to requ

To setup the Proxygen CLI credentials for the first time with user access enter the following command:
```
proxygen credentials set
proxygen credentials set --env <prod/ptl>
```
Parameter --env is optional, if the environment is not provided then CLI will default to Prod.

The CLI will ask you for your username, and password. These credentials will be securely stored on your local machine in the directory ~/.proxygen/credentials.yaml. The client_id and secret used for user access will be automatically added to this file after running the command.

Your user must have permissions to manipulate instances/secrets/specs for the API you set here. If you do not have sufficient permissions commands will fail. If you believe your permissions are incorrect, contact the API platform team via the [platforms-api-producer-support](https://nhsdigital-platforms.slack.com/archives/C016JRWN6AY) channel.
Expand All @@ -46,8 +50,10 @@ After having set up your API using the instructions at [Getting set up with prox

Using this information enter the following command:
```
proxygen credentials set private_key_path <PATH_TO_PRIVATE_KEY> key_id <KEY_ID_FOR_PRIVATE_KEY> client_id <MACHINE_USER_CLIENT_ID>
proxygen credentials set private_key_path <PATH_TO_PRIVATE_KEY> key_id <KEY_ID_FOR_PRIVATE_KEY> client_id <MACHINE_USER_CLIENT_ID> --env <prod/ptl>
```
Parameter --env is optional, if the environment is not provided then CLI will default to Prod.

The CLI will ask you for your username, and password. For machine authentication, the username and password must be left blank. The credentials will be securely stored in the directory ~/.proxygen/credentials.yaml.

> **_NOTE:_** If you are switching between using user access and machine-user access bare in mind:
Expand Down Expand Up @@ -77,7 +83,7 @@ Use the following command to publish an API specification:
```
# Production
proxygen spec publish <path_to_spec>

# UAT
proxygen spec publish <path_to_spec> --uat
```
Expand Down
801 changes: 466 additions & 335 deletions poetry.lock

Large diffs are not rendered by default.

39 changes: 27 additions & 12 deletions proxygen_cli/cli/command_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pydantic
import os

from proxygen_cli.lib import constants
from proxygen_cli.lib import output
from proxygen_cli.lib.credentials import (
Credentials, get_credentials, _yaml_credentials_file_source, create_yaml_credentials_file, initialise_credentials)
Expand Down Expand Up @@ -42,11 +43,11 @@
creds = get_credentials()
click.echo(getattr(creds, key))


@credentials.command()
@click.argument("custom_pairs", nargs=-1, metavar="KEY VALUE", required=False)
@click.option("--force", is_flag=True, help="Force re-entry of standard credentials")
def set(custom_pairs, force):
@click.option("--env", type=click.Choice(["prod", "ptl"], case_sensitive=False), default="prod", help="Environment to use for authentication")
def set(custom_pairs, force, env):
"""
Write a value to your credentials.
"""
Expand All @@ -55,18 +56,32 @@

current_credentials = _yaml_credentials_file_source(None)

# Check if user credentials are set
base_credentials_set = all(
current_credentials.get(field) is not None
for field in ["username", "password"]
)
#current_credentials["env"] = env.lower()

Check warning on line 59 in proxygen_cli/cli/command_credentials.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this commented out code.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tiawj5MaBOhPBfex_&open=AZ1tiawj5MaBOhPBfex_&pullRequest=78

KEYCLOAK_URLS = {
"prod": "https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"ptl": "https://identity.ptl.api.platform.nhs.uk/realms/api-producers",
}

PROXYGEN_CLIENT_IDS = {
"prod": constants.PROXYGEN_CLIENT_ID,
"ptl": constants.PTL_PROXYGEN_CLIENT_ID,
}

PROXYGEN_CLIENT_SECRETS = {
"prod": constants.PROXYGEN_CLIENT_SECRET,
"ptl": constants.PTL_PROXYGEN_CLIENT_SECRET,
}

current_credentials["base_url"] = KEYCLOAK_URLS[env.lower()]
current_credentials["client_id"] = PROXYGEN_CLIENT_IDS[env.lower()]
current_credentials["client_secret"] = PROXYGEN_CLIENT_SECRETS[env.lower()]

if not base_credentials_set or force:
username = click.prompt("Enter username", default="", show_default=False)
password = click.prompt("Enter password", default="", show_default=False)
username = click.prompt("Enter username", default="", show_default=False)
password = click.prompt("Enter password", default="", show_default=False)

current_credentials["username"] = username
current_credentials["password"] = password
current_credentials["username"] = username
current_credentials["password"] = password

# Prompt for individual custom key-value pairs
for i in range(0, len(custom_pairs), 2):
Expand Down
13 changes: 11 additions & 2 deletions proxygen_cli/cli/command_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def settings():
"""



def _get_setting(key):
return getattr(SETTINGS, key)

Expand All @@ -31,6 +30,7 @@ def list():
"""
output.print_spec(json.loads(SETTINGS.json(exclude_none=True)))


@settings.command()
@click.argument("key", type=CHOICE_OF_SETTINGS_KEYS)
def get(key):
Expand All @@ -43,14 +43,23 @@ def get(key):
@settings.command()
@click.argument("key", type=CHOICE_OF_SETTINGS_KEYS)
@click.argument("value")
def set(key, value):
@click.option("--env", type=click.Choice(["prod", "ptl"], case_sensitive=False), default="prod", help="Environment to use for authentication")
def set(key, value, env):
"""
Write a value to your settings.
"""
_get_setting(key)

current_settings = _yaml_settings_file_source(None)

PROXYGEN_URLS = {
"prod": "https://proxygen.prod.api.platform.nhs.uk",
"ptl": "https://proxygen.ptl.api.platform.nhs.uk",
}

current_settings["endpoint_url"] = PROXYGEN_URLS[env.lower()]
current_settings[key] = value

try:
new_settings = json.loads(Settings(**current_settings).json(exclude_none=True))
except pydantic.ValidationError as e:
Expand Down
7 changes: 5 additions & 2 deletions proxygen_cli/lib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
"mtls"
]

PROXYGEN_CLIENT_ID = ""
PROXYGEN_CLIENT_SECRET = ""
PROXYGEN_CLIENT_ID = " "
PROXYGEN_CLIENT_SECRET = " "

PTL_PROXYGEN_CLIENT_ID = " "
PTL_PROXYGEN_CLIENT_SECRET = " "
8 changes: 3 additions & 5 deletions proxygen_cli/lib/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@


class Credentials(BaseSettings):
base_url: AnyHttpUrl = (
"https://identity.prod.api.platform.nhs.uk/realms/api-producers"
)
base_url: AnyHttpUrl
private_key_path: Optional[str] = None
key_id: Optional[str] = None
client_id: str = constants.PROXYGEN_CLIENT_ID
client_secret: Optional[str] = constants.PROXYGEN_CLIENT_SECRET
client_id: str = None

Check warning on line 37 in proxygen_cli/lib/credentials.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace the type hint "str" with "Optional[str]" or don't assign "None" to "client_id"

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0N5MaBOhPBfeyA&open=AZ1tia0N5MaBOhPBfeyA&pullRequest=78
client_secret: Optional[str] = None
username: Optional[str] = None
password: Optional[str] = None

Expand Down
61 changes: 30 additions & 31 deletions proxygen_cli/test/command_credentials_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

def get_test_credentials(**kwargs):
base_credentials = {
"base_url": "https://mock-keycloak-url.nhs.uk",
"client_id": "mock-api-client",
"client_secret": "1a2f4g5",
"base_url": "https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"password": "mock-password",
"username": "mock-user",
}
Expand All @@ -31,7 +31,7 @@
#get mock values for credentials.yaml file
def get_test_proxygen_client_credentials(**kwargs):
client_credentials = {
"base_url": "https://mock-keycloak-url.nhs.uk",
"base_url": "https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"password": "mock-password",
Expand All @@ -56,10 +56,10 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",

Check failure on line 59 in proxygen_cli/test/command_credentials_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers" 7 times.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0i5MaBOhPBfeyF&open=AZ1tia0i5MaBOhPBfeyF&pullRequest=78
"client_id: "+CLIENT_ID,

Check failure on line 60 in proxygen_cli/test/command_credentials_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "client_id: " 7 times.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0i5MaBOhPBfeyE&open=AZ1tia0i5MaBOhPBfeyE&pullRequest=78
"client_secret: "+CLIENT_SECRET,

Check failure on line 61 in proxygen_cli/test/command_credentials_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "client_secret: " 6 times.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0i5MaBOhPBfeyC&open=AZ1tia0i5MaBOhPBfeyC&pullRequest=78
"password: mock-password",
"password: ''",

Check failure on line 62 in proxygen_cli/test/command_credentials_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "password: ''" 5 times.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0i5MaBOhPBfeyD&open=AZ1tia0i5MaBOhPBfeyD&pullRequest=78
"username: new-username",
]
)
Expand Down Expand Up @@ -89,11 +89,11 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: "+CLIENT_ID,
"client_secret: "+CLIENT_SECRET,
"password: new-password123",
"username: new-username123",
"username: ''",

Check failure on line 96 in proxygen_cli/test/command_credentials_test.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "username: ''" 3 times.

See more on https://sonarcloud.io/project/issues?id=NHSDigital_proxygen-cli&issues=AZ1tia0i5MaBOhPBfeyB&open=AZ1tia0i5MaBOhPBfeyB&pullRequest=78
]
)

Expand All @@ -106,8 +106,7 @@

# Convert mock_credentials to a dictionary
mock_credentials_dict = yaml.safe_load(mock_credentials)
print("HELLO")
print(mock_credentials_dict)

# Set an initial value for the username and password field
initial_clientid = "old-clientid"
mock_credentials_dict["username"] = initial_clientid
Expand All @@ -122,11 +121,11 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"client_id: new-clientid123",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: " + CLIENT_ID,
"client_secret: new-clientsecret123",
"password: mock-password",
"username: mock-user",
"password: ''",
"username: ''",
]
)

Expand Down Expand Up @@ -167,7 +166,7 @@
runner = CliRunner()
result = runner.invoke(cmd_credentials.get, ["client_id"])

assert result.output.strip() == "mock-api-client"
assert result.output.strip() == "proxygen-cli-user-client"


def test_get_invalid_setting(patch_config):
Expand Down Expand Up @@ -211,10 +210,10 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"client_id: mock-api-client",
"client_secret: 1a2f4g5",
"password: mock-password",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: "+CLIENT_ID,
"client_secret: "+CLIENT_SECRET,
"password: ''",
"username: new-username",
]
)
Expand All @@ -239,10 +238,10 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"client_id: mock-api-client",
"client_secret: 1a2f4g5",
"password: mock-password",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: "+CLIENT_ID,
"client_secret: "+CLIENT_SECRET,
"password: ''",
"username: new-username123", # Updated value
]
)
Expand All @@ -260,12 +259,12 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"client_id: mock-api-client",
"client_secret: 1a2f4g5",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: "+CLIENT_ID,
"client_secret: "+CLIENT_SECRET,
"key_id: test_kid",
"password: mock-password",
"username: mock-user",
"password: ''",
"username: ''",
]
)

Expand Down Expand Up @@ -302,9 +301,9 @@

expected_credentials = "\n".join(
[
"base_url: https://mock-keycloak-url.nhs.uk",
"client_id: mock-api-client",
"client_secret: 1a2f4g5",
"base_url: https://identity.prod.api.platform.nhs.uk/realms/api-producers",
"client_id: "+CLIENT_ID,
"client_secret: "+CLIENT_SECRET,
"password: mock-password",
"username: mock-user",
]
Expand Down
2 changes: 1 addition & 1 deletion proxygen_cli/test/command_settings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_set_setting(patch_config, settings_file):
expected_settings = "\n".join(
[
"api: new_api_name",
"endpoint_url: https://mock-proxygen.nhs.uk",
"endpoint_url: https://proxygen.prod.api.platform.nhs.uk",
"spec_output_format: json",
]
)
Expand Down
2 changes: 1 addition & 1 deletion proxygen_cli/test/validate_cli_version_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_validate_cli_version_success(patch_request):


def test_validate_cli_version_failure(patch_request):
required_version = "4.0.0"
required_version = "5.0.0"

mocked_response = {"proxygen_cli": {"min_version": required_version}}
with patch_request(200, mocked_response), pytest.raises(RuntimeError) as e:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "proxygen-cli"
version = "3.0.2"
version = "4.0.0"
description = "CLI for interacting with NHSD APIM's proxygen service"
authors = ["Ben Strutt <ben.strutt1@nhs.net>"]
readme = "README.md"
Expand Down
Loading