diff --git a/src/templates/csharp/Config.cs b/src/templates/csharp/Config.cs index 2c4585b..75a822c 100644 --- a/src/templates/csharp/Config.cs +++ b/src/templates/csharp/Config.cs @@ -22,6 +22,10 @@ public enum DeviceSourceEnums { Kobiton, Other } public const int SendKeysDelayInMs = 1500; public const int IdleDelayInMs = 3000; public const string KobitonApiUrl = "{{KobitonApiUrl}}"; + // Run with KOBITON_TRUST_ALL_CERTS=true to skip TLS cert validation — needed + // for on-prem standalone deployments served over a self-signed certificate. + public static readonly bool TrustAllCerts = new[] { "1", "true", "yes" } + .Contains((Environment.GetEnvironmentVariable("KOBITON_TRUST_ALL_CERTS") ?? "").Trim().ToLower()); {{kobitonCredential}} public static string GetAppiumServerUrlWithAuth() @@ -38,6 +42,23 @@ public static string GetBasicAuthString() return "Basic " + authEncString; } + // Returns an HttpClient that trusts any TLS certificate when TrustAllCerts + // is enabled; otherwise a default client that validates certificates + // normally. Used by the proxy and all Kobiton REST clients. + public static HttpClient CreateHttpClient() + { + if (!TrustAllCerts) + { + return new HttpClient(); + } + + var handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true + }; + return new HttpClient(handler); + } + {{desiredCaps}} } } diff --git a/src/templates/csharp/OtpService.cs b/src/templates/csharp/OtpService.cs index b8a6d07..f17723f 100644 --- a/src/templates/csharp/OtpService.cs +++ b/src/templates/csharp/OtpService.cs @@ -12,7 +12,7 @@ namespace AppiumTest public class OtpService { - private static readonly HttpClient httpClient = new HttpClient(); + private static readonly HttpClient httpClient = Config.CreateHttpClient(); public const int FindPhoneNumberMaxAttempts = 12; public const int FindPhoneNumberInternalInMs = 10000; diff --git a/src/templates/csharp/ProxyServer.cs b/src/templates/csharp/ProxyServer.cs index 572fd3e..614d105 100644 --- a/src/templates/csharp/ProxyServer.cs +++ b/src/templates/csharp/ProxyServer.cs @@ -59,7 +59,7 @@ private void HandleRequest(HttpListenerContext context, string appiumServerUrl) var url = new Uri(appiumServerUrl + urlString); - using (var client = new HttpClient()) + using (var client = Config.CreateHttpClient()) { var httpRequest = new HttpRequestMessage { diff --git a/src/templates/csharp/README.md b/src/templates/csharp/README.md index a352a53..0e10bb5 100644 --- a/src/templates/csharp/README.md +++ b/src/templates/csharp/README.md @@ -47,6 +47,9 @@ This project is generated by Scriptless Automation based on an exploratory sessi ## Commands - Execute tests: `dotnet test --logger "console;verbosity=normal"` + - On a Kobiton Standalone server using a self-signed SSL certificate, skip TLS cert validation with `KOBITON_TRUST_ALL_CERTS=true`: + - macOS/Linux: `KOBITON_TRUST_ALL_CERTS=true dotnet test --logger "console;verbosity=normal"` + - Windows (PowerShell): `$env:KOBITON_TRUST_ALL_CERTS="true"; dotnet test --logger "console;verbosity=normal"` ## View the test results diff --git a/src/templates/csharp/TestBase.cs b/src/templates/csharp/TestBase.cs index fdc31e8..31d0c6e 100644 --- a/src/templates/csharp/TestBase.cs +++ b/src/templates/csharp/TestBase.cs @@ -34,7 +34,7 @@ public enum PressTypes public Point? screenSize; public double retinaScale; public string deviceName, platformVersion; - public HttpClient httpClient = new HttpClient(); + public HttpClient httpClient = Config.CreateHttpClient(); private string? currentContext; private string currentWindow; @@ -1379,7 +1379,7 @@ public void SetCurrentCommandId(long currentCommandId) public string GetAppUrl(int appVersionId) { string appUrl = string.Empty; - using (HttpClient client = new HttpClient()) + using (HttpClient client = Config.CreateHttpClient()) { client.DefaultRequestHeaders.Add("Content-Type", "application/json"); client.DefaultRequestHeaders.Add("Authorization", Config.GetBasicAuthString()); @@ -1495,7 +1495,7 @@ public async Task GetAvailableDevice(AppiumOptions capabilities) }; deviceListUriBuilder.Query = new FormUrlEncodedContent(query).ReadAsStringAsync().Result; - using (var httpClient = new HttpClient()) + using (var httpClient = Config.CreateHttpClient()) { httpClient.DefaultRequestHeaders.Add(HttpRequestHeader.Authorization.ToString(), Config.GetBasicAuthString()); diff --git a/src/templates/java/Config.java b/src/templates/java/Config.java index 414ac88..dcce6a5 100644 --- a/src/templates/java/Config.java +++ b/src/templates/java/Config.java @@ -1,10 +1,17 @@ package com.kobiton.scriptlessautomation; +import okhttp3.OkHttpClient; import org.apache.commons.codec.binary.Base64; import org.openqa.selenium.remote.DesiredCapabilities; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import java.net.MalformedURLException; import java.net.URL; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Arrays; public class Config { enum DEVICE_SOURCE_ENUMS {KOBITON, OTHER} @@ -19,6 +26,10 @@ enum DEVICE_SOURCE_ENUMS {KOBITON, OTHER} public static final int SEND_KEYS_DELAY_IN_MS = 1500; public static final int IDLE_DELAY_IN_MS = 3000; public static final String KOBITON_API_URL = "{{kobiton_api_url}}"; + // Run with KOBITON_TRUST_ALL_CERTS=true to skip TLS cert validation — needed + // for on-prem standalone deployments served over a self-signed certificate. + public static final boolean TRUST_ALL_CERTS = Arrays.asList("1", "true", "yes") + .contains(String.valueOf(System.getenv("KOBITON_TRUST_ALL_CERTS")).trim().toLowerCase()); {{kobitonCredential}} public static String getAppiumServerUrlWithAuth() throws MalformedURLException { @@ -34,5 +45,33 @@ public static String getBasicAuthString() { return "Basic " + authEncString; } + // Returns an OkHttpClient builder that trusts any TLS certificate when + // TRUST_ALL_CERTS is enabled; otherwise a default builder that validates + // certificates normally. Used by the proxy and all Kobiton REST clients. + public static OkHttpClient.Builder createHttpClientBuilder() { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + if (!TRUST_ALL_CERTS) { + return builder; + } + + try { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) {} + public void checkServerTrusted(X509Certificate[] chain, String authType) {} + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + } + }; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0]); + builder.hostnameVerifier((hostname, session) -> true); + } catch (Exception e) { + throw new RuntimeException("Failed to build trust-all SSL context", e); + } + + return builder; + } + {{desiredCaps}} } diff --git a/src/templates/java/OtpService.java b/src/templates/java/OtpService.java index 66aaf8d..0bfd22f 100644 --- a/src/templates/java/OtpService.java +++ b/src/templates/java/OtpService.java @@ -20,7 +20,7 @@ public class OtpService { public static final int FIND_OTP_CODE_MAX_ATTEMPTS = 12; public static final int FIND_OTP_CODE_INTERVAL_IN_MS = 10000; - private final OkHttpClient httpClient = new OkHttpClient(); + private final OkHttpClient httpClient = Config.createHttpClientBuilder().build(); public String countryCode = "1"; public String rawPhoneNumber; diff --git a/src/templates/java/ProxyServer.java b/src/templates/java/ProxyServer.java index c1b8ecf..11b842d 100644 --- a/src/templates/java/ProxyServer.java +++ b/src/templates/java/ProxyServer.java @@ -23,7 +23,7 @@ public class ProxyServer extends NanoHTTPD { private final int socketTimeoutInSecond = 15 * 60; private boolean forceW3C = false; - private final OkHttpClient httpClient = new OkHttpClient.Builder() + private final OkHttpClient httpClient = Config.createHttpClientBuilder() .connectTimeout(socketTimeoutInSecond, TimeUnit.SECONDS) .writeTimeout(socketTimeoutInSecond, TimeUnit.SECONDS) .readTimeout(socketTimeoutInSecond, TimeUnit.SECONDS) diff --git a/src/templates/java/README.md b/src/templates/java/README.md index bc73692..9a4a769 100644 --- a/src/templates/java/README.md +++ b/src/templates/java/README.md @@ -47,7 +47,10 @@ This project is generated by Scriptless Automation based on an exploratory sessi ## Commands - Build project: `mvn clean install -DskipTests` -- Execute tests: `mvn test` or `mvn test -Djavax.net.ssl.trustStoreType=KeychainStore` on Kobiton Standalone environment +- Execute tests: `mvn test` + - On a Kobiton Standalone server using a self-signed SSL certificate, skip TLS cert validation with `KOBITON_TRUST_ALL_CERTS=true`: + - macOS/Linux: `KOBITON_TRUST_ALL_CERTS=true mvn test` + - Windows (PowerShell): `$env:KOBITON_TRUST_ALL_CERTS="true"; mvn test` ## View the test results diff --git a/src/templates/java/TestBase.java b/src/templates/java/TestBase.java index 5f6a71b..42c79bb 100644 --- a/src/templates/java/TestBase.java +++ b/src/templates/java/TestBase.java @@ -59,7 +59,7 @@ public class TestBase { enum PRESS_TYPES {HOME, BACK, POWER, APP_SWITCH, ENTER, DELETE} public Gson gson = new GsonBuilder().disableHtmlEscaping().create(); - public final OkHttpClient httpClient = new OkHttpClient(); + public final OkHttpClient httpClient = Config.createHttpClientBuilder().build(); private String currentContext; private String currentWindow; @@ -1182,7 +1182,7 @@ public Device findOnlineDevice(DesiredCapabilities capabilities) throws Exceptio public String getAppUrl(int appVersionId) throws Exception { String appUrl = ""; - OkHttpClient client = new OkHttpClient(); + OkHttpClient client = Config.createHttpClientBuilder().build(); Request request = new Request.Builder() .url(String.format("%s/v1/app/versions/%s/downloadUrl", Config.KOBITON_API_URL, appVersionId)) .addHeader(HttpHeaders.CONTENT_TYPE, "application/json") diff --git a/src/templates/nodejs/README.md b/src/templates/nodejs/README.md index 35ea9b4..dd3bb37 100644 --- a/src/templates/nodejs/README.md +++ b/src/templates/nodejs/README.md @@ -46,7 +46,10 @@ This project is generated by Scriptless Automation based on an exploratory sessi ## Commands - Install dependent modules: `npm install` -- Execute tests: `npm test` or `NODE_TLS_REJECT_UNAUTHORIZED=0 npm test` on Kobiton Standalone environment +- Execute tests: `npm test` + - On a Kobiton Standalone server using a self-signed SSL certificate, skip TLS cert validation with `KOBITON_TRUST_ALL_CERTS=true`: + - macOS/Linux: `KOBITON_TRUST_ALL_CERTS=true npm test` + - Windows (PowerShell): `$env:KOBITON_TRUST_ALL_CERTS="true"; npm test` ## View the test results diff --git a/src/templates/nodejs/src/test/config.js b/src/templates/nodejs/src/test/config.js index e1a6700..6c96fa3 100644 --- a/src/templates/nodejs/src/test/config.js +++ b/src/templates/nodejs/src/test/config.js @@ -12,6 +12,9 @@ export const Config = { SEND_KEYS_DELAY_IN_MS: 1500, IDLE_DELAY_IN_MS: 3000, KOBITON_API_URL: '{{kobitonApiUrl}}', + // Run with KOBITON_TRUST_ALL_CERTS=true to skip TLS cert validation — needed + // for on-prem standalone deployments served over a self-signed certificate. + TRUST_ALL_CERTS: ['1', 'true', 'yes'].includes((process.env.KOBITON_TRUST_ALL_CERTS || '').trim().toLowerCase()), getAppiumServerUrlWithAuth() { const url = new URL(this.APPIUM_SERVER_URL) diff --git a/src/templates/nodejs/src/test/helper/base.js b/src/templates/nodejs/src/test/helper/base.js index 23663c1..bb0ba48 100644 --- a/src/templates/nodejs/src/test/helper/base.js +++ b/src/templates/nodejs/src/test/helper/base.js @@ -1,6 +1,7 @@ import BPromise from 'bluebird' import canvas from 'canvas' import axios from 'axios' +import https from 'https' import path from 'path' import get from 'lodash/get' import flatten from 'lodash/flatten' @@ -15,6 +16,12 @@ import Point from './point' import {Config} from '../config' import {DEVICE_SOURCES, PRESS_TYPES} from './constants' +// Skip TLS cert validation on the direct Kobiton REST calls when +// KOBITON_TRUST_ALL_CERTS is set — needed for standalone self-signed certs. +if (Config.TRUST_ALL_CERTS) { + axios.defaults.httpsAgent = new https.Agent({rejectUnauthorized: false}) +} + const NATIVE_CONTEXT = 'NATIVE_APP' const PLATFORM_NAMES = { IOS: 'IOS', diff --git a/src/templates/nodejs/src/test/helper/proxy.js b/src/templates/nodejs/src/test/helper/proxy.js index aa0fb12..2a8a319 100644 --- a/src/templates/nodejs/src/test/helper/proxy.js +++ b/src/templates/nodejs/src/test/helper/proxy.js @@ -22,7 +22,7 @@ export default class Proxy { req.url = url.toString().replace(this.getServerUrl(), '') this._proxy.web(req, res, { target: Config.getAppiumServerUrlWithAuth().replace('/wd/hub', ''), - secure: false, + secure: !Config.TRUST_ALL_CERTS, changeOrigin: true }) }) diff --git a/src/templates/python/README.md b/src/templates/python/README.md index 909f085..de12f66 100644 --- a/src/templates/python/README.md +++ b/src/templates/python/README.md @@ -56,6 +56,12 @@ pytest test_suite.py -v -s Run `deactivate` when finished. On subsequent runs, only `source .venv/bin/activate` and `pytest test_suite.py -v -s` are needed. +On a Kobiton Standalone server using a self-signed SSL certificate, skip TLS cert +validation with `KOBITON_TRUST_ALL_CERTS=true`: + +- macOS/Linux: `KOBITON_TRUST_ALL_CERTS=true pytest test_suite.py -v -s` +- Windows (PowerShell): `$env:KOBITON_TRUST_ALL_CERTS="true"; pytest test_suite.py -v -s` + ## View the test results ### Run on Kobiton platform diff --git a/src/templates/python/config.py b/src/templates/python/config.py index bb0dfef..d3dcf1b 100644 --- a/src/templates/python/config.py +++ b/src/templates/python/config.py @@ -1,5 +1,10 @@ from urllib.parse import urlparse import base64 +import os + +import requests +from urllib3.exceptions import InsecureRequestWarning + from constants import DeviceSource @@ -14,6 +19,9 @@ class Config: SEND_KEYS_DELAY_IN_MS = 1500 IDLE_DELAY_IN_MS = 3000 KOBITON_API_URL = '{{kobitonApiUrl}}' + # Run with KOBITON_TRUST_ALL_CERTS=true to skip TLS cert validation — needed + # for on-prem standalone deployments served over a self-signed certificate. + TRUST_ALL_CERTS = os.getenv('KOBITON_TRUST_ALL_CERTS', '').strip().lower() in ('1', 'true', 'yes') {{kobitonCredential}} #{{desiredCaps}} @@ -29,3 +37,9 @@ def get_basic_auth_string(cls): credentials = f"{cls.API_USERNAME}:{cls.API_KEY}" encoded = base64.b64encode(credentials.encode()).decode() return f"Basic {encoded}" + + +# Suppress the per-request InsecureRequestWarning emitted when TRUST_ALL_CERTS +# disables verification across the proxy and the Kobiton REST calls. +if Config.TRUST_ALL_CERTS: + requests.packages.urllib3.disable_warnings(InsecureRequestWarning) diff --git a/src/templates/python/otp_service.py b/src/templates/python/otp_service.py index 39a371d..5f236a7 100644 --- a/src/templates/python/otp_service.py +++ b/src/templates/python/otp_service.py @@ -52,6 +52,7 @@ def fetch(): f"{Config.KOBITON_API_URL}/v1/otp/phone-numbers/available", params={'countryCode': country_code}, headers={'Authorization': Config.get_basic_auth_string()}, + verify=not Config.TRUST_ALL_CERTS, ) if response.status_code in (401, 403): raise _AbortRetry(Exception(response.text or f"HTTP {response.status_code}")) @@ -87,6 +88,7 @@ def fetch(): response = requests.get( f"{Config.KOBITON_API_URL}/v1/otp/email-address/available", headers={'Authorization': Config.get_basic_auth_string()}, + verify=not Config.TRUST_ALL_CERTS, ) if response.status_code in (401, 403): raise _AbortRetry(Exception(response.text or f"HTTP {response.status_code}")) @@ -121,6 +123,7 @@ def fetch(): response = requests.get( url, params=params, headers={'Authorization': Config.get_basic_auth_string()}, + verify=not Config.TRUST_ALL_CERTS, ) if response.status_code in (401, 403): raise _AbortRetry(Exception(response.text or f"HTTP {response.status_code}")) @@ -160,6 +163,7 @@ def cleanup(self): requests.post( url, params=params, headers={'Authorization': Config.get_basic_auth_string()}, + verify=not Config.TRUST_ALL_CERTS, ) self.is_cleanup = True except Exception as e: diff --git a/src/templates/python/proxy_server.py b/src/templates/python/proxy_server.py index 9a27bf9..201a5da 100644 --- a/src/templates/python/proxy_server.py +++ b/src/templates/python/proxy_server.py @@ -13,15 +13,6 @@ SOCKET_TIMEOUT_SECONDS = 15 * 60 -# Set False to enforce upstream TLS cert validation. Default True so on-prem -# standalone deployments with self-signed certs work out of the box. -TRUST_ALL_CERTS = True - -if TRUST_ALL_CERTS: - # Avoid an InsecureRequestWarning per forwarded request when verify=False. - from urllib3.exceptions import InsecureRequestWarning - requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - # JSON Wire Protocol status code -> W3C error string. _ERROR_CODES = { 6: "invalid session id", @@ -164,7 +155,7 @@ def serve(self, request_uri, method, request_body): method, url, headers=headers, data=request_body, timeout=SOCKET_TIMEOUT_SECONDS, - verify=not TRUST_ALL_CERTS, + verify=not Config.TRUST_ALL_CERTS, ) status_code = response.status_code content_type = response.headers.get('Content-Type', 'application/json') diff --git a/src/templates/python/test_base.py b/src/templates/python/test_base.py index 9728c78..6df25b6 100644 --- a/src/templates/python/test_base.py +++ b/src/templates/python/test_base.py @@ -99,7 +99,7 @@ def get_app_url(self, app_version_id): 'Content-Type': 'application/json', } url = f"{Config.KOBITON_API_URL}/v1/app/versions/{app_version_id}/downloadUrl" - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, verify=not Config.TRUST_ALL_CERTS) response.raise_for_status() return response.json()['url'] @@ -704,6 +704,7 @@ def get_available_device(self, capabilities): f"{Config.KOBITON_API_URL}/v1/devices", params={k: v for k, v in params.items() if v is not None}, headers={'Authorization': Config.get_basic_auth_string()}, + verify=not Config.TRUST_ALL_CERTS, ) if response.status_code != 200: raise Exception(response.text)