diff --git a/client-samples/java/rest/README.md b/client-samples/java/rest/README.md index 5e4f22f..6e90607 100644 --- a/client-samples/java/rest/README.md +++ b/client-samples/java/rest/README.md @@ -30,7 +30,7 @@ Now that we have the file private key in a usable format we can use the Java Cli ## Configuring the Java Client ### Properties -Make these changes to the `META-INF/microprofile-config.properties` resource file +Make these changes to the `META-INF/microprofile-config.properties` resource file. Alternatively, you can use the [`CreateConfig`](./src/main/java/com/ms/infra/example/application/CreateConfig.java) script to help generate it for you. | Property Name | Description | Required | |-----------------------------------|-----------------------------------------------------------------------------------------|----------| diff --git a/client-samples/java/rest/src/main/java/com/ms/infra/example/application/CreateConfig.java b/client-samples/java/rest/src/main/java/com/ms/infra/example/application/CreateConfig.java new file mode 100644 index 0000000..f909b70 --- /dev/null +++ b/client-samples/java/rest/src/main/java/com/ms/infra/example/application/CreateConfig.java @@ -0,0 +1,161 @@ +package com.ms.infra.example.application; + +import java.util.HashMap; + +import java.util.Map; +import java.util.Scanner; +import java.util.Properties; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CreateConfig { + private static Scanner scanner; + + public static void main(String... args) throws Exception { + scanner = new Scanner(System.in); + createPropertiesFile( + getEnv(), + getClientID(), + getAppScope(), + getPrivateKeyFile(), + getPublicKeyFile(), + getProxyConfig() + ); + scanner.close(); + } + + public static void createPropertiesFile(String env, String clientId, String clientAppScope, String privateKeyFile, String publicKeyFile, Map proxyConfig) { + String uatOption = env.equals("UAT") ? "-uat" : ""; + + Properties properties = new Properties(); + properties.setProperty( + "morgan-stanley-oauth2-token-uri", + String.format("https://login.microsoftonline.com/api%s.morganstanley.com/oauth2/v2.0/token", uatOption) + ); + properties.setProperty("client-app-id", clientId); + properties.setProperty("client-app-scope", clientAppScope); + properties.setProperty("private-key-file", privateKeyFile); + properties.setProperty("public-certificate-file", publicKeyFile); + properties.setProperty( + "ms-url-api-domain", + String.format("https://api%s.morganstanley.com/", uatOption) + ); + + if (!proxyConfig.isEmpty()) { + properties.setProperty("proxy-host", proxyConfig.get("proxy_host")); + properties.setProperty("proxy-port", proxyConfig.get("proxy_port")); + } + + Path configPath = Path.of(String.format("src/main/resources/META-INF/microprofile-config%s.properties", uatOption)); + + try { + Files.createDirectories(configPath.getParent()); // Ensure directory exists + try (FileOutputStream out = new FileOutputStream(configPath.toFile())) { + properties.store(out, "MicroProfile Configuration File"); + } + System.out.println("Config file created: " + configPath.toAbsolutePath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getClientID() { + System.out.println("Please enter your client ID"); + System.out.println("e.g. 12345678-abcd-1234-efgh-1234567890ab"); + + System.out.print("Client ID: "); + return scanner.nextLine().trim(); + } + + public static String getAppScope() { + System.out.println("\nPlease enter your app scope"); + System.out.println("e.g. https://api-uat.morganstanley.com/hello-world/.default"); + + System.out.print("App Scope: "); + return scanner.nextLine().trim(); + } + + public static String getPrivateKeyFile() { + System.out.println("\nPlease enter your private key file (should end in .der)"); + String privateKeyFile = ""; + while (!privateKeyFile.endsWith(".der")) { + System.out.print("Private Key File: "); + privateKeyFile = scanner.nextLine().trim(); + + if (privateKeyFile.endsWith(".pem")) { + System.out.println("You need to have a der encoded file, you can create one by using:"); + System.out.println("openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt\n"); + } + else if (!privateKeyFile.endsWith(".der")) { + System.out.println("Your file should end in .der\n"); + } + } + return privateKeyFile; + } + + public static String getPublicKeyFile() { + System.out.println("\nPlease enter your public key file (should end in .cer)"); + String publicKeyFile = ""; + while (!publicKeyFile.endsWith(".cer")) { + System.out.print("Public Key File: "); + publicKeyFile = scanner.nextLine().trim(); + + if (!publicKeyFile.endsWith(".cer")) { + System.out.println("Your file should end in .cer\n"); + } + } + return publicKeyFile; + } + + public static Map getProxyConfig() { + Map proxyConfig = new HashMap<>(); + + String option = ""; + + while (!option.equals("y") && !option.equals("n")) { + System.out.println("\nWould you like to add a proxy config?"); + System.out.print("\n y -> Yes\n n -> No\n Choice: "); + option = scanner.nextLine().trim().toLowerCase(); + } + + if (option.equals("n")) { + return proxyConfig; + }; + + System.out.print("Please enter the proxy host: "); + String proxyHost = scanner.nextLine().trim(); + proxyConfig.put("proxy_host", proxyHost); + + String proxyPort; + while (true) { + try { + System.out.print("Please enter the proxy port: "); + proxyPort = scanner.nextLine().trim(); + Integer.parseInt(proxyPort); + break; + } catch (NumberFormatException e) { + System.out.println("Error: The proxy port must be an integer.\n"); + } + } + proxyConfig.put("proxy_port", proxyPort); + + return proxyConfig; + } + + public static String getEnv() { + String option = ""; + + while (!option.equals("u") && !option.equals("p")) { + System.out.println("\nWhich environment is this for?"); + System.out.print("\n u -> UAT\n p -> Production\n Choice: "); + option = scanner.nextLine().trim().toLowerCase(); + } + + if (option.equals("u")) { + return "UAT"; + } + return "prod"; + } +} diff --git a/client-samples/java/websockets/README.md b/client-samples/java/websockets/README.md index 39fe501..9e5b9b4 100644 --- a/client-samples/java/websockets/README.md +++ b/client-samples/java/websockets/README.md @@ -19,7 +19,7 @@ The `der` output format is a just an encoding format, to find out more check proxyConfig) { + String uatOption = env.equals("UAT") ? "-uat" : ""; + + Properties properties = new Properties(); + properties.setProperty( + "morgan-stanley-oauth2-token-uri", + String.format("https://login.microsoftonline.com/api%s.morganstanley.com/oauth2/v2.0/token", uatOption) + ); + properties.setProperty("client-app-id", clientId); + properties.setProperty("client-app-scope", clientAppScope); + properties.setProperty("private-key-file", privateKeyFile); + properties.setProperty("public-certificate-file", publicKeyFile); + properties.setProperty( + "ms-url-api-domain", + String.format("https://api%s.morganstanley.com/", uatOption) + ); + + if (!proxyConfig.isEmpty()) { + properties.setProperty("proxy-host", proxyConfig.get("proxy_host")); + properties.setProperty("proxy-port", proxyConfig.get("proxy_port")); + } + + Path configPath = Path.of(String.format("src/main/resources/META-INF/microprofile-config%s.properties", uatOption)); + + try { + Files.createDirectories(configPath.getParent()); // Ensure directory exists + try (FileOutputStream out = new FileOutputStream(configPath.toFile())) { + properties.store(out, "MicroProfile Configuration File"); + } + System.out.println("Config file created: " + configPath.toAbsolutePath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String getClientID() { + System.out.println("Please enter your client ID"); + System.out.println("e.g. 12345678-abcd-1234-efgh-1234567890ab"); + + System.out.print("Client ID: "); + return scanner.nextLine().trim(); + } + + public static String getAppScope() { + System.out.println("\nPlease enter your app scope"); + System.out.println("e.g. https://api-uat.morganstanley.com/hello-world/.default"); + + System.out.print("App Scope: "); + return scanner.nextLine().trim(); + } + + public static String getPrivateKeyFile() { + System.out.println("\nPlease enter your private key file (should end in .der)"); + String privateKeyFile = ""; + while (!privateKeyFile.endsWith(".der")) { + System.out.print("Private Key File: "); + privateKeyFile = scanner.nextLine().trim(); + + if (privateKeyFile.endsWith(".pem")) { + System.out.println("You need to have a der encoded file, you can create one by using:"); + System.out.println("openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt\n"); + } + else if (!privateKeyFile.endsWith(".der")) { + System.out.println("Your file should end in .der\n"); + } + } + return privateKeyFile; + } + + public static String getPublicKeyFile() { + System.out.println("\nPlease enter your public key file (should end in .cer)"); + String publicKeyFile = ""; + while (!publicKeyFile.endsWith(".cer")) { + System.out.print("Public Key File: "); + publicKeyFile = scanner.nextLine().trim(); + + if (!publicKeyFile.endsWith(".cer")) { + System.out.println("Your file should end in .cer\n"); + } + } + return publicKeyFile; + } + + public static Map getProxyConfig() { + Map proxyConfig = new HashMap<>(); + + String option = ""; + + while (!option.equals("y") && !option.equals("n")) { + System.out.println("\nWould you like to add a proxy config?"); + System.out.print("\n y -> Yes\n n -> No\n Choice: "); + option = scanner.nextLine().trim().toLowerCase(); + } + + if (option.equals("n")) { + return proxyConfig; + }; + + System.out.print("Please enter the proxy host: "); + String proxyHost = scanner.nextLine().trim(); + proxyConfig.put("proxy_host", proxyHost); + + String proxyPort; + while (true) { + try { + System.out.print("Please enter the proxy port: "); + proxyPort = scanner.nextLine().trim(); + Integer.parseInt(proxyPort); + break; + } catch (NumberFormatException e) { + System.out.println("Error: The proxy port must be an integer.\n"); + } + } + proxyConfig.put("proxy_port", proxyPort); + + return proxyConfig; + } + + public static String getEnv() { + String option = ""; + + while (!option.equals("u") && !option.equals("p")) { + System.out.println("\nWhich environment is this for?"); + System.out.print("\n u -> UAT\n p -> Production\n Choice: "); + option = scanner.nextLine().trim().toLowerCase(); + } + + if (option.equals("u")) { + return "UAT"; + } + return "prod"; + } +} diff --git a/client-samples/python/rest/README.md b/client-samples/python/rest/README.md index 8ec4358..4391bde 100644 --- a/client-samples/python/rest/README.md +++ b/client-samples/python/rest/README.md @@ -18,6 +18,7 @@ Create a file, config.json, with the following properties: - `client_id`: Your Client Id for the Morgan Stanley API Platform. This is a GUID. - `scopes`: A list of scopes to request a token against, corresponding to the API you are calling. For help with finding the correct scope, please talk to your Morgan Stanley contact. - `thumbprint`: The thumbprint (also known as fingerprint) of your certificate, without colon separators. For example `AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47` + - You can get this by looking at the file via windows file explorer or running the following command `openssl x509 -in public_key.cer -noout -fingerprint -sha1 | sed 's/://g' | awk '{print tolower($0)}' | cut -d= -f2` - `private_key_file`: The path to your private key. This can be either an absolute or relative path. For example: `certs/private_key.pem` - `tenant`: The tenant you are requesting an access token against. - UAT: `api-uat.morganstanley.com` @@ -28,7 +29,7 @@ Create a file, config.json, with the following properties: - `requests_ca_bundle`: The file to use as the CA bundle when verifying HTTPS certificates. If omitted, use the default bundle shipped with the `requests` library. Please see the [SSL validation section](#ssl-validation-issues-and-the-requests-ca-bundle) for more details. - `disable_ssl_verification`: Explicitly disable SSL verification. **Not recommended for security reasons.** -You can use `config-example.json` as a starting point. +You can use `config-example.json` as a starting point, or run `python setup-config.py` to generate it with a cli. > NOTE: You may recieve a Thumbprint in the format 12:AB:FC:12. Please remove the ":" characters, the thumbrpint should look like 12ABFC12 diff --git a/client-samples/python/rest/setup-config.py b/client-samples/python/rest/setup-config.py new file mode 100644 index 0000000..bef84c7 --- /dev/null +++ b/client-samples/python/rest/setup-config.py @@ -0,0 +1,168 @@ +import json +from typing import Dict +import sys +import tty +import termios + + +def move_cursor(pos): + """Move the cursor to a specific position.""" + sys.stdout.write(f"\r\033[{pos+1}G") + sys.stdout.flush() + + +def get_char(): + """Capture a single character from user input without requiring Enter.""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + return sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + +def get_user_input(fixed_part, template): + """Allow user to type over 'x' characters and submit only when Enter is pressed.""" + input_list = list(fixed_part + template) + start_idx = len(fixed_part) + idx = start_idx + + sys.stdout.write(f"\r{''.join(input_list)}") + move_cursor(idx) + + while True: + char = get_char() + + if char in "\r\n": # Enter to submit + break + elif char == "\x7f" and idx > start_idx: # Backspace + while idx > start_idx and template[idx - start_idx] != "x": + idx -= 1 + if idx > start_idx: + input_list[idx] = "x" + idx -= 1 + elif ( + char.isalnum() + and idx < len(input_list) + and template[idx - start_idx] == "x" + ): # Typing + input_list[idx] = char + idx += 1 + while idx < len(input_list) and template[idx - start_idx] != "x": + idx += 1 + + sys.stdout.write(f"\r{''.join(input_list)}") + move_cursor(idx) + + return "".join(input_list) + + +def get_yes_no(question: str) -> bool: + """ + Prompt the user with a yes/no question and return the response as a boolean. + """ + prompt = f"\n{question}\n 1 -> Yes\n 2 -> No\n Choice: " + option = "" + + while option not in {"1", "2"}: + option = input(prompt).strip() + + return option == "1" + + +def get_client_id() -> str: + """ + Prompt the user for the client ID. + """ + print("Please enter your client ID") + print("e.g. 12345678-abcd-1234-efgh-1234567890ab") + + return get_user_input("Client ID: ", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + + +def get_api_scope() -> str: + """ + Prompt the user for the API scope. + """ + print("\n\nPlease enter your app scope") + print("e.g. https://api-uat.morganstanley.com/hello-world/.default") + + return input("App Scope: ") + + +def get_thumbprint() -> str: + """ + Prompt the user for the certificate thumbprint. + """ + print("\nPlease enter your certificate thumbprint") + print("e.g. AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47") + print(len("AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47")) + + return get_user_input("Thumbprint: ", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + + +def get_private_key_file() -> str: + """ + Prompt the user for the private key file path. + """ + private_key_file = "" + while not private_key_file.endswith(".pem"): + private_key_file = input( + "\n\nPlease enter your private key path (must end with .pem): " + ).strip() + if not private_key_file.endswith(".pem"): + print("Error: The file path must end with .pem") + return private_key_file + + +def get_proxies() -> Dict[str, str]: + """ + Prompt the user for proxy settings if applicable. + """ + if not get_yes_no("\nWould you like to add a proxy config? "): + return {} + + proxy_config = {"proxy_host": input("Please enter the proxy host: ").strip()} + + while True: + try: + proxy_port = int(input("Please enter the proxy port: ").strip()) + break + except ValueError: + print("Error: The proxy port must be an integer.\n") + proxy_config["proxy_port"] = proxy_port + return proxy_config + + +def create_python_config(config: Dict): + """ + Write the given configuration dictionary to a JSON file. + """ + try: + with open("./config.json", "w") as config_file: + json.dump(config, config_file, indent=4) + print("\n✅ Config file 'config.json' created successfully!") + except Exception as e: + print(f"\n❌ Error writing config file: {e}") + + +def main(): + """ + Gather user input and generate the configuration file. + """ + config = { + "client_id": get_client_id(), + "scopes": [get_api_scope()], + "thumbprint": get_thumbprint(), + "private_key_file": get_private_key_file(), + "tenant": "api.morganstanley.com", + "url": input("\nPlease enter your API URL: ").strip(), + } + + config.update(get_proxies()) + create_python_config(config) + + +if __name__ == "__main__": + main() diff --git a/client-samples/python/websockets/README.md b/client-samples/python/websockets/README.md index eb44387..d187338 100644 --- a/client-samples/python/websockets/README.md +++ b/client-samples/python/websockets/README.md @@ -18,6 +18,7 @@ Create a file, config.json, with the following properties: - `client_id`: Your Client Id for the Morgan Stanley API Platform. This is a GUID. - `scopes`: A list of scopes to request a token against, corresponding to the API you are calling. For help with finding the correct scope, please talk to your Morgan Stanley contact. - `thumbprint`: The thumbprint (also known as fingerprint) of your certificate, without colon separators. For example `AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47` + - You can get this by looking at the file via windows file explorer or running the following command `openssl x509 -in public_key.cer -noout -fingerprint -sha1 | sed 's/://g' | awk '{print tolower($0)}' | cut -d= -f2` - `private_key_file`: The path to your private key. This can be either an absolute or relative path. For example: `websockets/private_key.pem` - `tenant`: The tenant you are requesting an access token against. - UAT: `api-uat.morganstanley.com` @@ -29,7 +30,7 @@ Create a file, config.json, with the following properties: - `disable_ssl_verification`: Explicitly disable SSL verification. **Not recommended for security reasons.** - `retry_bad_handshake_status`: Whether or not to retry if the downstream API returns a handshake status other than 101 Switching Protocols. This may indicate an outage on the API and you may not always want to retry in this situation. Default value is `true`. -You may use [`config-example.json`](./config-example.json) as a starting point. +You may use [`config-example.json`](./config-example.json) as a starting pointY, or run `python setup-config.py` to generate it with a cli. > NOTE: You may recieve a Thumbprint in the formal 12:AB:FC:12, please remove the ":" so the thumbrpint would be 12ABFC12 diff --git a/client-samples/python/websockets/setup-config.py b/client-samples/python/websockets/setup-config.py new file mode 100644 index 0000000..36cb20b --- /dev/null +++ b/client-samples/python/websockets/setup-config.py @@ -0,0 +1,169 @@ +import json +from typing import Dict +import sys +import tty +import termios + + +def move_cursor(pos): + """Move the cursor to a specific position.""" + sys.stdout.write(f"\r\033[{pos+1}G") + sys.stdout.flush() + + +def get_char(): + """Capture a single character from user input without requiring Enter.""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + return sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + +def get_user_input(fixed_part, template): + """Allow user to type over 'x' characters and submit only when Enter is pressed.""" + input_list = list(fixed_part + template) + start_idx = len(fixed_part) + idx = start_idx + + sys.stdout.write(f"\r{''.join(input_list)}") + move_cursor(idx) + + while True: + char = get_char() + + if char in "\r\n": # Enter to submit + break + elif char == "\x7f" and idx > start_idx: # Backspace + while idx > start_idx and template[idx - start_idx] != "x": + idx -= 1 + if idx > start_idx: + input_list[idx] = "x" + idx -= 1 + elif ( + char.isalnum() + and idx < len(input_list) + and template[idx - start_idx] == "x" + ): # Typing + input_list[idx] = char + idx += 1 + while idx < len(input_list) and template[idx - start_idx] != "x": + idx += 1 + + sys.stdout.write(f"\r{''.join(input_list)}") + move_cursor(idx) + + return "".join(input_list) + + +def get_yes_no(question: str) -> bool: + """ + Prompt the user with a yes/no question and return the response as a boolean. + """ + prompt = f"\n{question}\n 1 -> Yes\n 2 -> No\n Choice: " + option = "" + + while option not in {"1", "2"}: + option = input(prompt).strip() + + return option == "1" + + +def get_client_id() -> str: + """ + Prompt the user for the client ID. + """ + print("Please enter your client ID") + print("e.g. 12345678-abcd-1234-efgh-1234567890ab") + + return get_user_input("Client ID: ", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + + +def get_api_scope() -> str: + """ + Prompt the user for the API scope. + """ + print("\n\nPlease enter your app scope") + print("e.g. https://api-uat.morganstanley.com/hello-world/.default") + + return input("App Scope: ") + + +def get_thumbprint() -> str: + """ + Prompt the user for the certificate thumbprint. + """ + print("\nPlease enter your certificate thumbprint") + print("e.g. AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47") + print(len("AB48C0D31F95EBF8425AECF3E7E6FA92B34C8D47")) + + return get_user_input("Thumbprint: ", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + + +def get_private_key_file() -> str: + """ + Prompt the user for the private key file path. + """ + private_key_file = "" + while not private_key_file.endswith(".pem"): + private_key_file = input( + "\n\nPlease enter your private key path (must end with .pem): " + ).strip() + if not private_key_file.endswith(".pem"): + print("Error: The file path must end with .pem") + return private_key_file + + +def get_proxies() -> Dict[str, str]: + """ + Prompt the user for proxy settings if applicable. + """ + if not get_yes_no("\nWould you like to add a proxy config? "): + return {} + + proxy_config = {"proxy_host": input("Please enter the proxy host: ").strip()} + + while True: + try: + proxy_port = int(input("Please enter the proxy port: ").strip()) + break + except ValueError: + print("Error: The proxy port must be an integer.\n") + proxy_config["proxy_port"] = proxy_port + return proxy_config + + +def create_python_config(config: Dict): + """ + Write the given configuration dictionary to a JSON file. + """ + try: + with open("./config.json", "w") as config_file: + json.dump(config, config_file, indent=4) + print("\n✅ Config file 'config.json' created successfully!") + except Exception as e: + print(f"\n❌ Error writing config file: {e}") + + +def main(): + """ + Gather user input and generate the configuration file. + """ + config = { + "client_id": get_client_id(), + "scopes": [get_api_scope()], + "thumbprint": get_thumbprint(), + "private_key_file": get_private_key_file(), + "tenant": "api.morganstanley.com", + "url": input("\nPlease enter your API URL: ").strip(), + "retry_bad_handshake_status": True, + } + + config.update(get_proxies()) + create_python_config(config) + + +if __name__ == "__main__": + main()