From c7b598d22dc165f53471999ed80dfa7de5b8986f Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 12:41:38 -0600 Subject: [PATCH 01/22] Add .editorconfig and editorconfig-maven-plugin Add .editorconfig enforcing UTF-8, LF line endings, 4-space indentation, trim trailing whitespace, and final newline. Add editorconfig-maven-plugin 0.2.0 to verify phase to enforce these rules during builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .editorconfig | 19 +++++++++++++++++++ pom.xml | 14 ++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..33fe931 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig: https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,yml,yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.xml] +indent_size = 4 diff --git a/pom.xml b/pom.xml index 523cf99..382731a 100644 --- a/pom.xml +++ b/pom.xml @@ -144,6 +144,20 @@ false + + org.ec4j.maven + editorconfig-maven-plugin + 0.2.0 + + + check + verify + + check + + + + From ea32569b581848e2d7b454aad074373dfeca4c1a Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 12:44:40 -0600 Subject: [PATCH 02/22] Normalize line endings and trim trailing whitespace Add .gitattributes to enforce LF line endings in the repository. Normalize all existing files: CRLF to LF, trim trailing whitespace, ensure final newline. This is a formatting-only change with no functional modifications. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitattributes | 9 + .github/copilot-instructions.md | 103 +++++ .gitignore | 2 +- azure-pipelines.yml | 2 +- settings/settings.xml | 6 +- .../safeguardjava/CertificateUtilities.java | 14 +- .../safeguardjava/IA2ARetrievableAccount.java | 2 +- .../safeguardjava/ISafeguardA2AContext.java | 38 +- .../safeguardjava/ISafeguardConnection.java | 50 +-- .../ISafeguardSessionsConnection.java | 12 +- .../safeguardjava/ISpsStreamingRequest.java | 16 +- .../safeguardjava/IStreamingRequest.java | 18 +- .../PersistentSafeguardConnection.java | 8 +- .../safeguard/safeguardjava/Safeguard.java | 416 +++++++++--------- .../safeguardjava/SafeguardA2AContext.java | 126 +++--- .../safeguardjava/SafeguardConnection.java | 68 +-- .../SafeguardForPrivilegedSessions.java | 10 +- .../SafeguardManagementServiceConnection.java | 8 +- .../safeguardjava/SpsStreamingRequest.java | 18 +- .../safeguardjava/StreamResponse.java | 8 +- .../safeguardjava/StreamingRequest.java | 14 +- .../safeguard/safeguardjava/Utils.java | 8 +- .../AccessTokenAuthenticator.java | 10 +- .../AnonymousAuthenticator.java | 14 +- .../authentication/AuthenticatorBase.java | 32 +- .../CertificateAuthenticator.java | 62 +-- .../ManagementServiceAuthenticator.java | 18 +- .../authentication/PasswordAuthenticator.java | 22 +- .../safeguardjava/data/A2ARegistration.java | 2 +- .../data/A2ARetrievableAccount.java | 6 +- .../data/A2ARetrievableAccountInternal.java | 8 +- .../safeguardjava/data/AccessTokenBody.java | 2 +- .../safeguardjava/data/ApiKeySecret.java | 2 +- .../safeguardjava/data/ApiKeySecretBase.java | 2 +- .../data/ApiKeySecretInternal.java | 2 +- .../data/BrokeredAccessRequest.java | 6 +- .../data/BrokeredAccessRequestType.java | 8 +- .../safeguardjava/data/FullResponse.java | 4 +- .../safeguardjava/data/JoinRequest.java | 6 +- .../safeguardjava/data/JsonBody.java | 6 +- .../safeguardjava/data/KeyFormat.java | 2 +- .../safeguardjava/data/OauthBody.java | 10 +- .../data/SafeguardEventListenerState.java | 2 +- .../safeguard/safeguardjava/data/Service.java | 10 +- .../safeguard/safeguardjava/data/SshKey.java | 2 +- .../safeguardjava/data/TransferProgress.java | 2 +- .../event/EventHandlerRegistry.java | 20 +- .../event/EventHandlerRunnable.java | 4 +- .../event/EventHandlerThread.java | 4 +- .../event/ISafeguardEventHandler.java | 4 +- .../event/ISafeguardEventListener.java | 18 +- .../ISafeguardEventListenerStateCallback.java | 4 +- .../PersistentSafeguardA2AEventListener.java | 8 +- .../PersistentSafeguardEventListener.java | 4 +- .../PersistentSafeguardEventListenerBase.java | 10 +- .../event/SafeguardEventListener.java | 28 +- .../exceptions/ArgumentException.java | 2 +- .../exceptions/SafeguardForJavaException.java | 2 +- .../restclient/ByteArrayEntity.java | 4 +- .../restclient/OutputStreamProgress.java | 6 +- .../safeguardjava/restclient/RestClient.java | 74 ++-- tests/safeguardjavaclient/pom.xml | 2 +- .../safeguardclient/CertificateValidator.java | 4 +- .../safeguardclient/ProgressNotification.java | 2 +- .../safeguardclient/SafeguardJavaClient.java | 32 +- .../safeguardclient/SafeguardTests.java | 290 ++++++------ .../data/SafeguardAppliance.java | 2 +- .../data/SafeguardApplianceStatus.java | 2 +- .../data/SafeguardSslCertificate.java | 2 +- .../data/SessionRecordings.java | 8 +- 70 files changed, 922 insertions(+), 810 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/copilot-instructions.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ab63eb8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Set default behavior to automatically normalize line endings +* text=auto eol=lf + +# Binary files +*.jar binary +*.pfx binary +*.jks binary +*.png binary +*.ico binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ca2b910 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,103 @@ +# Copilot Instructions for SafeguardJava + +## Build Commands + +```bash +# Build (requires JDK 8+ and Maven 3.0.5+) +mvn package + +# Build with a specific version +mvn package -Drevision=7.5.0 + +# Build for release (includes source jars, javadoc, GPG signing) +mvn deploy -P release --settings settings/settings.xml + +# Clean build +mvn clean package +``` + +There are no unit tests in the main project. The `tests/safeguardjavaclient/` directory contains an interactive CLI test harness (not automated tests), which requires a live Safeguard appliance to run. + +## Architecture + +SafeguardJava is a Java SDK for the One Identity Safeguard for Privileged Passwords REST API. It targets Java 8 source/target compatibility and is published to Maven Central as `com.oneidentity.safeguard:safeguardjava`. + +### Entry Points + +All SDK usage starts through static factory methods: + +- **`Safeguard.connect(...)`** — Creates `ISafeguardConnection` instances. Multiple overloads support password, certificate (keystore/file/thumbprint/byte array), and access token authentication. +- **`Safeguard.A2A.getContext(...)`** — Creates `ISafeguardA2AContext` for application-to-application credential retrieval. Only supports certificate authentication. +- **`Safeguard.A2A.Events.getPersistentA2AEventListener(...)`** — Creates auto-reconnecting A2A event listeners. +- **`Safeguard.Persist(connection)`** — Wraps any connection in a `PersistentSafeguardConnection` that auto-refreshes tokens. +- **`SafeguardForPrivilegedSessions.Connect(...)`** — Creates `ISafeguardSessionsConnection` for Safeguard for Privileged Sessions (SPS). + +### Package Structure (`com.oneidentity.safeguard.safeguardjava`) + +- **Root package** — Public API: `Safeguard`, `ISafeguardConnection`/`SafeguardConnection`, `ISafeguardA2AContext`/`SafeguardA2AContext`, streaming classes, and SPS integration. +- **`authentication/`** — `IAuthenticationMechanism` interface with implementations: `PasswordAuthenticator`, `CertificateAuthenticator`, `AccessTokenAuthenticator`, `AnonymousAuthenticator`, `ManagementServiceAuthenticator`. All extend `AuthenticatorBase`. +- **`event/`** — SignalR-based event system: `ISafeguardEventListener`/`SafeguardEventListener`, persistent (auto-reconnecting) variants, `EventHandlerRegistry` for thread-safe handler management. +- **`restclient/`** — `RestClient` wraps Apache HttpClient 4.5 for all HTTP operations. OkHttp 4.11 is present as a transitive dependency of the Microsoft SignalR client. +- **`data/`** — DTOs and enums (`Service`, `Method`, `KeyFormat`, `SafeguardEventListenerState`, `BrokeredAccessRequestType`). +- **`exceptions/`** — `SafeguardForJavaException` (general), `ArgumentException` (validation), `ObjectDisposedException` (resource lifecycle), `SafeguardEventListenerDisconnectedException`. + +### Safeguard API Services + +The SDK targets five backend services, represented by the `Service` enum: + +| Service | Endpoint Pattern | Auth Required | +|---|---|---| +| `Core` | `/service/core/v{version}` | Yes | +| `Appliance` | `/service/appliance/v{version}` | Yes | +| `Notification` | `/service/notification/v{version}` | No | +| `A2A` | `/service/a2a/v{version}` | Certificate | +| `Management` | `/service/management/v{version}` | Yes | + +The default API version is **v4** (since SDK 7.0). Pass `apiVersion` parameter to use v3. + +## Key Conventions + +### Interface-First Design + +Every public type has a corresponding `I`-prefixed interface (`ISafeguardConnection`, `ISafeguardA2AContext`, `ISafeguardEventListener`, `IAuthenticationMechanism`). Code against interfaces, not implementations. + +### Dispose Pattern + +Connections, A2A contexts, and event listeners implement a `dispose()` method that must be called to release resources. Every public method on these classes guards against use-after-dispose: + +```java +if (disposed) { + throw new ObjectDisposedException("ClassName"); +} +``` + +### Authentication Flow + +All authenticators obtain tokens via the embedded Safeguard RSTS (Resource Security Token Service) at `https://{host}/RSTS/oauth2/token`. Password auth uses the `password` grant type; certificate auth uses `client_credentials`. The `AccessTokenAuthenticator` accepts a pre-obtained token but cannot refresh it. + +### SSL/TLS Handling + +Three modes: `ignoreSsl=true` (uses `NoopHostnameVerifier`), custom `HostnameVerifier` callback, or default Java validation. Certificate contexts (`CertificateContext`) support JKS keystores, PFX files, byte arrays, and Windows certificate store (by thumbprint). + +### Error Handling + +- Parameter validation throws `ArgumentException` +- HTTP failures throw `SafeguardForJavaException` with status code and response body +- Null HTTP responses throw `SafeguardForJavaException("Unable to connect to ...")` +- Disposed object access throws `ObjectDisposedException` + +### Logging + +Uses `java.util.logging` (JUL). The SLF4J dependency provides the facade for the SignalR library. Debug-level HTTP wire logging can be enabled via Apache Commons Logging system properties (see `tests/safeguardjavaclient` for examples). + +### Sensitive Data as `char[]` + +Passwords, access tokens, and API keys are stored as `char[]` rather than `String` to allow explicit clearing from memory. + +### Event System Threading + +Each event type gets its own handler thread. Handlers for the same event execute sequentially; handlers for different events execute concurrently. The `EventHandlerRegistry` manages thread-safe concurrent dispatch. + +### CI/CD + +The project uses Azure Pipelines (`azure-pipelines.yml`). Release branches (`master`, `release-*`) deploy to Sonatype/Maven Central with GPG signing. Non-release branches only build and package. diff --git a/.gitignore b/.gitignore index 676bcd4..c1be709 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ hs_err_pid* /target/ /jenkins-plugin/safeguard4jenkins/target/ /safeguard4jenkins/target/ -/tests/safeguardjavaclient/target/ \ No newline at end of file +/tests/safeguardjavaclient/target/ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d394495..7e5f175 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -164,4 +164,4 @@ steps: $(Build.ArtifactStagingDirectory)/*.jar $(Build.ArtifactStagingDirectory)/*.asc displayName: 'Creating and publishing a release to Github' - condition: and(succeeded(), eq(variables.isReleaseBranch, true)) \ No newline at end of file + condition: and(succeeded(), eq(variables.isReleaseBranch, true)) diff --git a/settings/settings.xml b/settings/settings.xml index 2d47769..e911447 100644 --- a/settings/settings.xml +++ b/settings/settings.xml @@ -11,7 +11,7 @@ ${signingkeystorepassword} - + ossrh @@ -24,5 +24,5 @@ - - \ No newline at end of file + + diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java index 4668088..9c53a2d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java @@ -19,13 +19,13 @@ private CertificateUtilities() { } public static String WINDOWSKEYSTORE = "Windows-MY"; - + public static String getClientCertificateAliasFromStore(String thumbprint) throws SafeguardForJavaException { - + try { KeyStore keyStore = KeyStore.getInstance(WINDOWSKEYSTORE); keyStore.load(null, null); - + Enumeration enumeration = keyStore.aliases(); while (enumeration.hasMoreElements()) { String alias = enumeration.nextElement(); @@ -40,14 +40,14 @@ public static String getClientCertificateAliasFromStore(String thumbprint) throw { throw new SafeguardForJavaException(String.format("Failure to get certificate from thumbprint=%s : %s", thumbprint, ex.getMessage())); } - + throw new SafeguardForJavaException(String.format("Unable to find certificate matching thumbprint=%s in the User store", thumbprint)); } - + public static boolean isWindowsKeyStore(String path) { return path != null ? path.equalsIgnoreCase(WINDOWSKEYSTORE) : false; } - + private static String getThumbprint(X509Certificate cert) throws NoSuchAlgorithmException, CertificateEncodingException { MessageDigest md = MessageDigest.getInstance("SHA-1"); @@ -56,5 +56,5 @@ private static String getThumbprint(X509Certificate cert) byte[] digest = md.digest(); String digestHex = DatatypeConverter.printHexBinary(digest); return digestHex.toLowerCase(); - } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/IA2ARetrievableAccount.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/IA2ARetrievableAccount.java index 62c610a..d9c304b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/IA2ARetrievableAccount.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/IA2ARetrievableAccount.java @@ -42,7 +42,7 @@ public interface IA2ARetrievableAccount { String getAssetName(); /** - * Get the asset network address + * Get the asset network address * @return String */ String getAssetNetworkAddress(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardA2AContext.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardA2AContext.java index 1efc26b..a20e578 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardA2AContext.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardA2AContext.java @@ -17,16 +17,16 @@ public interface ISafeguardA2AContext /** * Retrieves the list of retrievable accounts for this A2A context. Listing the retrievable accounts is a * new feature for Safeguard v2.8+, and it needs to be enabled in the A2A configuration. - + * @return A list of retrievable accounts. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. - */ + */ List getRetrievableAccounts() throws ObjectDisposedException, SafeguardForJavaException; - + /** * Retrieves a password using Safeguard A2A. - + * @param apiKey API key corresponding to the configured account. * @return The password. * @throws ObjectDisposedException Object has already been disposed. @@ -43,9 +43,9 @@ public interface ISafeguardA2AContext * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; - + /** * Retrieves an SSH private key using Safeguard A2A. * @@ -55,40 +55,40 @@ public interface ISafeguardA2AContext * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; /** * Retrieves an API key secret using Safeguard A2A. - * + * * @param apiKey API key corresponding to the configured account. * @return A list of API key secrets. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ List retrieveApiKeySecret(char[] apiKey) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException; - + /** * Sets an SSH private key using Safeguard A2A. * * @param apiKey API key corresponding to the configured account. * @param privateKey Private key to set. * @param password Password associated with the private key. - * @param keyFormat Format to use when returning private key. + * @param keyFormat Format to use when returning private key. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, KeyFormat keyFormat) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException; - + /** * Gets an A2A event listener. The handler passed in will be registered for the AssetAccountPasswordUpdated * event, which is the only one supported in A2A. You just have to call Start(). The event listener returned * by this method WILL NOT automatically recover from a SignalR timeout which occurs when there is a 30+ * second outage. To get an event listener that supports recovering from longer term outages, please use * getPersistentEventListener() to request a persistent event listener. - * + * * @param apiKey API key corresponding to the configured account to listen for. * @param handler A delegate to call any time the AssetAccountPasswordUpdate event occurs. * @return The event listener. @@ -96,14 +96,14 @@ public interface ISafeguardA2AContext * @throws ArgumentException Invalid argument. */ ISafeguardEventListener getA2AEventListener(char[] apiKey, ISafeguardEventHandler handler) throws ObjectDisposedException, ArgumentException; - + /** * Gets an A2A event listener. The handler passed in will be registered for the AssetAccountPasswordUpdated * event, which is the only one supported in A2A. You just have to call Start(). The event listener returned * by this method WILL NOT automatically recover from a SignalR timeout which occurs when there is a 30+ * second outage. To get an event listener that supports recovering from longer term outages, please use * getPersistentEventListener() to request a persistent event listener. - * + * * @param apiKeys A list of API keys corresponding to the configured accounts to listen for. * @param handler A delegate to call any time the AssetAccountPasswordUpdate event occurs. * @return The event listener. @@ -139,10 +139,10 @@ public interface ISafeguardA2AContext * @throws ArgumentException Invalid argument */ ISafeguardEventListener getPersistentA2AEventListener(List apiKeys, ISafeguardEventHandler handler) throws ObjectDisposedException, ArgumentException; - + /** * Creates an access request on behalf of another user using Safeguard A2A. - * + * * @param apiKey API key corresponding to the configured account. * @param accessRequest The details of the access request to create. * @return A JSON string representing the new access request. @@ -151,7 +151,7 @@ public interface ISafeguardA2AContext * @throws ArgumentException Invalid argument */ String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRequest) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; - + /** * Dispose of an object */ diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardConnection.java index a5304ee..04c9b3a 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardConnection.java @@ -13,32 +13,32 @@ /** * This is the reusable connection interface that can be used to call Safeguard API after * connecting using the API access token obtained during authentication. - */ + */ public interface ISafeguardConnection { /** * Number of minutes remaining in the lifetime of the Safeguard API access token. - * + * * @return Remaining token life time * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. - */ + */ int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, SafeguardForJavaException; /** - * Request a new Safeguard API access token with the underlying credentials used to + * Request a new Safeguard API access token with the underlying credentials used to * initial create the connection. - * + * * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. - */ + */ void refreshAccessToken() throws ObjectDisposedException, SafeguardForJavaException; /** * Call a Safeguard API method and get any response as a string. Some Safeguard API * methods will return an empty body. If there is a failure a SafeguardDotNetException * will be thrown. - * + * * @param service Safeguard service to call. * @param method Safeguard method type to use. * @param relativeUrl Relative URL of the service to use. @@ -50,7 +50,7 @@ public interface ISafeguardConnection { * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ String invokeMethod(Service service, Method method, String relativeUrl, String body, Map parameters, Map additionalHeaders, Integer timeout) @@ -59,7 +59,7 @@ String invokeMethod(Service service, Method method, String relativeUrl, /** * Call a Safeguard API method and get a detailed response with status code, headers, * and body. If there is a failure a SafeguardDotNetException will be thrown. - * + * * @param service Safeguard service to call. * @param method Safeguard method type to use. * @param relativeUrl Relative URL of the service to use. @@ -71,7 +71,7 @@ String invokeMethod(Service service, Method method, String relativeUrl, * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ FullResponse invokeMethodFull(Service service, Method method, String relativeUrl, String body, Map parameters, Map additionalHeaders, Integer timeout) @@ -81,7 +81,7 @@ FullResponse invokeMethodFull(Service service, Method method, String relativeUrl * Call a Safeguard API method and get any response as a CSV string. Some Safeguard API * methods will return an empty body. If there is a failure a SafeguardDotNetException * will be thrown. - * + * * @param service Safeguard service to call. * @param method Safeguard method type to use. * @param relativeUrl Relative URL of the service to use. @@ -98,7 +98,7 @@ String invokeMethodCsv(Service service, Method method, String relativeUrl, String body, Map parameters, Map additionalHeaders, Integer timeout) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; - + /** * Join a Safeguard for Privileged Sessions and a Safeguard appliance. The Safeguard for * Privileged Sessions appliance needs to enable clustering and be a central search node. @@ -113,7 +113,7 @@ String invokeMethodCsv(Service service, Method method, String relativeUrl, */ FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; - + /** * Provides support for HTTP streaming requests * @return IStreamingRequest @@ -128,13 +128,13 @@ FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certific * second outage. To get an event listener that supports recovering from longer term * outages, please use GetPersistentEventListener() to request a persistent event * listener. - * + * * @return The event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. - */ + */ SafeguardEventListener getEventListener() throws ObjectDisposedException, ArgumentException; - + /** * Gets a persistent Safeguard event listener. You will need to call the * RegisterEventHandler() method to establish callbacks. Then, you just have to @@ -157,29 +157,29 @@ FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certific * @param networkAddress Network address. * @return Reusable Safeguard API connection. */ - ISafeguardConnection GetManagementServiceConnection(String networkAddress); - + ISafeguardConnection GetManagementServiceConnection(String networkAddress); + /** * Call Safeguard API to invalidate current access token and clear its value from * the connection. In order to continue using the connection you will need to call * RefreshAccessToken(). - * + * * @throws ObjectDisposedException Object has already been disposed. */ void logOut() throws ObjectDisposedException; - + /** * Returns a character array of the current access token which the caller can pass to other Safeguard - * methods, such as adding cluster members. + * methods, such as adding cluster members. * * @return The current access token. * @throws ObjectDisposedException Object has already been disposed. - */ + */ char[] getAccessToken() throws ObjectDisposedException; - + /** * Disposes of the connection. - * - */ + * + */ void dispose(); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardSessionsConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardSessionsConnection.java index 57631db..295843e 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardSessionsConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISafeguardSessionsConnection.java @@ -10,11 +10,11 @@ * This is the reusable connection interface that can be used to call SPS API. */ public interface ISafeguardSessionsConnection { - + /** * Call a Safeguard for Privileged Sessions API method and get any response as a string. * If there is a failure a SafeguardDotNetException will be thrown. - * + * * @param method Safeguard method type to use. * @param relativeUrl Relative URL of the service to use. * @param body Request body to pass to the method. @@ -22,7 +22,7 @@ public interface ISafeguardSessionsConnection { * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ String invokeMethod(Method method, String relativeUrl, String body) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; @@ -38,13 +38,13 @@ String invokeMethod(Method method, String relativeUrl, String body) * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ + */ FullResponse invokeMethodFull(Method method, String relativeUrl, String body) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; - + /** * Provides support for HTTP streaming requests - * + * * @return returns ISpsStreamingRequest * @throws ObjectDisposedException Object has already been disposed. */ diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISpsStreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISpsStreamingRequest.java index 0c04f97..b5dca5e 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/ISpsStreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/ISpsStreamingRequest.java @@ -27,7 +27,7 @@ public interface ISpsStreamingRequest { String uploadStream(String relativeUrl, byte[] stream, IProgressCallback progressCallback, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException; - + /** * Call a Safeguard Sps POST API providing a file as request content. If * there is a failure a SafeguardDotNetException will be thrown. @@ -40,15 +40,15 @@ String uploadStream(String relativeUrl, byte[] stream, IProgressCallback progres * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. */ - String uploadStream(String relativeUrl, String fileName, + String uploadStream(String relativeUrl, String fileName, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException; - + /** * Call a Safeguard Sps GET API returning output as a stream. The caller takes ownership of the - * StreamResponse and should dispose it when finished. + * StreamResponse and should dispose it when finished. * If there is a failure a SafeguardDotNetException will be thrown. - * + * * @param relativeUrl Relative URL of the service to use. * @param parameters Additional parameters to add to the URL. * @param additionalHeaders Additional headers to add to the request. @@ -62,7 +62,7 @@ StreamResponse downloadStream(String relativeUrl, Map parameters /** * Call a Safeguard GET API providing an output file path to which streaming download data will * be written. If there is a failure a SafeguardDotNetException will be thrown. - * + * * @param relativeUrl Relative URL of the service to use. * @param outputFilePath Full path to the file where download will be written. * @param progressCallback Optionally report upload progress. @@ -71,8 +71,8 @@ StreamResponse downloadStream(String relativeUrl, Map parameters * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. */ - void downloadStream(String relativeUrl, String outputFilePath, IProgressCallback progressCallback, + void downloadStream(String relativeUrl, String outputFilePath, IProgressCallback progressCallback, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException; - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/IStreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/IStreamingRequest.java index 405cb9c..f27eca7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/IStreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/IStreamingRequest.java @@ -12,9 +12,9 @@ public interface IStreamingRequest { /** - * Call a Safeguard POST API providing a stream as request content. If there is a + * Call a Safeguard POST API providing a stream as request content. If there is a * failure a SafeguardDotNetException will be thrown. - * + * * @param service Safeguard service to call. * @param relativeUrl Relative URL of the service to use. * @param stream Stream to upload as request content. @@ -25,15 +25,15 @@ public interface IStreamingRequest { * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ - String uploadStream(Service service, String relativeUrl, byte[] stream, IProgressCallback progressCallback, - Map parameters, Map additionalHeaders) + */ + String uploadStream(Service service, String relativeUrl, byte[] stream, IProgressCallback progressCallback, + Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException, ObjectDisposedException; /** * Call a Safeguard GET API providing an output file path to which streaming download data will * be written. If there is a failure a SafeguardDotNetException will be thrown. - * + * * @param service Safeguard service to call. * @param relativeUrl Relative URL of the service to use. * @param outputFilePath Full path to the file where download will be written. @@ -43,8 +43,8 @@ String uploadStream(Service service, String relativeUrl, byte[] stream, IProgres * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. * @throws ArgumentException Invalid argument. - */ - void downloadStream(Service service, String relativeUrl, String outputFilePath, IProgressCallback progressCallback, - Map parameters, Map additionalHeaders) + */ + void downloadStream(Service service, String relativeUrl, String outputFilePath, IProgressCallback progressCallback, + Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException, ObjectDisposedException; } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java index 2ceb704..e50f16c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java @@ -11,7 +11,7 @@ import java.util.Map; class PersistentSafeguardConnection implements ISafeguardConnection { - + private final ISafeguardConnection _connection; private boolean disposed; @@ -30,7 +30,7 @@ public void dispose() { } @Override - public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) + public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { if (_connection.getAccessTokenLifetimeRemaining() <= 0) _connection.refreshAccessToken(); @@ -72,11 +72,11 @@ public String invokeMethodCsv(Service service, Method method, String relativeUrl public SafeguardEventListener getEventListener() throws ObjectDisposedException, ArgumentException { return _connection.getEventListener(); } - + @Override public ISafeguardConnection GetManagementServiceConnection(String networkAddress) { return _connection.GetManagementServiceConnection(networkAddress); - } + } @Override public ISafeguardEventListener getPersistentEventListener() throws ObjectDisposedException, SafeguardForJavaException { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java index 9710915..dc8e9e3 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java @@ -38,7 +38,7 @@ private static SafeguardConnection getConnection(IAuthenticationMechanism authen * @param accessToken Existing API access token. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ArgumentException Invalid argument. */ @@ -66,7 +66,7 @@ public static ISafeguardConnection connect(String networkAddress, char[] accessT * @param accessToken Existing API access token. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ArgumentException Invalid argument. */ @@ -81,7 +81,7 @@ public static ISafeguardConnection connect(String networkAddress, char[] accessT // So, don't use GetConnection() function above return new SafeguardConnection(new AccessTokenAuthenticator(networkAddress, accessToken, version, false, validationCallback)); } - + /** * Connect to Safeguard API using a user name and password. * @@ -97,7 +97,7 @@ public static ISafeguardConnection connect(String networkAddress, char[] accessT * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String provider, String username, - char[] password, Integer apiVersion, Boolean ignoreSsl) + char[] password, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -128,7 +128,7 @@ public static ISafeguardConnection connect(String networkAddress, String provide * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String provider, String username, - char[] password, HostnameVerifier validationCallback, Integer apiVersion) + char[] password, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -149,14 +149,14 @@ public static ISafeguardConnection connect(String networkAddress, String provide * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String keystorePath, + public static ISafeguardConnection connect(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, - Integer apiVersion, Boolean ignoreSsl) + Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -168,7 +168,7 @@ public static ISafeguardConnection connect(String networkAddress, String keystor sslIgnore = ignoreSsl; } - return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, + return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, sslIgnore, null)); } @@ -182,26 +182,26 @@ public static ISafeguardConnection connect(String networkAddress, String keystor * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String keystorePath, + public static ISafeguardConnection connect(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, - HostnameVerifier validationCallback, Integer apiVersion) + HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; } - return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, + return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, false, validationCallback)); } /** - * Connect to Safeguard API using a certificate from the Windows + * Connect to Safeguard API using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -209,13 +209,13 @@ public static ISafeguardConnection connect(String networkAddress, String keystor * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String thumbprint, - Integer apiVersion, Boolean ignoreSsl) + public static ISafeguardConnection connect(String networkAddress, String thumbprint, + Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -235,13 +235,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - - return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, + + return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, version, sslIgnore, null)); } /** - * Connect to Safeguard API using a certificate from the Windows + * Connect to Safeguard API using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -249,13 +249,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String thumbprint, - HostnameVerifier validationCallback, Integer apiVersion) + public static ISafeguardConnection connect(String networkAddress, String thumbprint, + HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -270,8 +270,8 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - - return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, + + return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, version, false, validationCallback)); } @@ -284,13 +284,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String certificatePath, - char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) + char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -315,13 +315,13 @@ public static ISafeguardConnection connect(String networkAddress, String certifi * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String certificatePath, - char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) + char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -341,13 +341,13 @@ public static ISafeguardConnection connect(String networkAddress, String certifi * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, byte[] certificateData, - char[] certificatePassword, String certificateAlias, Integer apiVersion, Boolean ignoreSsl) + char[] certificatePassword, String certificateAlias, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -362,7 +362,7 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi return getConnection(new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, sslIgnore, null)); } - + /** * Connect to Safeguard API using a certificate stored in memory. * @@ -372,13 +372,13 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, byte[] certificateData, - char[] certificatePassword, String certificateAlias, HostnameVerifier validationCallback, Integer apiVersion) + char[] certificatePassword, String certificateAlias, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -388,7 +388,7 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi return getConnection(new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, false, validationCallback)); } - + /** * Connect to Safeguard API using a certificate from the keystore. The * appropriate keystore must have been loaded in the java process. @@ -400,14 +400,14 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String keystorePath, + public static ISafeguardConnection connect(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, String provider, - Integer apiVersion, Boolean ignoreSsl) + Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -419,7 +419,7 @@ public static ISafeguardConnection connect(String networkAddress, String keystor sslIgnore = ignoreSsl; } - return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, + return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, sslIgnore, null, provider)); } @@ -434,26 +434,26 @@ public static ISafeguardConnection connect(String networkAddress, String keystor * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String keystorePath, + public static ISafeguardConnection connect(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, - HostnameVerifier validationCallback, String provider, Integer apiVersion) + HostnameVerifier validationCallback, String provider, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; } - return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, + return getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, false, validationCallback, provider)); } /** - * Connect to Safeguard API using a certificate from the Windows + * Connect to Safeguard API using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -462,13 +462,13 @@ public static ISafeguardConnection connect(String networkAddress, String keystor * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String thumbprint, - String provider, Integer apiVersion, Boolean ignoreSsl) + public static ISafeguardConnection connect(String networkAddress, String thumbprint, + String provider, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -488,13 +488,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - - return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, + + return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, version, sslIgnore, null, provider)); } /** - * Connect to Safeguard API using a certificate from the Windows + * Connect to Safeguard API using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -503,13 +503,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, String thumbprint, - HostnameVerifier validationCallback, String provider, Integer apiVersion) + public static ISafeguardConnection connect(String networkAddress, String thumbprint, + HostnameVerifier validationCallback, String provider, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -524,8 +524,8 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - - return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, + + return getConnection(new CertificateAuthenticator(networkAddress, thumbprint, version, false, validationCallback, provider)); } @@ -539,13 +539,13 @@ public static ISafeguardConnection connect(String networkAddress, String thumbpr * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. * @param provider Safeguard authentication provider name (e.g. local). - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String certificatePath, - char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl, String provider) + char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl, String provider) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -571,13 +571,13 @@ public static ISafeguardConnection connect(String networkAddress, String certifi * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, String certificatePath, - char[] certificatePassword, HostnameVerifier validationCallback, String provider, Integer apiVersion) + char[] certificatePassword, HostnameVerifier validationCallback, String provider, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -598,13 +598,13 @@ public static ISafeguardConnection connect(String networkAddress, String certifi * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, byte[] certificateData, - char[] certificatePassword, String certificateAlias, String provider, Integer apiVersion, Boolean ignoreSsl) + char[] certificatePassword, String certificateAlias, String provider, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -619,7 +619,7 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi return getConnection(new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, sslIgnore, null, provider)); } - + /** * Connect to Safeguard API using a certificate stored in memory. * @@ -630,14 +630,14 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardConnection connect(String networkAddress, byte[] certificateData, char[] certificatePassword, String certificateAlias, HostnameVerifier validationCallback, - String provider, Integer apiVersion) + String provider, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -647,18 +647,18 @@ public static ISafeguardConnection connect(String networkAddress, byte[] certifi return getConnection(new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, false, validationCallback, provider)); } - + /** * Connect to Safeguard API anonymously. * * @param networkAddress Network address. * @param apiVersion API version. * @param ignoreSsl If set to true ignore ssl. - * + * * @return Reusable Safeguard API connection. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, Integer apiVersion, Boolean ignoreSsl) + public static ISafeguardConnection connect(String networkAddress, Integer apiVersion, Boolean ignoreSsl) throws SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -681,11 +681,11 @@ public static ISafeguardConnection connect(String networkAddress, Integer apiVer * @param networkAddress Network address. * @param apiVersion API version. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard API connection. * @throws SafeguardForJavaException General Safeguard for Java exception. */ - public static ISafeguardConnection connect(String networkAddress, HostnameVerifier validationCallback, Integer apiVersion) + public static ISafeguardConnection connect(String networkAddress, HostnameVerifier validationCallback, Integer apiVersion) throws SafeguardForJavaException { int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -696,13 +696,13 @@ public static ISafeguardConnection connect(String networkAddress, HostnameVerifi // So, don't use GetConnection() function above return new SafeguardConnection(new AnonymousAuthenticator(networkAddress, version, false, validationCallback)); } - + /** * Create a persistent connection to the Safeguard API that automatically renews expired access tokens. - * + * * @param connection Connection to be made persistent. * @return Reusable persistent Safeguard API connection. - */ + */ public static ISafeguardConnection Persist(ISafeguardConnection connection) { return new PersistentSafeguardConnection(connection); @@ -727,7 +727,7 @@ public static class Event { * @param password User password to use for authentication. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -737,7 +737,7 @@ public static class Event { public static ISafeguardEventListener getPersistentEventListener(String networkAddress, String provider, String username, char[] password, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -762,7 +762,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param password User password to use for authentication. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -772,7 +772,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA public static ISafeguardEventListener getPersistentEventListener(String networkAddress, String provider, String username, char[] password, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -792,7 +792,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -825,7 +825,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -852,7 +852,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -874,7 +874,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection( new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, ignoreSsl, null))); } - + /** * Get a persistent event listener using a client certificate stored in memory. * @@ -884,7 +884,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -901,7 +901,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection( new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, false, validationCallback))); } - + /** * Get a persistent event listener using a client certificate from the * certificate keystore for authentication. @@ -912,7 +912,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. @@ -933,7 +933,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, ignoreSsl, null))); } - + /** * Get a persistent event listener using a client certificate from the * certificate keystore for authentication. @@ -944,7 +944,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. @@ -972,7 +972,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. * @param provider Safeguard authentication provider name (e.g. local). - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1006,7 +1006,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1025,7 +1025,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } /** - * Get a persistent event listener using a certificate from the Windows + * Get a persistent event listener using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1033,7 +1033,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1051,7 +1051,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA if (ignoreSsl != null) { sslIgnore = ignoreSsl; } - + if (Utils.isWindows()) { if (!Utils.isSunMSCAPILoaded()) { throw new SafeguardForJavaException("Missing SunMSCAPI provider. The SunMSCAPI provider must be added as a security provider in $JAVA_HOME/jre/lib/security/java.security configuration file."); @@ -1066,7 +1066,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } /** - * Get a persistent event listener using a certificate from the Windows + * Get a persistent event listener using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1074,7 +1074,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1087,7 +1087,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA if (apiVersion != null) { version = apiVersion; } - + if (Utils.isWindows()) { if (!Utils.isSunMSCAPILoaded()) { throw new SafeguardForJavaException("Missing SunMSCAPI provider. The SunMSCAPI provider must be added as a security provider in $JAVA_HOME/jre/lib/security/java.security configuration file."); @@ -1102,7 +1102,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } /** - * Get a persistent event listener using a certificate from the Windows + * Get a persistent event listener using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1111,7 +1111,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. * @param provider Safeguard authentication provider name (e.g. local). - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1129,7 +1129,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA if (ignoreSsl != null) { sslIgnore = ignoreSsl; } - + if (Utils.isWindows()) { if (!Utils.isSunMSCAPILoaded()) { throw new SafeguardForJavaException("Missing SunMSCAPI provider. The SunMSCAPI provider must be added as a security provider in $JAVA_HOME/jre/lib/security/java.security configuration file."); @@ -1144,7 +1144,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } /** - * Get a persistent event listener using a certificate from the Windows + * Get a persistent event listener using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1153,7 +1153,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1166,7 +1166,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA if (apiVersion != null) { version = apiVersion; } - + if (Utils.isWindows()) { if (!Utils.isSunMSCAPILoaded()) { throw new SafeguardForJavaException("Missing SunMSCAPI provider. The SunMSCAPI provider must be added as a security provider in $JAVA_HOME/jre/lib/security/java.security configuration file."); @@ -1190,7 +1190,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1212,7 +1212,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection( new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, ignoreSsl, null, provider))); } - + /** * Get a persistent event listener using a client certificate stored in memory. * @@ -1223,7 +1223,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java @@ -1241,7 +1241,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection( new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, false, validationCallback, provider))); } - + /** * Get a persistent event listener using a client certificate from the * certificate keystore for authentication. @@ -1253,7 +1253,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. @@ -1275,7 +1275,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA return new PersistentSafeguardEventListener(getConnection(new CertificateAuthenticator(networkAddress, keystorePath, keystorePassword, certificateAlias, version, ignoreSsl, null, provider))); } - + /** * Get a persistent event listener using a client certificate from the * certificate keystore for authentication. @@ -1287,13 +1287,13 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA * @param provider Safeguard authentication provider name (e.g. local). * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent Safeguard event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws SafeguardForJavaException General Safeguard for Java exception. */ public static ISafeguardEventListener getPersistentEventListener(String networkAddress, - String keystorePath, char[] keystorePassword, String certificateAlias, + String keystorePath, char[] keystorePassword, String certificateAlias, HostnameVerifier validationCallback, String provider, Integer apiVersion) throws ObjectDisposedException, SafeguardForJavaException { int version = DEFAULTAPIVERSION; @@ -1321,7 +1321,7 @@ public static class A2A { * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, @@ -1348,7 +1348,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String keys * @param certificateAlias Alias identifying a client certificate in the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, @@ -1362,7 +1362,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String keys } /** - * Establish a Safeguard A2A context using a certificate from the Windows + * Establish a Safeguard A2A context using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1370,7 +1370,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String keys * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard A2A context. * @throws SafeguardForJavaException General Safeguard for Java exception. */ @@ -1394,12 +1394,12 @@ public static ISafeguardA2AContext getContext(String networkAddress, String thum else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - + return new SafeguardA2AContext(networkAddress, version, sslIgnore, thumbprint, null); } /** - * Establish a Safeguard A2A context using a certificate from the Windows + * Establish a Safeguard A2A context using a certificate from the Windows * certificate store. This is a Windows only API and requires that the * SunMSCAPI security provider is available in the Java environment. * @@ -1407,7 +1407,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String thum * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard A2A context. * @throws SafeguardForJavaException General Safeguard for Java exception. */ @@ -1417,7 +1417,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String thum if (apiVersion != null) { version = apiVersion; } - + if (Utils.isWindows()) { if (!Utils.isSunMSCAPILoaded()) { throw new SafeguardForJavaException("Missing SunMSCAPI provider. The SunMSCAPI provider must be added as a security provider in $JAVA_HOME/jre/lib/security/java.security configuration file."); @@ -1439,7 +1439,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String thum * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, String certificatePath, @@ -1466,7 +1466,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String cert * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, String certificatePath, @@ -1487,7 +1487,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, String cert * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, byte[] certificateData, @@ -1504,7 +1504,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, byte[] cert return new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, sslIgnore, null); } - + /** * Establish a Safeguard A2A context using a client certificate stored in memory. * @@ -1513,7 +1513,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, byte[] cert * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return Reusable Safeguard A2A context. */ public static ISafeguardA2AContext getContext(String networkAddress, byte[] certificateData, @@ -1525,7 +1525,7 @@ public static ISafeguardA2AContext getContext(String networkAddress, byte[] cert return new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, false, validationCallback); } - + /** * This static class provides access to Safeguard A2A Event * functionality with persistent event listeners. Persistent event @@ -1536,9 +1536,9 @@ public static ISafeguardA2AContext getContext(String networkAddress, byte[] cert public static class Event { /** - * Get a persistent A2A event listener. The handler passed - * in will be registered for the AssetAccountPasswordUpdated - * event, which is the only one supported in A2A. Uses a + * Get a persistent A2A event listener. The handler passed + * in will be registered for the AssetAccountPasswordUpdated + * event, which is the only one supported in A2A. Uses a * client certificate in a keystore. * * @param apiKey API key corresponding to the configured account to @@ -1553,7 +1553,7 @@ public static class Event { * the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1578,9 +1578,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe } /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate the keystore. * * @param apiKey API key corresponding to the configured account to @@ -1595,7 +1595,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1615,9 +1615,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe } /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate from a file. * * @param apiKey API key corresponding to the configured account to @@ -1630,7 +1630,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1639,7 +1639,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe ISafeguardEventHandler handler, String networkAddress, String certificatePath, char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -1654,11 +1654,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, ignoreSsl, null), apiKey, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate from a file. * * @param apiKey API key corresponding to the configured account to @@ -1671,7 +1671,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1680,7 +1680,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe ISafeguardEventHandler handler, String networkAddress, String certificatePath, char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -1690,11 +1690,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, false, validationCallback), apiKey, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in memory. * * @param apiKey API key corresponding to the configured account to @@ -1706,7 +1706,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1715,7 +1715,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe ISafeguardEventHandler handler, String networkAddress, byte[] certificateData, char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -1730,11 +1730,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, ignoreSsl, null), apiKey, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in memory. * * @param apiKey API key corresponding to the configured account to @@ -1746,7 +1746,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1755,7 +1755,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe ISafeguardEventHandler handler, String networkAddress, byte[] certificateData, char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -1765,11 +1765,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, false, validationCallback), apiKey, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate from a keystore. * * @param apiKeys A list of API keys corresponding to the configured accounts to @@ -1784,7 +1784,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe * the keystore. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1809,9 +1809,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List } /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate from a keystore. * * @param apiKeys A list of API keys corresponding to the configured accounts to @@ -1826,7 +1826,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * the keystore. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1846,9 +1846,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List } /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in a file. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -1861,7 +1861,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1870,7 +1870,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, String certificatePath, char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -1885,15 +1885,15 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, ignoreSsl, null), apiKeys, handler); } - + /** * NOT SUPPORTED - * - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client - * certificate from the Windows certificate store. This is a - * Windows only API and requires that the SunMSCAPI security + * + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client + * certificate from the Windows certificate store. This is a + * Windows only API and requires that the SunMSCAPI security * provider is available in the Java environment. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -1904,7 +1904,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1914,9 +1914,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, String thumbprint, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { - + throw new SafeguardForJavaException("Not supported. This function is not available for Java."); - + /* int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -1931,21 +1931,21 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - + return new PersistentSafeguardA2AEventListener( - new SafeguardA2AContext(networkAddress, version, false, + new SafeguardA2AContext(networkAddress, version, false, thumbprint, validationCallback), apiKeys, handler); */ } - + /** * NOT SUPPORTED - * - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client - * certificate from the Windows certificate store. This is a - * Windows only API and requires that the SunMSCAPI security + * + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client + * certificate from the Windows certificate store. This is a + * Windows only API and requires that the SunMSCAPI security * provider is available in the Java environment. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -1956,7 +1956,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param thumbprint Thumbprint of the client certificate. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -1966,9 +1966,9 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, String thumbprint, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { - + throw new SafeguardForJavaException("Not supported. This function is not available for Java."); - + /* int version = DEFAULTAPIVERSION; if (apiVersion != null) { @@ -1988,17 +1988,17 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List else { throw new SafeguardForJavaException("Not implemented. This function is only available on the Windows platform."); } - + return new PersistentSafeguardA2AEventListener( - new SafeguardA2AContext(networkAddress, version, ignoreSsl, + new SafeguardA2AContext(networkAddress, version, ignoreSsl, thumbprint, null), apiKeys, handler); */ } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in a file. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -2011,7 +2011,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -2020,7 +2020,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, String certificatePath, char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -2030,11 +2030,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, false, validationCallback), apiKeys, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in memory. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -2046,7 +2046,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param ignoreSsl Ignore server certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -2055,7 +2055,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, byte[] certificateData, char[] certificatePassword, Integer apiVersion, Boolean ignoreSsl) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; @@ -2070,11 +2070,11 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, ignoreSsl, null), apiKeys, handler); } - + /** - * Get a persistent A2A event listener. The handler passed in - * will be registered for the AssetAccountPasswordUpdated event, - * which is the only one supported in A2A. Uses a client + * Get a persistent A2A event listener. The handler passed in + * will be registered for the AssetAccountPasswordUpdated event, + * which is the only one supported in A2A. Uses a client * certificate stored in memory. * * @param apiKeys A list of API key corresponding to the configured accounts to @@ -2086,7 +2086,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List * @param certificatePassword Password to decrypt the certificate file. * @param apiVersion Target API version to use. * @param validationCallback Callback function to be executed during SSL certificate validation. - * + * * @return New persistent A2A event listener. * @throws ObjectDisposedException Object has already been disposed. * @throws ArgumentException Invalid argument. @@ -2095,7 +2095,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List ISafeguardEventHandler handler, String networkAddress, byte[] certificateData, char[] certificatePassword, HostnameVerifier validationCallback, Integer apiVersion) throws ObjectDisposedException, ArgumentException { - + int version = DEFAULTAPIVERSION; if (apiVersion != null) { version = apiVersion; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java index 3b02062..a88e188 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java @@ -45,10 +45,10 @@ public class SafeguardA2AContext implements ISafeguardA2AContext { private final RestClient a2AClient; private final RestClient coreClient; - + public SafeguardA2AContext(String networkAddress, CertificateContext clientCertificate, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { this.networkAddress = networkAddress; - + String safeguardA2AUrl = String.format("https://%s/service/a2a/v%d", this.networkAddress, apiVersion); this.a2AClient = new RestClient(safeguardA2AUrl, ignoreSsl, validationCallback); String safeguardCoreUrl = String.format("https://%s/service/core/v%d", this.networkAddress, apiVersion); @@ -64,20 +64,20 @@ public SafeguardA2AContext(String networkAddress, String certificateAlias, Strin char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { this(networkAddress, new CertificateContext(certificateAlias, certificatePath, null, certificatePassword), apiVersion, ignoreSsl, validationCallback); } - + public SafeguardA2AContext(String networkAddress, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { this(networkAddress, new CertificateContext(certificateAlias, null, null, null), apiVersion, ignoreSsl, validationCallback); } - + public SafeguardA2AContext(String networkAddress, int apiVersion, boolean ignoreSsl, String thumbprint, HostnameVerifier validationCallback) throws SafeguardForJavaException { this(networkAddress, new CertificateContext(thumbprint), apiVersion, ignoreSsl, validationCallback); } - + public SafeguardA2AContext(String networkAddress, String certificatePath, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { this(networkAddress, new CertificateContext(null, certificatePath, null, certificatePassword), apiVersion, ignoreSsl, validationCallback); } - + public SafeguardA2AContext(String networkAddress, byte[] certificateData, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { this(networkAddress, new CertificateContext(null, null, certificateData, certificatePassword), apiVersion, ignoreSsl, validationCallback); @@ -85,7 +85,7 @@ public SafeguardA2AContext(String networkAddress, byte[] certificateData, char[] @Override public List getRetrievableAccounts() throws ObjectDisposedException, SafeguardForJavaException { - + if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } @@ -96,37 +96,37 @@ public List getRetrievableAccounts() throws ObjectDispo headers.put(HttpHeaders.ACCEPT, "application/json"); Map parameters = new HashMap<>(); - + CloseableHttpResponse response = coreClient.execGET("A2ARegistrations", parameters, headers, null, clientCertificate); if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getStatusLine().getStatusCode(), reply)); - - + + List registrations = parseA2ARegistationResponse(reply); - + for (A2ARegistration registration : registrations) { - + int registrationId = registration.getId(); - - response = coreClient.execGET(String.format("A2ARegistrations/%d/RetrievableAccounts", registrationId), + + response = coreClient.execGET(String.format("A2ARegistrations/%d/RetrievableAccounts", registrationId), parameters, headers, null, clientCertificate); - + if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getStatusLine().getStatusCode(), reply)); - + List retrievals = parseA2ARetrievableAccountResponse(reply); - + for (A2ARetrievableAccountInternal retrieval : retrievals) { A2ARetrievableAccount account = new A2ARetrievableAccount(); @@ -144,7 +144,7 @@ public List getRetrievableAccounts() throws ObjectDispo account.setDomainName(retrieval.getDomainName()); account.setAccountType(retrieval.getAccountType()); account.setAccountDescription(retrieval.getAccountDescription()); - + list.add(account); } } @@ -153,11 +153,11 @@ public List getRetrievableAccounts() throws ObjectDispo @Override public char[] retrievePassword(char[] apiKey) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } - + if (apiKey == null) { throw new ArgumentException("The apiKey parameter may not be null"); } @@ -173,7 +173,7 @@ public char[] retrievePassword(char[] apiKey) throws ObjectDisposedException, Sa if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + String reply = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " @@ -191,11 +191,11 @@ public void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedExc if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } - + if (apiKey == null) { throw new ArgumentException("The apiKey parameter may not be null"); } - + if (password == null) { throw new ArgumentException("The password parameter may not be null"); } @@ -205,9 +205,9 @@ public void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedExc Map parameters = new HashMap<>(); - CloseableHttpResponse response = a2AClient.execPUT("Credentials/Password", parameters, headers, null, + CloseableHttpResponse response = a2AClient.execPUT("Credentials/Password", parameters, headers, null, new JsonBody("\""+new String(password)+"\""), clientCertificate); - + if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } @@ -217,7 +217,7 @@ public void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedExc throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); } - + Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A password."); } @@ -229,7 +229,7 @@ public char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws Obje if (keyFormat == null) keyFormat = KeyFormat.OpenSsh; - + if (apiKey == null) throw new ArgumentException("The apiKey parameter may not be null."); @@ -245,7 +245,7 @@ public char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws Obje if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + String reply = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " @@ -253,15 +253,15 @@ public char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws Obje } char[] privateKey = (new Gson().fromJson(reply, String.class)).toCharArray(); - + Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A private key."); return privateKey; } @Override - public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, KeyFormat keyFormat) + public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, KeyFormat keyFormat) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { - + if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } @@ -271,17 +271,17 @@ public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, Key if (apiKey == null) throw new ArgumentException("The apiKey parameter may not be null."); - + if (privateKey == null) throw new ArgumentException("The privateKey parameter may not be null"); - + if (password == null) throw new ArgumentException("The password parameter may not be null"); SshKey sshKey = new SshKey(); sshKey.setPassphrase(new String(password)); sshKey.setPrivateKey(new String(privateKey)); - + String body = new Gson().toJson(sshKey); Map headers = new HashMap<>(); @@ -291,27 +291,27 @@ public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, Key parameters.put("keyFormat", keyFormat.name()); CloseableHttpResponse response = a2AClient.execPUT("Credentials/SshKey", parameters, headers, null, new JsonBody(body), clientCertificate); - + if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + String reply = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); } - + Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A private key."); return; } - + @Override public List retrieveApiKeySecret(char[] apiKey) throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } - + if (apiKey == null) throw new ArgumentException("The apiKey parameter may not be null."); @@ -334,9 +334,9 @@ public List retrieveApiKeySecret(char[] apiKey) throws ObjectDisp throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); } - + List apiKeySecretsInternal = parseApiKeySecretResponse(reply); - + for (ApiKeySecretInternal apiKeySecretInternal : apiKeySecretsInternal) { ApiKeySecret apiKeySecret = new ApiKeySecret(); apiKeySecret.setId(apiKeySecretInternal.getId()); @@ -345,17 +345,17 @@ public List retrieveApiKeySecret(char[] apiKey) throws ObjectDisp apiKeySecret.setClientId(apiKeySecretInternal.getClientId()); apiKeySecret.setClientSecret(apiKeySecretInternal.getClientSecret().toCharArray()); apiKeySecret.setClientSecretId(apiKeySecretInternal.getClientSecretId()); - + list.add(apiKeySecret); } return list; } - + @Override public ISafeguardEventListener getA2AEventListener(char[] apiKey, ISafeguardEventHandler handler) throws ObjectDisposedException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } @@ -371,11 +371,11 @@ public ISafeguardEventListener getA2AEventListener(char[] apiKey, ISafeguardEven Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Event listener successfully created for Safeguard A2A context."); return eventListener; } - + @Override public ISafeguardEventListener getA2AEventListener(List apiKeys, ISafeguardEventHandler handler) throws ObjectDisposedException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardA2AContext"); } @@ -404,7 +404,7 @@ public ISafeguardEventListener getPersistentA2AEventListener(char[] apiKey, ISaf return new PersistentSafeguardA2AEventListener((ISafeguardA2AContext)this.cloneObject(), apiKey, handler); } - + @Override public ISafeguardEventListener getPersistentA2AEventListener(List apiKeys, ISafeguardEventHandler handler) throws ObjectDisposedException, ArgumentException { @@ -417,7 +417,7 @@ public ISafeguardEventListener getPersistentA2AEventListener(List apiKey return new PersistentSafeguardA2AEventListener((ISafeguardA2AContext)this.cloneObject(), apiKeys, handler); } - + @Override public String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRequest) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { @@ -437,7 +437,7 @@ public String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRe if (accessRequest.getAssetId() == null && accessRequest.getAssetName() == null) { throw new SafeguardForJavaException("You must specify an asset to create an access request for"); } - + BrokeredAccessRequest brokeredAccessRequest = (BrokeredAccessRequest)accessRequest; brokeredAccessRequest.setVersion(apiVersion); @@ -452,7 +452,7 @@ public String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRe if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", a2AClient.getBaseURL())); } - + String reply = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " @@ -478,16 +478,16 @@ protected void finalize() throws Throwable { super.finalize(); } } - + public Object cloneObject() { return new SafeguardA2AContext(networkAddress, clientCertificate, apiVersion, ignoreSsl, validationCallback); } - + private List parseA2ARegistationResponse(String response) { - + ObjectMapper mapper = new ObjectMapper(); - + try { A2ARegistration[] registrations = mapper.readValue(response, A2ARegistration[].class); return Arrays.asList(registrations); @@ -497,11 +497,11 @@ private List parseA2ARegistationResponse(String response) { return null; } - + private List parseA2ARetrievableAccountResponse(String response) { - + ObjectMapper mapper = new ObjectMapper(); - + try { A2ARetrievableAccountInternal[] accounts = mapper.readValue(response, A2ARetrievableAccountInternal[].class); return Arrays.asList(accounts); @@ -513,9 +513,9 @@ private List parseA2ARetrievableAccountResponse(S } private List parseApiKeySecretResponse(String response) { - + ObjectMapper mapper = new ObjectMapper(); - + try { ApiKeySecretInternal[] apiKeySecrets = mapper.readValue(response, ApiKeySecretInternal[].class); return Arrays.asList(apiKeySecrets); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java index ea2856c..f2137d9 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java @@ -34,7 +34,7 @@ class SafeguardConnection implements ISafeguardConnection { private final RestClient applianceClient; private final RestClient notificationClient; private final IStreamingRequest streamingRequest; - + public SafeguardConnection(IAuthenticationMechanism authenticationMechanism) { this.authenticationMechanism = authenticationMechanism; @@ -49,7 +49,7 @@ public SafeguardConnection(IAuthenticationMechanism authenticationMechanism) { String safeguardNotificationUrl = String.format("https://%s/service/notification/v%d", this.authenticationMechanism.getNetworkAddress(), this.authenticationMechanism.getApiVersion()); notificationClient = new RestClient(safeguardNotificationUrl, authenticationMechanism.isIgnoreSsl(), authenticationMechanism.getValidationCallback()); - + streamingRequest = new StreamingRequest(this); } @@ -96,17 +96,17 @@ public FullResponse invokeMethodFull(Service service, Method method, String rela } if (Utils.isNullOrEmpty(relativeUrl)) throw new ArgumentException("Parameter relativeUrl may not be null or empty"); - + RestClient client = getClientForService(service); if (!authenticationMechanism.isAnonymous() && !authenticationMechanism.hasAccessToken()) { throw new SafeguardForJavaException("Access token is missing due to log out, you must refresh the access token to invoke a method"); } - + Map headers = prepareHeaders(additionalHeaders, service); CloseableHttpResponse response = null; logRequestDetails(method, client.getBaseURL() + "/" + relativeUrl, parameters, additionalHeaders); - + switch (method) { case Get: response = client.execGET(relativeUrl, parameters, headers, timeout); @@ -121,30 +121,30 @@ public FullResponse invokeMethodFull(Service service, Method method, String rela response = client.execDELETE(relativeUrl, parameters, headers, timeout); break; } - + if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", client.getBaseURL())); } String reply = Utils.getResponse(response); - + if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); } FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); - + logResponseDetails(fullResponse); - + return fullResponse; } @Override - public String invokeMethodCsv(Service service, Method method, String relativeUrl, + public String invokeMethodCsv(Service service, Method method, String relativeUrl, String body, Map parameters, Map additionalHeaders, Integer timeout) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardConnection"); } @@ -152,14 +152,14 @@ public String invokeMethodCsv(Service service, Method method, String relativeUrl additionalHeaders = new HashMap<>(); } additionalHeaders.put(HttpHeaders.ACCEPT, "text/csv"); - + return invokeMethodFull(service, method, relativeUrl, body, parameters, additionalHeaders, timeout).getBody(); } - + @Override - public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) + public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + if (disposed) throw new ObjectDisposedException("SafeguardConnection"); @@ -167,15 +167,15 @@ public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String c request.setSpp(sppAddress); request.setSpp_api_token(authenticationMechanism.getAccessToken()); request.setSpp_cert_chain(certificateChain); - + Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Sending join request."); FullResponse joinResponse = spsConnection.invokeMethodFull(Method.Post, "cluster/spp", request.toJson()); - + logResponseDetails(joinResponse); return joinResponse; } - + @Override public SafeguardEventListener getEventListener() throws ObjectDisposedException, ArgumentException { SafeguardEventListener eventListener = new SafeguardEventListener( @@ -189,7 +189,7 @@ public SafeguardEventListener getEventListener() throws ObjectDisposedException, @Override public ISafeguardEventListener getPersistentEventListener() throws ObjectDisposedException, SafeguardForJavaException { - + if (disposed) throw new ObjectDisposedException("SafeguardConnection"); @@ -204,13 +204,13 @@ public ISafeguardEventListener getPersistentEventListener() public ISafeguardConnection GetManagementServiceConnection(String networkAddress) { return new SafeguardManagementServiceConnection(authenticationMechanism, networkAddress); } - + @Override public void logOut() throws ObjectDisposedException { - + if (disposed) throw new ObjectDisposedException("SafeguardConnection"); - + if (!authenticationMechanism.hasAccessToken()) return; try { @@ -223,7 +223,7 @@ public void logOut() throws ObjectDisposedException { authenticationMechanism.clearAccessToken(); Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Cleared access token"); } - + static void logRequestDetails(Method method, String uri, Map parameters, Map headers) { String msg = String.format("Invoking method: %s %s", method.toString().toUpperCase(), uri); @@ -258,30 +258,30 @@ protected RestClient getClientForService(Service service) throws SafeguardForJav throw new SafeguardForJavaException("Unknown or unsupported service specified"); } } - - Map prepareHeaders(Map additionalHeaders, Service service) + + Map prepareHeaders(Map additionalHeaders, Service service) throws ObjectDisposedException { - + Map headers = new HashMap<>(); - if (!(authenticationMechanism instanceof AnonymousAuthenticator)) { + if (!(authenticationMechanism instanceof AnonymousAuthenticator)) { headers.put(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", new String(authenticationMechanism.getAccessToken()))); } - - if (additionalHeaders != null) { + + if (additionalHeaders != null) { headers.putAll(additionalHeaders); if (!additionalHeaders.containsKey(HttpHeaders.ACCEPT)) headers.put(HttpHeaders.ACCEPT, "application/json"); // Assume JSON unless specified } return headers; } - + boolean isDisposed() { return disposed; } - + IAuthenticationMechanism getAuthenticationMechanism() { return authenticationMechanism; - } + } @Override public void dispose() @@ -301,8 +301,8 @@ protected void finalize() throws Throwable { super.finalize(); } } - - public Object cloneObject() throws SafeguardForJavaException + + public Object cloneObject() throws SafeguardForJavaException { return new SafeguardConnection((IAuthenticationMechanism)authenticationMechanism.cloneObject()); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardForPrivilegedSessions.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardForPrivilegedSessions.java index abe99da..3e30118 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardForPrivilegedSessions.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardForPrivilegedSessions.java @@ -13,20 +13,20 @@ public class SafeguardForPrivilegedSessions { * @param username User name to use for authentication. * @param password User password to use for authentication. * @param ignoreSsl Ignore server certificate validation. - * + * * @return Reusable Safeguard for Privileged Sessions API connection. * @throws SafeguardForJavaException General Safeguard for Java exception. - */ + */ public static ISafeguardSessionsConnection Connect(String networkAddress, String username, - char[] password, boolean ignoreSsl) + char[] password, boolean ignoreSsl) throws SafeguardForJavaException { return new SafeguardSessionsConnection(networkAddress, username, password, ignoreSsl, null); } - + //TODO: This class should provide an Connect API with a validationCallback parameter // public static ISafeguardSessionsConnection Connect(String networkAddress, String username, -// char[] password, HostnameVerifier validationCallback) +// char[] password, HostnameVerifier validationCallback) // throws SafeguardForJavaException // { // return new SafeguardSessionsConnection(networkAddress, username, password, ignoreSsl); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardManagementServiceConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardManagementServiceConnection.java index f3764df..e5505e4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardManagementServiceConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardManagementServiceConnection.java @@ -11,11 +11,11 @@ class SafeguardManagementServiceConnection extends SafeguardConnection { private final IAuthenticationMechanism authenticationMechanism; - + private final RestClient managementClient; public SafeguardManagementServiceConnection(IAuthenticationMechanism parentAuthenticationMechanism, String networkAddress) { - + super(parentAuthenticationMechanism); authenticationMechanism = new ManagementServiceAuthenticator(parentAuthenticationMechanism, networkAddress); @@ -24,11 +24,11 @@ public SafeguardManagementServiceConnection(IAuthenticationMechanism parentAuthe managementClient = new RestClient(safeguardManagementUrl, authenticationMechanism.isIgnoreSsl(), authenticationMechanism.getValidationCallback()); } - public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) + public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String certificateChain, String sppAddress) throws SafeguardForJavaException { throw new SafeguardForJavaException("Management connection cannot be used to join SPS."); } - + public ISafeguardEventListener GetEventListener() throws SafeguardForJavaException { throw new SafeguardForJavaException("Management connection does not support event listeners."); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java index 7bacf49..c9a3c07 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java @@ -57,9 +57,9 @@ public String uploadStream(String relativeUrl, byte[] stream, IProgressCallback return fullResponse.getBody(); } - + @Override - public String uploadStream(String relativeUrl, String fileName, + public String uploadStream(String relativeUrl, String fileName, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException { @@ -93,12 +93,12 @@ public String uploadStream(String relativeUrl, String fileName, return fullResponse.getBody(); } - - + + @Override - public StreamResponse downloadStream(String relativeUrl, Map parameters, Map additionalHeaders) + public StreamResponse downloadStream(String relativeUrl, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException { - + if (Utils.isNullOrEmpty(relativeUrl)) { throw new ArgumentException("Parameter relativeUrl cannot be null or empty"); } @@ -127,12 +127,12 @@ public StreamResponse downloadStream(String relativeUrl, Map par return new StreamResponse(response); } - + @Override - public void downloadStream(String relativeUrl, String outputFilePath, IProgressCallback progressCallback, + public void downloadStream(String relativeUrl, String outputFilePath, IProgressCallback progressCallback, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException { - + StreamResponse streamResponse = null; InputStream input = null; OutputStream output = null; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java index 4575e95..b170154 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java @@ -5,7 +5,7 @@ import java.io.InputStream; import org.apache.http.client.methods.CloseableHttpResponse; -/** +/** * Represents a streamed response */ public class StreamResponse { @@ -48,13 +48,13 @@ public Long getContentLength() { } return contentLength; } - + public void dispose() { if (!disposed) { disposed = true; if (stream != null) { - try { - stream.close(); + try { + stream.close(); } catch (IOException logOrIgnore) {} } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java index 1045d53..40921e4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java @@ -31,7 +31,7 @@ class StreamingRequest implements IStreamingRequest { @Override public String uploadStream(Service service, String relativeUrl, byte[] stream, IProgressCallback progressCallback, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException, ObjectDisposedException { - + if (safeguardConnection.isDisposed()) throw new ObjectDisposedException("SafeguardConnection"); if (Utils.isNullOrEmpty(relativeUrl)) @@ -41,10 +41,10 @@ public String uploadStream(Service service, String relativeUrl, byte[] stream, I if (!authenticationMechanism.isAnonymous() && !authenticationMechanism.hasAccessToken()) { throw new SafeguardForJavaException("Access token is missing due to log out, you must refresh the access token to invoke a method"); } - + Map headers = safeguardConnection.prepareHeaders(additionalHeaders, service); CloseableHttpResponse response = null; - + SafeguardConnection.logRequestDetails(Method.Post, client.getBaseURL() + "/" + relativeUrl, parameters, additionalHeaders); response = client.execPOSTBytes(relativeUrl, parameters, headers, null, stream, progressCallback); @@ -70,7 +70,7 @@ public String uploadStream(Service service, String relativeUrl, byte[] stream, I @Override public void downloadStream(Service service, String relativeUrl, String outputFilePath, IProgressCallback progressCallback, Map parameters, Map additionalHeaders) throws SafeguardForJavaException, ArgumentException, ObjectDisposedException { - + if (safeguardConnection.isDisposed()) throw new ObjectDisposedException("SafeguardConnection"); if (Utils.isNullOrEmpty(relativeUrl)) @@ -80,9 +80,9 @@ public void downloadStream(Service service, String relativeUrl, String outputFil if (!authenticationMechanism.isAnonymous() && !authenticationMechanism.hasAccessToken()) { throw new SafeguardForJavaException("Access token is missing due to log out, you must refresh the access token to invoke a method"); } - + Map headers = safeguardConnection.prepareHeaders(additionalHeaders, service); - + SafeguardConnection.logRequestDetails(Method.Get, client.getBaseURL() + "/" + relativeUrl, parameters, additionalHeaders); CloseableHttpResponse response = client.execGETBytes(relativeUrl, parameters, headers, null, progressCallback); @@ -114,7 +114,7 @@ public void downloadStream(Service service, String relativeUrl, String outputFil if (output != null) try { output.close(); } catch (IOException logOrIgnore) {} if (input != null) try { input.close(); } catch (IOException logOrIgnore) {} } - + FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), null); SafeguardConnection.logResponseDetails(fullResponse); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java index 874a263..df92409 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java @@ -52,7 +52,7 @@ public static String getResponse(CloseableHttpResponse response) { if (entity != null) { try { return EntityUtils.toString(response.getEntity()); - + } catch (IOException | ParseException ex) {} } return ""; @@ -69,7 +69,7 @@ public static boolean isSuccessful(int status) { return false; } } - + public static String getOsName() { if (OS == null) { OS = System.getProperty("os.name"); @@ -80,10 +80,10 @@ public static String getOsName() { public static boolean isWindows() { return getOsName().startsWith("Windows"); } - + public static boolean isSunMSCAPILoaded() { Provider provider = Security.getProvider("SunMSCAPI"); return provider != null; } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java index e478549..94deb45 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java @@ -14,7 +14,7 @@ public AccessTokenAuthenticator(String networkAddress, char[] accessToken, super(networkAddress, apiVersion, ignoreSsl, validationCallback); if (accessToken == null) throw new ArgumentException("The accessToken parameter can not be null"); - + this.accessToken = accessToken.clone(); } @@ -22,7 +22,7 @@ public AccessTokenAuthenticator(String networkAddress, char[] accessToken, public String getId() { return "AccessToken"; } - + @Override protected char[] getRstsTokenInternal() throws SafeguardForJavaException { @@ -33,14 +33,14 @@ protected char[] getRstsTokenInternal() throws SafeguardForJavaException public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Access token authenticators are not cloneable"); } - + @Override public void dispose() { super.dispose(); disposed = true; } - + @Override protected void finalize() throws Throwable { try { @@ -49,5 +49,5 @@ protected void finalize() throws Throwable { super.finalize(); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java index d77ea39..7b7aceb 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java @@ -15,10 +15,10 @@ public class AnonymousAuthenticator extends AuthenticatorBase { public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); - + String notificationUrl = String.format("https://%s/service/notification/v%d", networkAddress, apiVersion); RestClient notificationClient = new RestClient(notificationUrl, ignoreSsl, validationCallback); - + Map headers = new HashMap<>(); headers.put(HttpHeaders.ACCEPT, "application/json"); headers.put(HttpHeaders.CONTENT_TYPE, "application/json"); @@ -27,7 +27,7 @@ public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ign if (response == null) { throw new SafeguardForJavaException(String.format("Unable to anonymously connect to web service %s", notificationClient.getBaseURL())); } - + String reply = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { @@ -40,12 +40,12 @@ public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ign public String getId() { return "Anonymous"; } - + @Override public boolean isAnonymous() { return true; } - + @Override protected char[] getRstsTokenInternal() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous connection cannot be used to get an API access token, Error: Unsupported operation"); @@ -55,12 +55,12 @@ protected char[] getRstsTokenInternal() throws SafeguardForJavaException { public boolean hasAccessToken() { return false; } - + @Override public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous authenticators are not cloneable"); } - + @Override public void dispose() { super.dispose(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java index 1a473c0..212162b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java @@ -161,27 +161,27 @@ public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJav accessToken = map.get("UserToken").toCharArray(); } } - + public String resolveProviderToScope(String provider) throws SafeguardForJavaException { try { CloseableHttpResponse response; Map headers = new HashMap<>(); - + headers.clear(); headers.put(HttpHeaders.ACCEPT, "application/json"); - + response = coreClient.execGET("AuthenticationProviders", null, headers, null); - + if (response == null) throw new SafeguardForJavaException("Unable to connect to RSTS to find identity provider scopes"); - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) throw new SafeguardForJavaException("Error requesting identity provider scopes from RSTS, Error: " + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); - + List knownScopes = parseLoginResponse(reply); // 3 step check for determining if the user provided scope is valid: @@ -205,7 +205,7 @@ public String resolveProviderToScope(String provider) throws SafeguardForJavaExc }); throw new SafeguardForJavaException(String.format("Unable to find scope matching '%s' in [%s]", provider, s.toString())); } - + return scope; } catch (SafeguardForJavaException ex) { @@ -239,7 +239,7 @@ protected void finalize() throws Throwable { super.finalize(); } } - + private class Provider { private String RstsProviderId; private String Name; @@ -251,28 +251,28 @@ public Provider(String RstsProviderId, String Name, String RstsProviderScope) { this.RstsProviderScope = RstsProviderScope; } } - + private List parseLoginResponse(String response) { - + List providers = new ArrayList<>(); ObjectMapper mapper = new ObjectMapper(); - + try { JsonNode jsonNodeProviders = mapper.readTree(response); Iterator iter = jsonNodeProviders.elements(); - + while(iter.hasNext()){ JsonNode providerNode=iter.next(); Provider p = new Provider(getJsonValue(providerNode, "RstsProviderId"), getJsonValue(providerNode, "Name"), getJsonValue(providerNode, "RstsProviderScope")); providers.add(p); - } + } } catch (IOException ex) { Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); } return providers; } - + private String getMatchingScope(String provider, List providers) { for (Provider s : providers) { if (s.Name.equalsIgnoreCase(provider) || s.RstsProviderId.equalsIgnoreCase(provider)) @@ -280,7 +280,7 @@ private String getMatchingScope(String provider, List providers) { } return null; } - + private String getJsonValue(JsonNode node, String propName) { if (node.get(propName) != null) { return node.get(propName).asText(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java index 17d03fc..7cb99cd 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java @@ -14,17 +14,17 @@ public class CertificateAuthenticator extends AuthenticatorBase private boolean disposed; private final CertificateContext clientCertificate; - + private String provider; - public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, + public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(certificateAlias, keystorePath, null, keystorePassword); } - - public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, + + public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); @@ -33,34 +33,34 @@ public CertificateAuthenticator(String networkAddress, String certificateThumbpr public CertificateAuthenticator(String networkAddress, String certificatePath, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(null, certificatePath, null, certificatePassword); } public CertificateAuthenticator(String networkAddress, byte[] certificateData, char[] certificatePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(certificateAlias, null, certificateData, certificatePassword); } - - private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, + + private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.clientCertificate = clientCertificate.cloneObject(); } - - public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, + + public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(certificateAlias, keystorePath, null, keystorePassword); } - - public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, + + public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); @@ -70,7 +70,7 @@ public CertificateAuthenticator(String networkAddress, String certificateThumbpr public CertificateAuthenticator(String networkAddress, String certificatePath, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(null, certificatePath, null, certificatePassword); @@ -78,25 +78,25 @@ public CertificateAuthenticator(String networkAddress, String certificatePath, c public CertificateAuthenticator(String networkAddress, byte[] certificateData, char[] certificatePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(certificateAlias, null, certificateData, certificatePassword); } - - private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, + + private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; this.clientCertificate = clientCertificate.cloneObject(); } - + @Override public String getId() { return "Certificate"; } - + @Override protected char[] getRstsTokenInternal() throws ObjectDisposedException, SafeguardForJavaException { @@ -110,40 +110,40 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar CloseableHttpResponse response = null; OauthBody body = new OauthBody("client_credentials", providerScope); - + response = rstsClient.execPOST("oauth2/token", null, null, null, body, clientCertificate); - + if (response == null) throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); - + String content = Utils.getResponse(response); if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { - String msg = Utils.isNullOrEmpty(clientCertificate.getCertificateAlias()) ? + String msg = Utils.isNullOrEmpty(clientCertificate.getCertificateAlias()) ? String.format("file=%s", clientCertificate.getCertificatePath()) : String.format("alias=%s", clientCertificate.getCertificateAlias()); - + throw new SafeguardForJavaException("Error using client_credentials grant_type with " + clientCertificate.toString() + String.format(", Error: %d %s", response.getStatusLine().getStatusCode(), content)); } - + Map map = Utils.parseResponse(content); - + if (!map.containsKey("access_token")) { throw new SafeguardForJavaException(String.format("Error retrieving the access token for certificate: %s", clientCertificate.getCertificatePath())); } - + return map.get("access_token").toCharArray(); } @Override public Object cloneObject() throws SafeguardForJavaException { - CertificateAuthenticator auth = new CertificateAuthenticator(this.getNetworkAddress(), clientCertificate, + CertificateAuthenticator auth = new CertificateAuthenticator(this.getNetworkAddress(), clientCertificate, this.getApiVersion(), this.isIgnoreSsl(), this.getValidationCallback()); if (this.accessToken != null) { auth.accessToken = this.accessToken.clone(); } return auth; } - + @Override public void dispose() { @@ -151,7 +151,7 @@ public void dispose() clientCertificate.dispose(); disposed = true; } - + @Override protected void finalize() throws Throwable { try { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java index 446849d..7a77ce6 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java @@ -7,7 +7,7 @@ public class ManagementServiceAuthenticator implements IAuthenticationMechanism { private boolean disposed; - + private final String networkAddress; private final int apiVersion; private final boolean ignoreSsl; @@ -25,12 +25,12 @@ public ManagementServiceAuthenticator(IAuthenticationMechanism parentAuthenticat public String getId() { return "Management"; } - + @Override public String getNetworkAddress() { return networkAddress; } - + @Override public int getApiVersion() { return apiVersion; @@ -50,12 +50,12 @@ public HostnameVerifier getValidationCallback() { public boolean isAnonymous() { return true; } - + @Override public boolean hasAccessToken() { return false; } - + @Override public void clearAccessToken() { // There is no access token for anonymous auth @@ -70,22 +70,22 @@ public char[] getAccessToken() throws ObjectDisposedException { public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, SafeguardForJavaException { return 0; } - + @Override public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous connection cannot be used to get an API access token, Error: Unsupported operation"); } - + @Override public String resolveProviderToScope(String provider) throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous connection does not require a provider, Error: Unsupported operation"); } - + @Override public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous authenticators are not cloneable"); } - + @Override public void dispose() { disposed = true; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java index 76d04db..23cc6f2 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java @@ -12,7 +12,7 @@ import javax.net.ssl.HostnameVerifier; import org.apache.http.client.methods.CloseableHttpResponse; -public class PasswordAuthenticator extends AuthenticatorBase +public class PasswordAuthenticator extends AuthenticatorBase { private boolean disposed; @@ -22,15 +22,15 @@ public class PasswordAuthenticator extends AuthenticatorBase private final char[] password; public PasswordAuthenticator(String networkAddress, String provider, String username, - char[] password, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) + char[] password, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; - + if (Utils.isNullOrEmpty(this.provider) || this.provider.equalsIgnoreCase("local")) providerScope = "rsts:sts:primaryproviderid:local"; - + this.username = username; if (password == null) throw new ArgumentException("The password parameter can not be null"); @@ -55,9 +55,9 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar if (response == null) throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) throw new SafeguardForJavaException(String.format("Error using password grant_type with scope %s, Error: ", providerScope) + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); @@ -65,7 +65,7 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar if (!map.containsKey("access_token")) throw new SafeguardForJavaException(String.format("Error retrieving the access key for scope: %s", providerScope)); - + return map.get("access_token").toCharArray(); } @@ -73,7 +73,7 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar public Object cloneObject() throws SafeguardForJavaException { try { - PasswordAuthenticator auth = new PasswordAuthenticator(getNetworkAddress(), provider, username, password, + PasswordAuthenticator auth = new PasswordAuthenticator(getNetworkAddress(), provider, username, password, getApiVersion(), isIgnoreSsl(), getValidationCallback()); auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); return auth; @@ -82,7 +82,7 @@ public Object cloneObject() throws SafeguardForJavaException } return null; } - + @Override public void dispose() { @@ -91,7 +91,7 @@ public void dispose() Arrays.fill(password, '0'); disposed = true; } - + @Override protected void finalize() throws Throwable { try { @@ -102,5 +102,5 @@ protected void finalize() throws Throwable { super.finalize(); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java index 81c572e..6c43b45 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class A2ARegistration { - + @JsonProperty("Id") private Integer id; @JsonProperty("AppName") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java index 9a03cba..e85c3c0 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java @@ -99,7 +99,7 @@ public String getAssetNetworkAddress() { public void setAssetNetworkAddress(String assetNetworkAddress) { this.assetNetworkAddress = assetNetworkAddress; } - + @Override public int getAccountId() { return accountId; @@ -135,7 +135,7 @@ public String getAccountType() { public void setAccountType(String accountType) { this.accountType = accountType; } - + @Override public String getAssetDescription() { return assetDescription; @@ -162,7 +162,7 @@ public void dispose() disposed = true; apiKey = null; } - + @Override protected void finalize() throws Throwable { try { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java index 375e66d..0728b72 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java @@ -62,7 +62,7 @@ public int getAssetId() { public void setSystemId(int systemId) { this.assetId = systemId; } - + public void setAssetId(int assetId) { this.assetId = assetId; } @@ -74,7 +74,7 @@ public String getAssetName() { public void setSystemName(String systemName) { this.assetName = systemName; } - + public void setAssetName(String assetName) { this.assetName = assetName; } @@ -118,7 +118,7 @@ public String getAssetDescription() { public void setSystemDescription(String systemDescription) { this.assetDescription = systemDescription; } - + public void setAssetDescription(String assetDescription) { this.assetDescription = assetDescription; } @@ -130,7 +130,7 @@ public String getAccountDescription() { public void setAccountDescription(String accountDescription) { this.accountDescription = accountDescription; } - + public String getAssetNetworkAddress() { return assetNetworkAddress; } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java index 43ddf4a..7f67859 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java @@ -3,7 +3,7 @@ import com.oneidentity.safeguard.safeguardjava.Utils; public class AccessTokenBody implements JsonObject { - + private final char[] stsAccessToken; public AccessTokenBody(char[] stsAccessToken ) { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java index fb0383d..3619579 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java @@ -10,7 +10,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class ApiKeySecret extends ApiKeySecretBase implements IApiKeySecret { - + @JsonProperty("ClientSecret") private char[] clientSecret; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java index 7303500..a9b4389 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) class ApiKeySecretBase { - + @JsonProperty("Id") private Integer id; @JsonProperty("Name") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java index f2de700..e0fbf57 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class ApiKeySecretInternal extends ApiKeySecretBase { - + @JsonProperty("ClientSecret") private String clientSecret; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java index 223ec68..fa598a7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java @@ -10,7 +10,7 @@ public class BrokeredAccessRequest implements JsonObject, IBrokeredAccessRequest { private int version; - + private BrokeredAccessRequestType AccessType; // converted by AccessRequestTypeConverter private String ForUserName; private String ForUserIdentityProvider; // renamed from ForProvider @@ -35,7 +35,7 @@ public class BrokeredAccessRequest implements JsonObject, IBrokeredAccessRequest public void setVersion(int apiVersion) { version = apiVersion; } - + /** * Get the type of access request to create. * @return BrokeredAccessRequestType @@ -353,7 +353,7 @@ public Long getRequestedDurationMinutes() { public void setRequestedDurationMinutes(Long RequestedDurationMinutes) { this.RequestedDurationMinutes = RequestedDurationMinutes; } - + @Override public String toJson() { return new StringBuffer("{") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java index 27109ee..3eceb73 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java @@ -17,17 +17,17 @@ public enum BrokeredAccessRequestType * Access request is for a remote desktop session. */ Rdp ("RemoteDesktop"); - + private final String name; - + private BrokeredAccessRequestType(String s) { name = s; } - + public boolean equalsName (String otherName) { return name.equals(otherName); } - + @Override public String toString() { return this.name; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java index fa59beb..c1c94c9 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java @@ -9,7 +9,7 @@ * A simple class for returning extended information from a Safeguard API method call. */ public class FullResponse { - + private int statusCode; private Header[] headers; private String body; @@ -43,5 +43,5 @@ public String getBody() { public void setBody(String body) { this.body = body; } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java index 94bedbc..42f6c58 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java @@ -15,7 +15,7 @@ public class JoinRequest implements JsonObject { public JoinRequest() { } - + public String getSpp() { return spp; } @@ -39,7 +39,7 @@ public String getSpp_cert_chain() { public void setSpp_cert_chain(String spp_cert_chain) { this.spp_cert_chain = spp_cert_chain; } - + @Override public String toJson() throws SafeguardForJavaException { ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); @@ -50,5 +50,5 @@ public String toJson() throws SafeguardForJavaException { throw new SafeguardForJavaException("Failed to convert request to json", ex); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java index cda59df..11a1bd0 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java @@ -1,13 +1,13 @@ package com.oneidentity.safeguard.safeguardjava.data; public class JsonBody implements JsonObject { - + private final String body; - + public JsonBody(String body) { this.body = body; } - + @Override public String toJson() { return body; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java index 26c11f7..db5f0a3 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java @@ -17,4 +17,4 @@ public enum KeyFormat * PuttY format for use with PuTTY tools */ Putty -} \ No newline at end of file +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java index 8afaf16..d00ce77 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java @@ -3,7 +3,7 @@ import com.oneidentity.safeguard.safeguardjava.Utils; public class OauthBody implements JsonObject { - + private String grantType; private String username; private char[] password; @@ -17,7 +17,7 @@ public OauthBody(String grantType, String username, char[] password, String scop this.scope = scope; this.isPassword = true; } - + public OauthBody(String grantType, String scope ) { this.grantType = grantType; this.username = null; @@ -26,7 +26,7 @@ public OauthBody(String grantType, String scope ) { this.isPassword = false; } - + public String getGrantType() { return grantType; } @@ -58,7 +58,7 @@ public String getScope() { public void setScope(String scope) { this.scope = scope; } - + @Override public String toJson() { if (isPassword) { @@ -75,5 +75,5 @@ public String toJson() { .append(Utils.toJsonString("scope", this.scope, true)) .append("}").toString(); } - } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java index a0317d2..484ccd7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java @@ -2,7 +2,7 @@ /** * Connection state of the Safeguard event listener. - */ + */ public enum SafeguardEventListenerState { /** diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java index 54e5424..431ef76 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java @@ -8,26 +8,26 @@ public enum Service { * The core service contains all general cluster-wide Safeguard operations. */ Core, - + /** * The appliance service contains appliance-specific Safeguard operations. */ Appliance, - + /** * The notification service contains unauthenticated Safeguard operations. */ Notification, - + /** * The a2a service contains application integration Safeguard operations. It is called via the Safeguard.A2A class. */ A2A, - + /** * The Management service contains unauthenticated endpoints for disaster-recovery and support operations. On hardware * it is bound to the MGMT network interface. For on-prem VM it is unavailable except through the Kiosk app. On cloud * VM it is listening on port 9337 and should be firewalled appropriately to restrict access. */ - Management + Management } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java index c855094..b532d3b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java @@ -8,7 +8,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class SshKey { - + @JsonProperty("Passphrase") private String passphrase; @JsonProperty("PrivateKey") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java index 6d909a1..97c3f28 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java @@ -19,7 +19,7 @@ public long getBytesTotal() { public void setBytesTotal(long BytesTotal) { this.BytesTotal = BytesTotal; } - + public int getPercentComplete() { return BytesTotal == 0 ? 0 : (int)((double)BytesTransferred / BytesTotal * 100); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java index efef237..09c2a64 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java @@ -14,12 +14,12 @@ public class EventHandlerRegistry { private static final Map> delegateRegistry = new HashMap<>(); private final Logger logger = Logger.getLogger(getClass().getName()); - + private void handleEvent(String eventName, JsonElement eventBody) { if (!delegateRegistry.containsKey(eventName)) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.FINEST, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.FINEST, String.format("No handlers registered for event %s", eventName)); return; } @@ -29,13 +29,13 @@ private void handleEvent(String eventName, JsonElement eventBody) List handlers = delegateRegistry.get(eventName); for (ISafeguardEventHandler handler : handlers) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.INFO, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.INFO, String.format("Calling handler for event %s", eventName)); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, String.format("Event %s has body %s", eventName, eventBody)); final EventHandlerRunnable handlerRunnable = new EventHandlerRunnable(handler, eventName, eventBody.toString()); final EventHandlerThread eventHandlerThread = new EventHandlerThread(handlerRunnable) { - + }; eventHandlerThread.start(); } @@ -53,7 +53,7 @@ private Map parseEvents(JsonElement eventObject) { try { Integer.parseInt(name); name = ((JsonObject)body).get("EventName").getAsString(); - } catch (Exception e) { + } catch (Exception e) { } } events.put(name, body); @@ -61,7 +61,7 @@ private Map parseEvents(JsonElement eventObject) { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, String.format("Unable to parse event object %s", eventObject.toString())); return null; } @@ -75,7 +75,7 @@ public void handleEvent(JsonElement eventObject) for (Map.Entry eventInfo : events.entrySet()) { if (eventInfo.getKey() == null) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, String.format("Found null event with body %s", eventInfo.getValue())); continue; } @@ -88,9 +88,9 @@ public void registerEventHandler(String eventName, ISafeguardEventHandler handle if (!delegateRegistry.containsKey(eventName)) { delegateRegistry.put(eventName, new ArrayList<>()); } - + delegateRegistry.get(eventName).add(handler); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, String.format("Registered a handler for event %s", eventName)); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java index 01644d8..fe63a6c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java @@ -8,7 +8,7 @@ class EventHandlerRunnable implements Runnable { private final ISafeguardEventHandler handler; private final String eventName; private final String eventBody; - + EventHandlerRunnable(ISafeguardEventHandler handler, String eventName, String eventBody) { this.handler = handler; this.eventName = eventName; @@ -23,7 +23,7 @@ public void run() { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, + Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, "An error occured while calling onEventReceived"); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java index 0e7e522..6cb0ba7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java @@ -1,9 +1,9 @@ package com.oneidentity.safeguard.safeguardjava.event; abstract class EventHandlerThread extends Thread { - + public EventHandlerThread(Runnable eventHandlerRunnable) { super(eventHandlerRunnable); } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java index c0a0153..79f3eb4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java @@ -3,11 +3,11 @@ /** * A callback that will be called when a given event occurs in Safeguard. The callback will * receive the event name and JSON data representing the event. - */ + */ public interface ISafeguardEventHandler { /** * Handles an incoming event - * + * * @param eventName Event name. * @param eventBody Event body. */ diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java index 9f64766..f81171c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java @@ -4,7 +4,7 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardEventListenerDisconnectedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -/** +/** * This is an event listener interface that will allow you to be notified each time something * changes on Safeguard. The events that you are notified for depend on the role and event * registrations of the authenticated user. Safeguard event listeners use SignalR to make @@ -15,11 +15,11 @@ public interface ISafeguardEventListener /** * Register an event handler to be called each time the specified event occurs. Multiple * handlers may be registered for each event. - * + * * @param eventName Name of the event. * @param handler Callback method. * @throws ObjectDisposedException Object has already been disposed - */ + */ void registerEventHandler(String eventName, ISafeguardEventHandler handler) throws ObjectDisposedException; /** @@ -27,9 +27,9 @@ public interface ISafeguardEventListener * state changes of the event listener. * * @param eventListenerStateCallback Callback method. - */ + */ void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback eventListenerStateCallback); - + /** * Start listening for Safeguard events in a background thread. * @throws ObjectDisposedException Object has already been disposed @@ -40,7 +40,7 @@ public interface ISafeguardEventListener /** * Stop listening for Safeguard events in a background thread. - * + * * @throws ObjectDisposedException Object has already been disposed * @throws SafeguardForJavaException General Safeguard for Java exception */ @@ -48,15 +48,15 @@ public interface ISafeguardEventListener /** * Indicates whether the SignalR connection has completed start up. - * + * * @return boolean flag */ boolean isStarted(); /** * Disposes of the connection. - * - */ + * + */ void dispose(); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java index e95d39b..8907626 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java @@ -4,11 +4,11 @@ /** * A callback that will be called whenever the event listener connection state Changes. - */ + */ public interface ISafeguardEventListenerStateCallback { /** * Handles an incoming event listener connection change. - * + * * @param eventListenerState New connection state of the event listener. */ void onEventListenerStateChange(SafeguardEventListenerState eventListenerState); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java index a9be20b..7c31bcf 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java @@ -39,7 +39,7 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, char Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); } - public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List apiKeys, ISafeguardEventHandler handler) + public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List apiKeys, ISafeguardEventHandler handler) throws ArgumentException, ObjectDisposedException { this.a2AContext = a2AContext; if (apiKeys == null) { @@ -58,7 +58,7 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List registerEventHandler("AccountApiKeySecretUpdated", handler); Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); } - + @Override public SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, ArgumentException { @@ -81,7 +81,7 @@ public void dispose() a2AContext.dispose(); disposed = true; } - + @Override protected void finalize() throws Throwable { try { @@ -97,5 +97,5 @@ protected void finalize() throws Throwable { super.finalize(); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java index ca094b6..6fdb5d7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java @@ -21,11 +21,11 @@ public PersistentSafeguardEventListener(ISafeguardConnection connection) { @Override public SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardEventListener"); } - + if (connection.getAccessTokenLifetimeRemaining() == 0) { connection.refreshAccessToken(); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java index e2cd6e8..504e470 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java @@ -34,7 +34,7 @@ public void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback e { this.eventListenerStateCallback = eventListenerStateCallback; } - + protected abstract SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; class PersistentReconnectAndStartHandler implements IDisconnectHandler { @@ -81,15 +81,15 @@ public void run() { } } }; - - + + try { this.reconnectThread.start(); this.reconnectThread.join(); } catch (InterruptedException ex1) { isCancellationRequested = true; } - + this.reconnectThread = null; } @@ -118,7 +118,7 @@ public void stop() throws ObjectDisposedException, SafeguardForJavaException { public boolean isStarted() { return this.eventListener == null ? false : this.eventListener.isStarted(); } - + @Override public void dispose() { if (this.eventListener != null) { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java index a5f158d..52b20f4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java @@ -83,7 +83,7 @@ public SafeguardEventListener(String eventUrl, char[] accessToken, boolean ignor this.accessToken = accessToken.clone(); } - public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, + public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, String certificateAlias, char[] apiKey, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKey == null) @@ -92,7 +92,7 @@ public SafeguardEventListener(String eventUrl, String clientCertificatePath, cha this.clientCertificate = new CertificateContext(certificateAlias, clientCertificatePath, null, certificatePassword); } - public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, + public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, char[] apiKey, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKey == null) @@ -101,7 +101,7 @@ public SafeguardEventListener(String eventUrl, CertificateContext clientCertific this.apiKey = apiKey.clone(); } - public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, + public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, String certificateAlias, List apiKeys, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKeys == null) @@ -115,7 +115,7 @@ public SafeguardEventListener(String eventUrl, String clientCertificatePath, cha throw new ArgumentException("The apiKeys parameter must include at least one item"); } - public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, + public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, List apiKeys, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); @@ -152,7 +152,7 @@ public void registerEventHandler(String eventName, ISafeguardEventHandler handle } eventHandlerRegistry.registerEventHandler(eventName, handler); } - + @Override public void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback eventListenerStateCallback) { @@ -265,7 +265,7 @@ private void handleDisconnect() throws SafeguardEventListenerDisconnectedExcepti CallEventListenerStateCallback(SafeguardEventListenerState.Disconnected); disconnectHandler.func(); } - + private void CallEventListenerStateCallback(SafeguardEventListenerState newState) { if (eventListenerStateCallback != null) { @@ -309,7 +309,7 @@ else if (apiKeys != null) { if (authKey.isEmpty()) throw new SafeguardForJavaException("No API keys found in the authorization header"); - builder.withHeader("Authorization", String.format("A2A %s", authKey)); + builder.withHeader("Authorization", String.format("A2A %s", authKey)); } builder.setHttpClientBuilderCallback(new Action1(){ @@ -330,13 +330,13 @@ private void ConfigureHttpClientBuilder(Builder builder) } KeyManager[] km = null; - if(clientCertificate != null && + if(clientCertificate != null && (clientCertificate.getCertificateData() != null || clientCertificate.getCertificatePath() != null)){ - + // If we have a client certificate, set it into the KeyStore/KeyManager try{ KeyStore keyStore = KeyStore.getInstance("PKCS12"); - InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) + InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) : new ByteArrayInputStream(clientCertificate.getCertificateData()); keyStore.load(inputStream, clientCertificate.getCertificatePassword()); @@ -346,7 +346,7 @@ private void ConfigureHttpClientBuilder(Builder builder) km = keyManagerFactory.getKeyManagers(); // when we send a client certificate singlar resets the stream - // and requires 1.1. No idea why. okhttp3 doesn't handle this reset + // and requires 1.1. No idea why. okhttp3 doesn't handle this reset // automatically so the only option is to restrict the client to 1.1 builder.protocols(Arrays.asList(Protocol.HTTP_1_1)); } @@ -373,14 +373,14 @@ private void ConfigureHttpClientBuilder(Builder builder) if (tm.length != 1 || !(tm[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(tm)); } - x509tm = (X509TrustManager) tm[0]; + x509tm = (X509TrustManager) tm[0]; } - // Configure the SSL Context according to options and set the + // Configure the SSL Context according to options and set the // OkHttpClient builder SSL socket factory SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(km, tm, null); - builder.sslSocketFactory(sslContext.getSocketFactory(), x509tm); + builder.sslSocketFactory(sslContext.getSocketFactory(), x509tm); } catch(NoSuchAlgorithmException ex) { Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java index c9ab038..f343d51 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java @@ -5,7 +5,7 @@ public class ArgumentException extends Exception { public ArgumentException(String msg) { super(msg); } - + public ArgumentException(String msg, Exception cause) { super(msg, cause); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java index 81c6ef4..558917b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java @@ -5,7 +5,7 @@ public class SafeguardForJavaException extends Exception { public SafeguardForJavaException(String msg) { super(msg); } - + public SafeguardForJavaException(String msg, Throwable cause) { super(msg, cause); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java index e9ee59a..a21082b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java @@ -5,7 +5,7 @@ import java.io.OutputStream; public class ByteArrayEntity extends org.apache.http.entity.ByteArrayEntity { - + private OutputStreamProgress outstream; private final IProgressCallback progressCallback; private final long totalBytes; @@ -32,5 +32,5 @@ public int getProgress() { } long writtenLength = outstream.getWrittenLength(); return (int) (100*writtenLength/contentLength); - } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java index ebb0989..fa22106 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java @@ -27,7 +27,7 @@ private void sendProgress() { progressCallback.checkProgress(transferProgress); } } - + @Override public void write(int b) throws IOException { outstream.write(b); @@ -59,7 +59,7 @@ public void write(byte[] b, int off, int len) throws IOException { public void flush() throws IOException { outstream.flush(); } - + @Override public void close() throws IOException { outstream.close(); @@ -68,4 +68,4 @@ public void close() throws IOException { public long getWrittenLength() { return bytesWritten; } -} \ No newline at end of file +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index d182af5..117619d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -88,14 +88,14 @@ public RestClient(String connectionAddr, boolean ignoreSsl, HostnameVerifier val } public RestClient(String connectionAddr, String userName, char[] password, boolean ignoreSsl, HostnameVerifier validationCallback) { - + HttpClientBuilder builder = createClientBuilder(connectionAddr, ignoreSsl, validationCallback); CredentialsProvider provider = new BasicCredentialsProvider(); provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, new String(password))); - + RequestConfig customizedRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); - + client = builder.setDefaultCredentialsProvider(provider) .setDefaultRequestConfig(customizedRequestConfig) .setDefaultCookieStore(cookieStore) @@ -115,7 +115,7 @@ private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ign this.ignoreSsl = ignoreSsl; this.serverUrl = connectionAddr; - + try { URL aUrl = new URL(connectionAddr); this.hostDomain = aUrl.getHost(); @@ -123,22 +123,22 @@ private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ign Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Invalid URL", ex); } - SSLConnectionSocketFactory sslsf = null; + SSLConnectionSocketFactory sslsf = null; if (ignoreSsl) { this.validationCallback = null; sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), NoopHostnameVerifier.INSTANCE); } else if (validationCallback != null) { this.validationCallback = validationCallback; - sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), validationCallback); + sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), validationCallback); } else { sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null)); } Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - + return HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connectionManager); } - + private URI getBaseURI(String segments) { try { return new URI(serverUrl+"/"+segments); @@ -153,7 +153,7 @@ public String getBaseURL() { } private Map parseKeyValue(String value) { - + HashMap keyValues = new HashMap<>(); String[] parts = value.split(";"); for (String p : parts) { @@ -165,16 +165,16 @@ private Map parseKeyValue(String value) { keyValues.put(kv[0].trim(), kv[1].trim()); } } - + return keyValues; } - + public void addSessionId(String cookieValue) { if (cookieValue == null) { Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Session cookie cannot be null"); return; } - + try { Map keyValues = parseKeyValue(cookieValue); @@ -197,7 +197,7 @@ public void addSessionId(String cookieValue) { if (this.hostDomain != null) { cookie.setDomain(this.hostDomain); } - + String secure = keyValues.get("Secure"); if (secure != null) { cookie.setSecure(true); @@ -210,7 +210,7 @@ public void addSessionId(String cookieValue) { Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Failed to set session cookie.", ex); } } - + public CloseableHttpResponse execGET(String path, Map queryParams, Map headers, Integer timeout) { RequestBuilder rb = prepareRequest (RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); @@ -239,8 +239,8 @@ public CloseableHttpResponse execGET(String path, Map queryParam } return null; } - - public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, + + public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, Integer timeout, IProgressCallback progressCallback) { if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) { @@ -256,12 +256,12 @@ public CloseableHttpResponse execGETBytes(String path, Map query return null; } } - - public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, + + public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, Integer timeout, CertificateContext certificateContext, IProgressCallback progressCallback) { CloseableHttpClient certClient = getClientWithCertificate(certificateContext); - + if (certClient != null) { if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) { headers = headers == null ? new HashMap<>() : headers; @@ -293,7 +293,7 @@ public CloseableHttpResponse execPUT(String path, Map queryParam } } - public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, + public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity, CertificateContext certificateContext) { CloseableHttpClient certClient = getClientWithCertificate(certificateContext); @@ -311,7 +311,7 @@ public CloseableHttpResponse execPUT(String path, Map queryParam } return null; } - + public CloseableHttpResponse execPOST(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity) { RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); @@ -346,8 +346,8 @@ public CloseableHttpResponse execPOST(String path, Map queryPara return null; } - - public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, + + public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, byte[] requestEntity, IProgressCallback progressCallback) { if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { @@ -364,7 +364,7 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer return null; } } - + public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, byte[] requestEntity, CertificateContext certificateContext, IProgressCallback progressCallback) { @@ -388,11 +388,11 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer return null; } - public CloseableHttpResponse execPOSTFile(String path, Map queryParams, Map queryParams, Map headers, Integer timeout, String fileName) { File file = new File(fileName); - + HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); @@ -409,7 +409,7 @@ public CloseableHttpResponse execPOSTFile(String path, Map query return null; } } - + public CloseableHttpResponse execPOSTFile(String path, Map queryParams, Map headers, Integer timeout, String fileName, CertificateContext certificateContext) { @@ -419,7 +419,7 @@ public CloseableHttpResponse execPOSTFile(String path, Map query File file = new File(fileName); HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); - + if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; } @@ -435,7 +435,7 @@ public CloseableHttpResponse execPOSTFile(String path, Map query } return null; } - + public CloseableHttpResponse execDELETE(String path, Map queryParams, Map headers, Integer timeout) { RequestBuilder rb = prepareRequest(RequestBuilder.delete(getBaseURI(path)), queryParams, headers, timeout); @@ -451,8 +451,8 @@ public CloseableHttpResponse execDELETE(String path, Map queryPa private CloseableHttpClient getClientWithCertificate(CertificateContext certificateContext) { CloseableHttpClient certClient = null; - if (certificateContext.getCertificatePath() != null - || certificateContext.getCertificateData() != null + if (certificateContext.getCertificatePath() != null + || certificateContext.getCertificateData() != null || certificateContext.getCertificateThumbprint() != null) { InputStream in; @@ -467,7 +467,7 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific aliases = new ArrayList<>(); aliases = Collections.list(clientKs.aliases()); } else { - in = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) + in = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) : new ByteArrayInputStream(certificateContext.getCertificateData()); try { clientKs = KeyStore.getInstance("JKS"); @@ -485,11 +485,11 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, null, ex); } - SSLConnectionSocketFactory sslsf = null; + SSLConnectionSocketFactory sslsf = null; if (ignoreSsl) { sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), NoopHostnameVerifier.INSTANCE); } else if (validationCallback != null) { - sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), validationCallback); + sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), validationCallback); } else { sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext)); } @@ -502,12 +502,12 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific } private RequestBuilder prepareRequest(RequestBuilder rb, Map queryParams, Map headers, Integer timeout) { - + if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) rb.addHeader(HttpHeaders.ACCEPT, "application/json"); if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) rb.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - + if (headers != null) { headers.entrySet().forEach((entry) -> { rb.addHeader(entry.getKey(), entry.getValue()); @@ -615,5 +615,5 @@ public PrivateKey getPrivateKey(String string) { return defaultKeyManager.getPrivateKey(string); } } - + } diff --git a/tests/safeguardjavaclient/pom.xml b/tests/safeguardjavaclient/pom.xml index 3a8fb9b..7efd4db 100644 --- a/tests/safeguardjavaclient/pom.xml +++ b/tests/safeguardjavaclient/pom.xml @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java index 82416f6..14672aa 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java @@ -6,12 +6,12 @@ public class CertificateValidator implements HostnameVerifier { public static final CertificateValidator INSTANCE = new CertificateValidator(); - + @Override public boolean verify(final String s, final SSLSession sslSession) { return true; } - + @Override public final String toString() { return "CertificateValidator"; diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java index 4024d67..8f0c961 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java @@ -9,5 +9,5 @@ public class ProgressNotification implements IProgressCallback { public void checkProgress(TransferProgress transferProgress) { System.out.println(String.format("\tBytes transfered %d done", transferProgress.getPercentComplete())); } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index ac31a92..f1aef50 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -24,14 +24,14 @@ public static void main(String[] args) { ISafeguardA2AContext a2aContext = null; ISafeguardEventListener eventListener = null; ISafeguardEventListener a2aEventListener = null; - + boolean done = false; SafeguardTests tests = new SafeguardTests(); - + // Uncomment the lines below to enable console debug logging of the http requests //System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog"); //System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); - //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG"); + //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG"); //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.impl.conn", "DEBUG"); //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.impl.client", "DEBUG"); @@ -160,12 +160,12 @@ public static void main(String[] args) { System.out.println("/tException Stack: "); ex.printStackTrace(); } - + System.out.println("All done."); } private static Integer displayMenu() { - + System.out.println("Select an option:"); System.out.println ("\t1. Connect by user/password"); System.out.println ("\t2. Connect by thumbprint"); @@ -200,16 +200,16 @@ private static Integer displayMenu() { System.out.println ("\t31. Test Join SPS"); System.out.println ("\t32. Test Management Interface API"); System.out.println ("\t33. Test Anonymous Connection"); - + System.out.println ("\t99. Exit"); - + System.out.print ("Selection: "); Scanner in = new Scanner(System.in); int selection = in.nextInt(); - + return selection; } - + private static CommandLine parseArguments(String[] args) { Options options = getOptions(); @@ -256,7 +256,7 @@ public static String toJsonString(String name, Object value, boolean prependSep) } public static String readLine(String format, String defaultStr, Object... args) { - + String value = defaultStr; format += "["+defaultStr+"] "; try { @@ -271,17 +271,17 @@ public static String readLine(String format, String defaultStr, Object... args) System.out.println(ex.getMessage()); return defaultStr; } - + if (value.trim().length() == 0) return defaultStr; return value.trim(); } - - - - - + + + + + } //class EventHandler implements ISafeguardEventHandler { diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java index 738b07d..292547d 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java @@ -48,7 +48,7 @@ public class SafeguardTests { public SafeguardTests() { } - + private void logResponseDetails(FullResponse fullResponse) { System.out.println(String.format("\t\tReponse status code: %d", fullResponse.getStatusCode())); @@ -59,16 +59,16 @@ private void logResponseDetails(FullResponse fullResponse) } public ISafeguardConnection safeguardConnectByUserPassword() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String user = readLine("User:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardConnection connection = null; - + try { if (withCertValidator) { connection = Safeguard.connect(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -80,12 +80,12 @@ public ISafeguardConnection safeguardConnectByUserPassword() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectByThumbprint() { ISafeguardConnection connection = null; @@ -93,7 +93,7 @@ public ISafeguardConnection safeguardConnectByThumbprint() { String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "y").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, thumbprint, new CertificateValidator(), null); @@ -105,7 +105,7 @@ public ISafeguardConnection safeguardConnectByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -113,14 +113,14 @@ public ISafeguardConnection safeguardConnectByThumbprint() { public ISafeguardConnection safeguardConnectByCertificate() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, certificatePath, password.toCharArray(), new CertificateValidator(), provider, null); @@ -132,7 +132,7 @@ public ISafeguardConnection safeguardConnectByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -140,12 +140,12 @@ public ISafeguardConnection safeguardConnectByCertificate() { public ISafeguardConnection safeguardConnectByToken() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String token = readLine("Token:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, token.toCharArray(), new CertificateValidator(), null); @@ -157,32 +157,32 @@ public ISafeguardConnection safeguardConnectByToken() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectAnonymous() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { connection = Safeguard.connect(address, null, ignoreSsl); } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectByKeystore() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", null); String keystorePath = readLine("Keystore Path:", null); @@ -190,7 +190,7 @@ public ISafeguardConnection safeguardConnectByKeystore() { String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), provider, null); @@ -202,42 +202,42 @@ public ISafeguardConnection safeguardConnectByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public void safeguardTestConnection(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } - + try { char[] token = connection.getAccessToken(); System.out.println(String.format("\tAccess Token: %s", new String(token))); - + int remaining = connection.getAccessTokenLifetimeRemaining(); System.out.println(String.format("\tTime remaining: %d", remaining)); - + String response = connection.invokeMethod(Service.Core, Method.Get, "Users", null, null, null, null); System.out.println(String.format("\t\\Users response:")); System.out.println(response); - + FullResponse fullResponse = connection.invokeMethodFull(Service.Core, Method.Get, "Users", null, null, null, null); System.out.println(String.format("\t\\Users full response:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Core, Method.Post, "Events/FireTestEvent", null, null, null, null); System.out.println(String.format("\t\\FireTestEvent response:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Notification, Method.Get, "Status", null, null, null, null); System.out.println(String.format("\t\\Appliance status:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Appliance, Method.Get, "NetworkInterfaces", null, null, null, null); System.out.println(String.format("\t\\NetworkInterfaces response:")); logResponseDetails(fullResponse); @@ -246,7 +246,7 @@ public void safeguardTestConnection(ISafeguardConnection connection) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + public ISafeguardConnection safeguardDisconnect(ISafeguardConnection connection) { if (connection != null) { connection.dispose(); @@ -256,19 +256,19 @@ public ISafeguardConnection safeguardDisconnect(ISafeguardConnection connection) public ISafeguardA2AContext safeguardGetA2AContextByCertificate() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + return safeguardGetA2AContextByCertificate(address, certificatePath, password, withCertValidator, ignoreSsl); } - + private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, String certificatePath, String password, boolean withCertValidator, boolean ignoreSsl) { ISafeguardA2AContext a2aContext = null; - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, certificatePath, password.toCharArray(), new CertificateValidator(), null); @@ -278,7 +278,7 @@ private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; @@ -286,14 +286,14 @@ private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, public ISafeguardA2AContext safeguardGetA2AContextByKeystore() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String keystorePath = readLine("Keystore Path:", null); String password = readLine("Password: ", null); String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), null); @@ -303,14 +303,14 @@ public ISafeguardA2AContext safeguardGetA2AContextByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; } public ISafeguardA2AContext safeguardGetA2AContextByThumbprint() { - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "y").equalsIgnoreCase("y"); @@ -318,10 +318,10 @@ public ISafeguardA2AContext safeguardGetA2AContextByThumbprint() { return safeguardGetA2AContextByThumbprint(address, thumbprint, withCertValidator, ignoreSsl); } - + private ISafeguardA2AContext safeguardGetA2AContextByThumbprint(String address, String thumbprint, boolean withCertValidator, boolean ignoreSsl) { ISafeguardA2AContext a2aContext = null; - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, thumbprint, new CertificateValidator(), null); @@ -331,19 +331,19 @@ private ISafeguardA2AContext safeguardGetA2AContextByThumbprint(String address, } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; } - + private byte[] readAllBytes(InputStream in) throws IOException { ByteArrayOutputStream baos= new ByteArrayOutputStream(); byte[] buf = new byte[1024]; for (int read=0; read != -1; read = in.read(buf)) { baos.write(buf, 0, read); } return baos.toByteArray(); } - + private String formatPEM(String resource) throws IOException { InputStream in = new ByteArrayInputStream(resource.getBytes()); String pem = new String(readAllBytes(in), StandardCharsets.ISO_8859_1); @@ -351,14 +351,14 @@ private String formatPEM(String resource) throws IOException { String encoded = parse.matcher(pem).replaceFirst("$1"); return encoded.replace("\r", "").replace("\n", ""); } - + public void safeguardTestA2AContext(ISafeguardA2AContext a2aContext) { - + if (a2aContext == null) { System.out.println(String.format("Missing Safeguard A2A context.")); return; } - + if (readLine("Test Credential Retrieval(y/n): ", "y").equalsIgnoreCase("y")) { String typeOfRelease = readLine("Password, Private Key or API Key Secret (p/k/a): ", "p"); String apiKey = readLine("API Key: ", null); @@ -401,11 +401,11 @@ else if (typeOfRelease.equalsIgnoreCase("a")) { if (typeOfRelease.equalsIgnoreCase("p")) { String newPassword = readLine("New Password: ", ""); a2aContext.SetPassword(apiKey.toCharArray(), newPassword.toCharArray()); - + String password = new String(a2aContext.retrievePassword(apiKey.toCharArray())); if (password.compareTo(newPassword) == 0) System.out.println(String.format("\tSuccessfully set password")); - else + else System.out.println(String.format("\tFailed to set password")); } else if (typeOfRelease.equalsIgnoreCase("k")) { @@ -415,15 +415,15 @@ else if (typeOfRelease.equalsIgnoreCase("k")) { String privateKey = new String(Files.readAllBytes(filePath)); a2aContext.SetPrivateKey(apiKey.toCharArray(), privateKey.toCharArray(), privateKeyPassword.toCharArray(), KeyFormat.OpenSsh); - + String key = new String(a2aContext.retrievePrivateKey(apiKey.toCharArray(), KeyFormat.OpenSsh)); - + String privkey1 = formatPEM(privateKey); String privkey2 = formatPEM(key); - + if (privkey1.compareTo(privkey2) == 0) System.out.println(String.format("\tSuccessful private key release")); - else + else System.out.println(String.format("\tFailed to set private key")); } else { @@ -434,32 +434,32 @@ else if (typeOfRelease.equalsIgnoreCase("k")) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + if (readLine("Test Access Request Broker(y/n): ", "y").equalsIgnoreCase("y")) { try { List registrations = a2aContext.getRetrievableAccounts(); System.out.println(String.format("\tRetrievable accounts:")); for (IA2ARetrievableAccount reg : registrations) { - System.out.println(String.format("\t\tAssetId: %d AssetName: %s AccountId: %d AccountName: %s AccountDescription: %s", + System.out.println(String.format("\t\tAssetId: %d AssetName: %s AccountId: %d AccountName: %s AccountDescription: %s", reg.getAssetId(), reg.getAssetName(), reg.getAccountId(), reg.getAccountName(), reg.getAccountDescription())); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Failed to get the retrievable accounts: " + ex.getMessage()); } - + String accountId = readLine("Account Id: ", null); String assetId = readLine("Asset Id:", null); String forUserId = readLine("For User Id:", null); String accessRequestType = readLine("Access Request Type((p)assword/(s)sh/(r)dp): ", "p"); String apiKey = readLine("Api Key: ", null); - + try { IBrokeredAccessRequest accessRequest = new BrokeredAccessRequest(); accessRequest.setAccountId(Integer.parseInt(accountId)); accessRequest.setForUserId(Integer.parseInt(forUserId)); accessRequest.setAssetId(Integer.parseInt(assetId)); - accessRequest.setAccessType(accessRequestType.toLowerCase().equals("p") ? BrokeredAccessRequestType.Password - : accessRequestType.toLowerCase().equals("s") ? BrokeredAccessRequestType.Ssh + accessRequest.setAccessType(accessRequestType.toLowerCase().equals("p") ? BrokeredAccessRequestType.Password + : accessRequestType.toLowerCase().equals("s") ? BrokeredAccessRequestType.Ssh : BrokeredAccessRequestType.Rdp); String result = a2aContext.brokerAccessRequest(apiKey.toCharArray(), accessRequest); @@ -489,9 +489,9 @@ private List ReadAllApiKeys(ISafeguardA2AContext context) throws ObjectD return apiKeys; } - + public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); @@ -501,20 +501,20 @@ public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { ISafeguardA2AContext a2aContext = safeguardGetA2AContextByCertificate(address, certificatePath, password, withCertValidator, ignoreSsl); ISafeguardEventListener eventListener = null; - + try { List apiKeys = ReadAllApiKeys(a2aContext); - ISafeguardEventHandler a2aHandler = + ISafeguardEventHandler a2aHandler = (String eventName, String eventBody) -> { System.out.println(String.format("\tEvent body for %s event", eventName)); System.out.println(String.format("\t\t%s", eventBody)); }; if (withCertValidator) { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, certificatePath, password.toCharArray(), new CertificateValidator(), null); } else { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, certificatePath, password.toCharArray(), null, ignoreSsl); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -522,14 +522,14 @@ public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); @@ -539,20 +539,20 @@ public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { ISafeguardA2AContext a2aContext = safeguardGetA2AContextByThumbprint(address, thumbprint, withCertValidator, ignoreSsl); ISafeguardEventListener eventListener = null; - + try { List apiKeys = ReadAllApiKeys(a2aContext); - ISafeguardEventHandler a2aHandler = + ISafeguardEventHandler a2aHandler = (String eventName, String eventBody) -> { System.out.println(String.format("\tEvent body for %s event", eventName)); System.out.println(String.format("\t\t%s", eventBody)); }; if (withCertValidator) { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, thumbprint, new CertificateValidator(), null); } else { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, thumbprint, null, ignoreSsl); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -560,23 +560,23 @@ public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardEventListenerByUserPassword() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String user = readLine("User:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -588,14 +588,14 @@ public ISafeguardEventListener safeguardEventListenerByUserPassword() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardEventListenerByCertificate() { - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); @@ -603,7 +603,7 @@ public ISafeguardEventListener safeguardEventListenerByCertificate() { boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, certificatePath, password.toCharArray(), new CertificateValidator(), null); @@ -615,14 +615,14 @@ public ISafeguardEventListener safeguardEventListenerByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } public ISafeguardEventListener safeguardEventListenerByKeystore() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String keystorePath = readLine("Keystore Path:", null); @@ -630,9 +630,9 @@ public ISafeguardEventListener safeguardEventListenerByKeystore() { String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), provider, null); @@ -644,22 +644,22 @@ public ISafeguardEventListener safeguardEventListenerByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + ISafeguardEventListener safeguardEventListenerByThumbprint() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, thumbprint, new CertificateValidator(), null); @@ -671,21 +671,21 @@ ISafeguardEventListener safeguardEventListenerByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } public void safeguardTestEventListener(ISafeguardEventListener eventListener) { - + if (eventListener == null) { System.out.println(String.format("\t[ERROR]Missing event listener")); return; } - + String e = readLine("Comma delimited events: ", "UserCreated,UserDeleted,TestConnectionFailed,TestConnectionStarted,TestConnectionSucceeded"); - + try { String[] events = e.split(","); if (events.length == 0) { @@ -701,7 +701,7 @@ public void onEventReceived(String eventName, String eventBody) { } }); } - + System.out.print("\tStarting the event listener"); eventListener.start(); readLine("Press enter to stop...", null); @@ -714,18 +714,18 @@ public void onEventReceived(String eventName, String eventBody) { } public void safeguardTestA2AEventListener(ISafeguardEventListener eventListener) { - + if (eventListener == null) { System.out.println(String.format("\t[ERROR]Missing event listener")); return; } - + try { eventListener.SetEventListenerStateCallback((SafeguardEventListenerState eventListenerState) -> { System.out.println(String.format("\tGot a SignalR connection state change: %s", eventListenerState.toString())); }); - + System.out.print("\tStarting the event listener"); eventListener.start(); readLine("Press enter to stop...", null); @@ -736,7 +736,7 @@ public void safeguardTestA2AEventListener(ISafeguardEventListener eventListener) System.out.println("\t[ERROR]Test event listener failed: " + ex.getMessage()); } } - + public ISafeguardEventListener safeguardDisconnectEventListener(ISafeguardEventListener eventListener) { if (eventListener != null) { eventListener.dispose(); @@ -745,7 +745,7 @@ public ISafeguardEventListener safeguardDisconnectEventListener(ISafeguardEventL } public void safeguardTestBackupDownload(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -754,12 +754,12 @@ public void safeguardTestBackupDownload(ISafeguardConnection connection) { String backupId = readLine("Backup Id: ", null); String backupFileName = readLine("Backup File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (backupId == null || backupFileName == null) { System.out.println(String.format("Missing id or file name")); return; } - + try { String filePath = Paths.get(".", backupFileName).toAbsolutePath().toString(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; @@ -774,7 +774,7 @@ public void safeguardTestBackupDownload(ISafeguardConnection connection) { } public void safeguardListBackups(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -782,7 +782,7 @@ public void safeguardListBackups(ISafeguardConnection connection) { try { String response = connection.invokeMethod(Service.Appliance, Method.Get, "Backups", null, null, null, null); - + SafeguardBackup[] backups = new Gson().fromJson(response, SafeguardBackup[].class); System.out.println(String.format("\t\\Backups response:")); @@ -793,9 +793,9 @@ public void safeguardListBackups(ISafeguardConnection connection) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + public void safeguardTestBackupUpload(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -803,12 +803,12 @@ public void safeguardTestBackupUpload(ISafeguardConnection connection) { String backupFileName = readLine("Backup File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (backupFileName == null) { System.out.println(String.format("Missing id or file name")); return; } - + try { Path filePath = Paths.get(".", backupFileName).toAbsolutePath(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; @@ -829,9 +829,9 @@ ISafeguardSessionsConnection safeguardSessionsConnection() { String password = readLine("Password: ", null); // boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardSessionsConnection connection = null; - + try { // if (withCertValidator) { // connection = Safeguard.connect(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -843,7 +843,7 @@ ISafeguardSessionsConnection safeguardSessionsConnection() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -854,35 +854,35 @@ void safeguardSessionsApi(ISafeguardSessionsConnection connection) { System.out.println(String.format("Safeguard sessions not connected")); return; } - + try { FullResponse fullResponse = connection.invokeMethodFull(Method.Get, "configuration/network/naming", null); System.out.println(String.format("\t\\Network Naming full response:")); logResponseDetails(fullResponse); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } public void safeguardSessionsFileUpload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println("Safeguard not connected"); return; } String patchFileName = readLine("SPS Firmware File Name: ", null); - + if (patchFileName == null) { System.out.println("Missing file name"); return; } - + try { Path filePath = Paths.get(patchFileName).toAbsolutePath(); System.out.println(String.format("\tFile path: %s", filePath.toAbsolutePath())); - + connection.getStreamingRequest().uploadStream("upload/firmware", filePath.toString(), null, null); System.out.println(String.format("\tUploaded file: %s", patchFileName)); } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -893,7 +893,7 @@ public void safeguardSessionsFileUpload(ISafeguardSessionsConnection connection) } public void safeguardSessionsStreamUpload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard Sessions not connected")); return; @@ -901,18 +901,18 @@ public void safeguardSessionsStreamUpload(ISafeguardSessionsConnection connectio String patchFileName = readLine("SPS Firmware File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (patchFileName == null) { System.out.println(String.format("file name")); return; } - + try { Path filePath = Paths.get(patchFileName).toAbsolutePath(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; byte[] fileContent = Files.readAllBytes(filePath); System.out.println(String.format("\tFile path: %s", filePath.toAbsolutePath())); - + connection.getStreamingRequest().uploadStream("upload/firmware", fileContent, progressCallback, null, null); System.out.println(String.format("\tUploaded file: %s", patchFileName)); } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -927,34 +927,34 @@ private String[] safeguardSessionsGetRecordings(ISafeguardSessionsConnection con FullResponse fullResponse = connection.invokeMethodFull(Method.Get, "audit/sessions", null); System.out.println(String.format("\t\\Session Id's full response:")); logResponseDetails(fullResponse); - + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); SessionRecordings sessionIds = mapper.readValue(fullResponse.getBody(), SessionRecordings.class); return sessionIds.toArray(); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Get session recordings failed: " + ex.getMessage()); } catch (JsonProcessingException ex) { System.out.println("JSON deserialization failed: " + ex.getMessage()); } - + return null; } public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } String[] sessions = safeguardSessionsGetRecordings(connection); - + if (sessions == null) { System.out.println(String.format("Failed to get the session id's")); return; } - + for (int x = 0; x < sessions.length; x++) { System.out.println(String.format("\t%d. %s", x, sessions[x])); } @@ -965,14 +965,14 @@ public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection c System.out.println(String.format("Invalid session selection")); return; } - + String sessionId = sessions[sessionSelection]; String recordingFileName = sessionId + ".zat"; boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); String filePath = Paths.get(".", recordingFileName).toAbsolutePath().toString(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; - + try { System.out.println(String.format("\tSession recording file path: %s", filePath)); connection.getStreamingRequest().downloadStream(String.format("audit/sessions/%s/audit_trail", sessionId), filePath, progressCallback, null, null); @@ -983,7 +983,7 @@ public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection c System.out.println("\t[ERROR]Test backup download failed: " + ex.getMessage()); } } - + public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardSessionsConnection spsConnection) { if (sppConnection == null) { System.out.println(String.format("Safeguard SPP not connected")); @@ -993,21 +993,21 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS System.out.println(String.format("Safeguard SPS not connected")); return; } - + SafeguardSslCertificate[] sslCerts = null; SafeguardApplianceStatus applianceStatus = null; try { FullResponse fullResponse = sppConnection.invokeMethodFull(Service.Core, Method.Get, "SslCertificates", null, null, null, null); System.out.println(String.format("\t\\SslCertificates full response:")); logResponseDetails(fullResponse); - + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); sslCerts = mapper.readValue(fullResponse.getBody(), SafeguardSslCertificate[].class); fullResponse = sppConnection.invokeMethodFull(Service.Appliance, Method.Get, "ApplianceStatus", null, null, null, null); System.out.println(String.format("\t\\ApplianceStatus full response:")); logResponseDetails(fullResponse); - + applianceStatus = mapper.readValue(fullResponse.getBody(), SafeguardApplianceStatus.class); } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { @@ -1020,7 +1020,7 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS System.out.println("Test Join Sps failed: failed to get the Safeguard appliance information"); return; } - + String certChain = null; String sppAddress = null; for (SafeguardSslCertificate cert : sslCerts) { @@ -1034,7 +1034,7 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS } } } - + try { sppConnection.JoinSps(spsConnection, certChain, sppAddress); } catch (ObjectDisposedException | SafeguardForJavaException | ArgumentException ex) { @@ -1047,9 +1047,9 @@ void safeguardTestManagementConnection(ISafeguardConnection connection) { System.out.println(String.format("Safeguard not connected. This test requires an annonymous connection.")); return; } - + String address = readLine("SPP address(management service): ", null); - + try { ISafeguardConnection managementConnection = connection.GetManagementServiceConnection(address); FullResponse response = managementConnection.invokeMethodFull(Service.Management, Method.Get, "ApplianceInformation", null, null, null, null); @@ -1058,24 +1058,24 @@ void safeguardTestManagementConnection(ISafeguardConnection connection) { } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test management connection failed: " + ex.getMessage()); } - + } - + public void safeguardTestAnonymousConnection(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } - + try { int remaining = connection.getAccessTokenLifetimeRemaining(); System.out.println(String.format("\tTime remaining: %d", remaining)); - + FullResponse fullResponse = connection.invokeMethodFull(Service.Notification, Method.Get, "Status", null, null, null, null); System.out.println(String.format("\t\\Appliance status:")); logResponseDetails(fullResponse); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java index 1c966e3..a8d4e77 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java @@ -43,5 +43,5 @@ public String getIpv6Address() { public void setIpv6Address(String Ipv6Address) { this.Ipv6Address = Ipv6Address; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java index c33e0e6..3ef187a 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java @@ -23,5 +23,5 @@ public String getName() { public void setName(String Name) { this.Name = Name; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java index bc8ad61..5e7f745 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java @@ -43,6 +43,6 @@ public SafeguardAppliance[] getAppliances() { public void setAppliances(SafeguardAppliance[] Appliances) { this.Appliances = Appliances; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java index 6ed9b53..4e2a4b8 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java @@ -11,10 +11,10 @@ class SessionRecording { } public class SessionRecordings { - + @JsonProperty("items") public SessionRecording[] items; - + public String[] toArray() { List sessionIds = new ArrayList<>(); for(SessionRecording sr : items) { @@ -22,5 +22,5 @@ public String[] toArray() { } return sessionIds.toArray(new String[sessionIds.size()]); } - -} \ No newline at end of file + +} From 9fd42a001d87f0b01504fe1426f9939052c6adcd Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 13:16:58 -0600 Subject: [PATCH 03/22] Upgrading dependencies --- pom.xml | 26 ++-- .../safeguardjava/SafeguardA2AContext.java | 59 +++++--- .../safeguardjava/SafeguardConnection.java | 10 +- .../SafeguardSessionsConnection.java | 14 +- .../safeguardjava/SpsStreamingRequest.java | 20 +-- .../safeguardjava/StreamResponse.java | 2 +- .../safeguardjava/StreamingRequest.java | 14 +- .../safeguard/safeguardjava/Utils.java | 8 +- .../AnonymousAuthenticator.java | 8 +- .../authentication/AuthenticatorBase.java | 14 +- .../CertificateAuthenticator.java | 6 +- .../authentication/PasswordAuthenticator.java | 6 +- .../safeguardjava/data/FullResponse.java | 2 +- .../event/EventHandlerRegistry.java | 1 - .../restclient/ByteArrayEntity.java | 65 ++++++++- .../safeguardjava/restclient/RestClient.java | 136 +++++++++--------- .../safeguardclient/SafeguardTests.java | 6 +- 17 files changed, 233 insertions(+), 164 deletions(-) diff --git a/pom.xml b/pom.xml index 382731a..475a7d0 100644 --- a/pom.xml +++ b/pom.xml @@ -42,17 +42,17 @@ com.squareup.okhttp3 okhttp - 4.11.0 + 4.12.0 com.microsoft.signalr signalr - 7.0.11 + 8.0.12 - org.apache.httpcomponents - httpclient - 4.5.14 + org.apache.httpcomponents.client5 + httpclient5 + 5.4.2 commons-codec @@ -63,12 +63,7 @@ commons-codec commons-codec - 1.15 - - - org.apache.httpcomponents - httpmime - 4.5.14 + 1.17.1 jakarta.xml.bind @@ -84,18 +79,19 @@ com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.3 jar org.slf4j slf4j-api - 2.0.9 + 2.0.17 + com.google.code.gson gson - 2.10.1 + 2.11.0 @@ -107,7 +103,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.13.0 true 1.8 diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java index a88e188..336f854 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java @@ -1,7 +1,6 @@ package com.oneidentity.safeguard.safeguardjava; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; import com.oneidentity.safeguard.safeguardjava.data.A2ARegistration; import com.oneidentity.safeguard.safeguardjava.data.A2ARetrievableAccount; import com.oneidentity.safeguard.safeguardjava.data.A2ARetrievableAccountInternal; @@ -29,8 +28,8 @@ import java.util.ArrayList; import java.util.List; import javax.net.ssl.HostnameVerifier; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class SafeguardA2AContext implements ISafeguardA2AContext { @@ -104,8 +103,8 @@ public List getRetrievableAccounts() throws ObjectDispo } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) - throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getStatusLine().getStatusCode(), reply)); + if (!Utils.isSuccessful(response.getCode())) + throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getCode(), reply)); List registrations = parseA2ARegistationResponse(reply); @@ -122,8 +121,8 @@ public List getRetrievableAccounts() throws ObjectDispo } reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) - throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getStatusLine().getStatusCode(), reply)); + if (!Utils.isSuccessful(response.getCode())) + throw new SafeguardForJavaException(String.format("Error returned from Safeguard API, Error: %s %s", response.getCode(), reply)); List retrievals = parseA2ARetrievableAccountResponse(reply); @@ -175,12 +174,12 @@ public char[] retrievePassword(char[] apiKey) throws ObjectDisposedException, Sa } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } - char[] password = (new Gson().fromJson(reply, String.class)).toCharArray(); + char[] password = parseJsonString(reply).toCharArray(); Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A password."); return password; @@ -213,9 +212,9 @@ public void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedExc } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A password."); @@ -247,12 +246,12 @@ public char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws Obje } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } - char[] privateKey = (new Gson().fromJson(reply, String.class)).toCharArray(); + char[] privateKey = parseJsonString(reply).toCharArray(); Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A private key."); return privateKey; @@ -282,7 +281,7 @@ public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, Key sshKey.setPassphrase(new String(password)); sshKey.setPrivateKey(new String(privateKey)); - String body = new Gson().toJson(sshKey); + String body = serializeToJson(sshKey); Map headers = new HashMap<>(); headers.put(HttpHeaders.AUTHORIZATION, String.format("A2A %s", new String(apiKey))); @@ -297,9 +296,9 @@ public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, Key } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A private key."); @@ -330,9 +329,9 @@ public List retrieveApiKeySecret(char[] apiKey) throws ObjectDisp } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } List apiKeySecretsInternal = parseApiKeySecretResponse(reply); @@ -454,9 +453,9 @@ public String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRe } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%s %s", response.getCode(), reply)); } Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully created A2A access request."); @@ -525,4 +524,20 @@ private List parseApiKeySecretResponse(String response) { return null; } + + private String parseJsonString(String json) throws SafeguardForJavaException { + try { + return new ObjectMapper().readValue(json, String.class); + } catch (IOException e) { + throw new SafeguardForJavaException("Error parsing JSON response", e); + } + } + + private String serializeToJson(Object obj) throws SafeguardForJavaException { + try { + return new ObjectMapper().writeValueAsString(obj); + } catch (IOException e) { + throw new SafeguardForJavaException("Error serializing object to JSON", e); + } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java index f2137d9..04ab166 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java @@ -21,8 +21,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; class SafeguardConnection implements ISafeguardConnection { @@ -128,12 +128,12 @@ public FullResponse invokeMethodFull(Service service, Method method, String rela String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); logResponseDetails(fullResponse); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java index 1f23eef..c47107f 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java @@ -14,8 +14,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; -import org.apache.http.Header; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.Header; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; /** * This is the reusable connection interface that can be used to call SPS API. @@ -46,9 +46,9 @@ public SafeguardSessionsConnection(String networkAddress, String username, String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } Header authCookie = response.getFirstHeader("Set-Cookie"); @@ -118,14 +118,14 @@ public FullResponse invokeMethodFull(Method method, String relativeUrl, String b String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } Logger.getLogger(SafeguardSessionsConnection.class.getName()).log(Level.FINEST, String.format("Invoking method finished: $s", reply)); - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); logResponseDetails(fullResponse); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java index c9a3c07..a94972a 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java @@ -11,7 +11,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Map; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; class SpsStreamingRequest implements ISpsStreamingRequest { @@ -46,12 +46,12 @@ public String uploadStream(String relativeUrl, byte[] stream, IProgressCallback String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from SPS API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); SafeguardConnection.logResponseDetails(fullResponse); @@ -82,12 +82,12 @@ public String uploadStream(String relativeUrl, String fileName, String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from SPS API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); SafeguardConnection.logResponseDetails(fullResponse); @@ -116,13 +116,13 @@ public StreamResponse downloadStream(String relativeUrl, Map par throw new SafeguardForJavaException(String.format("Unable to connect to SPS service %s", client.getBaseURL())); } - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { String reply = Utils.getResponse(response); throw new SafeguardForJavaException("Error returned from SPS API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), null); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), null); SafeguardConnection.logResponseDetails(fullResponse); return new StreamResponse(response); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java index b170154..3b30ad4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java @@ -3,7 +3,7 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import java.io.IOException; import java.io.InputStream; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; /** * Represents a streamed response diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java index 40921e4..e4f3073 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java @@ -14,7 +14,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Map; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; class StreamingRequest implements IStreamingRequest { @@ -55,12 +55,12 @@ public String uploadStream(Service service, String relativeUrl, byte[] stream, I String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), reply); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); SafeguardConnection.logResponseDetails(fullResponse); @@ -91,10 +91,10 @@ public void downloadStream(Service service, String relativeUrl, String outputFil throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", client.getBaseURL())); } - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { String reply = Utils.getResponse(response); throw new SafeguardForJavaException("Error returned from Safeguard API, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } InputStream input = null; @@ -115,7 +115,7 @@ public void downloadStream(Service service, String relativeUrl, String outputFil if (input != null) try { input.close(); } catch (IOException logOrIgnore) {} } - FullResponse fullResponse = new FullResponse(response.getStatusLine().getStatusCode(), response.getAllHeaders(), null); + FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), null); SafeguardConnection.logResponseDetails(fullResponse); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java index df92409..f727e10 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java @@ -10,10 +10,10 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.http.HttpEntity; -import org.apache.http.ParseException; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.util.EntityUtils; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.io.entity.EntityUtils; public class Utils { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java index 7b7aceb..6422204 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java @@ -6,8 +6,8 @@ import java.util.HashMap; import java.util.Map; import javax.net.ssl.HostnameVerifier; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class AnonymousAuthenticator extends AuthenticatorBase { @@ -30,9 +30,9 @@ public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ign String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Unable to anonymously connect to {networkAddress}, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java index 212162b..3b3f2ed 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java @@ -17,8 +17,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; abstract class AuthenticatorBase implements IAuthenticationMechanism { @@ -115,7 +115,7 @@ public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, Saf if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", coreClient.getBaseURL())); } - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { return 0; } @@ -151,9 +151,9 @@ public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJav } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error exchanging RSTS token from " + this.getId() + "authenticator for Safeguard API access token, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } Map map = Utils.parseResponse(reply); @@ -178,9 +178,9 @@ public String resolveProviderToScope(String provider) throws SafeguardForJavaExc throw new SafeguardForJavaException("Unable to connect to RSTS to find identity provider scopes"); String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getCode())) throw new SafeguardForJavaException("Error requesting identity provider scopes from RSTS, Error: " + - String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + String.format("%d %s", response.getCode(), reply)); List knownScopes = parseLoginResponse(reply); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java index 7cb99cd..c7206b2 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java @@ -7,7 +7,7 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import java.util.Map; import javax.net.ssl.HostnameVerifier; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class CertificateAuthenticator extends AuthenticatorBase { @@ -117,12 +117,12 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); String content = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { String msg = Utils.isNullOrEmpty(clientCertificate.getCertificateAlias()) ? String.format("file=%s", clientCertificate.getCertificatePath()) : String.format("alias=%s", clientCertificate.getCertificateAlias()); throw new SafeguardForJavaException("Error using client_credentials grant_type with " + clientCertificate.toString() + - String.format(", Error: %d %s", response.getStatusLine().getStatusCode(), content)); + String.format(", Error: %d %s", response.getCode(), content)); } Map map = Utils.parseResponse(content); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java index 23cc6f2..fda6bc7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java @@ -10,7 +10,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class PasswordAuthenticator extends AuthenticatorBase { @@ -57,9 +57,9 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getCode())) throw new SafeguardForJavaException(String.format("Error using password grant_type with scope %s, Error: ", providerScope) + - String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + String.format("%s %s", response.getCode(), reply)); Map map = Utils.parseResponse(reply); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java index c1c94c9..a5d028d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java @@ -2,7 +2,7 @@ import java.util.Arrays; import java.util.List; -import org.apache.http.Header; +import org.apache.hc.core5.http.Header; /** diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java index 09c2a64..f15b045 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java @@ -1,6 +1,5 @@ package com.oneidentity.safeguard.safeguardjava.event; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.ArrayList; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java index a21082b..10b0e51 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java @@ -2,24 +2,81 @@ import com.oneidentity.safeguard.safeguardjava.IProgressCallback; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.util.List; +import java.util.Set; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; -public class ByteArrayEntity extends org.apache.http.entity.ByteArrayEntity { +public class ByteArrayEntity implements HttpEntity { private OutputStreamProgress outstream; private final IProgressCallback progressCallback; private final long totalBytes; + private final org.apache.hc.core5.http.io.entity.ByteArrayEntity delegate; public ByteArrayEntity(byte[] b, IProgressCallback progressCallback) { - super(b); + this.delegate = new org.apache.hc.core5.http.io.entity.ByteArrayEntity(b, null); this.progressCallback = progressCallback; this.totalBytes = b.length; } + @Override + public boolean isRepeatable() { + return delegate.isRepeatable(); + } + + @Override + public long getContentLength() { + return delegate.getContentLength(); + } + + @Override + public String getContentType() { + return delegate.getContentType(); + } + + @Override + public String getContentEncoding() { + return delegate.getContentEncoding(); + } + + @Override + public InputStream getContent() throws IOException { + return delegate.getContent(); + } + @Override public void writeTo(OutputStream outstream) throws IOException { this.outstream = new OutputStreamProgress(outstream, this.progressCallback, totalBytes); - super.writeTo(this.outstream); + delegate.writeTo(this.outstream); + } + + @Override + public boolean isStreaming() { + return delegate.isStreaming(); + } + + @Override + public boolean isChunked() { + return delegate.isChunked(); + } + + @Override + public Set getTrailerNames() { + return delegate.getTrailerNames(); + } + + @Override + public Supplier> getTrailers() { + return delegate.getTrailers(); + } + + @Override + public void close() throws IOException { + delegate.close(); } public int getProgress() { @@ -27,7 +84,7 @@ public int getProgress() { return 0; } long contentLength = getContentLength(); - if (contentLength <= 0) { // Prevent division by zero and negative values + if (contentLength <= 0) { return 0; } long writtenLength = outstream.getWrittenLength(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index 117619d..c1d0cce 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -44,31 +44,31 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.CookieSpecs; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.util.Timeout; public class RestClient { @@ -91,13 +91,10 @@ public RestClient(String connectionAddr, String userName, char[] password, boole HttpClientBuilder builder = createClientBuilder(connectionAddr, ignoreSsl, validationCallback); - CredentialsProvider provider = new BasicCredentialsProvider(); - provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, new String(password))); - - RequestConfig customizedRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); + BasicCredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(userName, password)); client = builder.setDefaultCredentialsProvider(provider) - .setDefaultRequestConfig(customizedRequestConfig) .setDefaultCookieStore(cookieStore) .build(); } @@ -136,7 +133,7 @@ private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ign Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - return HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connectionManager); + return HttpClients.custom().setConnectionManager(connectionManager); } private URI getBaseURI(String segments) { @@ -186,7 +183,7 @@ public void addSessionId(String cookieValue) { if (expiryDate != null) { SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z"); Date date = formatter.parse(expiryDate); - cookie.setExpiryDate(date); + cookie.setExpiryDate(date.toInstant()); } String path = keyValues.get("Path"); @@ -213,10 +210,10 @@ public void addSessionId(String cookieValue) { public CloseableHttpResponse execGET(String path, Map queryParams, Map headers, Integer timeout) { - RequestBuilder rb = prepareRequest (RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -228,10 +225,10 @@ public CloseableHttpResponse execGET(String path, Map queryParam CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -247,10 +244,10 @@ public CloseableHttpResponse execGETBytes(String path, Map query headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.ACCEPT, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -267,10 +264,10 @@ public CloseableHttpResponse execGETBytes(String path, Map query headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.ACCEPT, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -281,12 +278,12 @@ public CloseableHttpResponse execGETBytes(String path, Map query public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity) { - RequestBuilder rb = prepareRequest(RequestBuilder.put(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.put(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -298,12 +295,12 @@ public CloseableHttpResponse execPUT(String path, Map queryParam CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.put(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.put(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -314,12 +311,12 @@ public CloseableHttpResponse execPUT(String path, Map queryParam public CloseableHttpResponse execPOST(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity) { - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -332,12 +329,12 @@ public CloseableHttpResponse execPOST(String path, Map queryPara CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -354,11 +351,11 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.CONTENT_TYPE, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(new ByteArrayEntity(requestEntity, progressCallback)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -375,11 +372,11 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.CONTENT_TYPE, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(new ByteArrayEntity(requestEntity, progressCallback)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -393,17 +390,17 @@ public CloseableHttpResponse execPOSTFile(String path, Map query File file = new File(fileName); - HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(data); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -417,17 +414,17 @@ public CloseableHttpResponse execPOSTFile(String path, Map query if (certClient != null) { File file = new File(fileName); - HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(data); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -438,10 +435,10 @@ public CloseableHttpResponse execPOSTFile(String path, Map query public CloseableHttpResponse execDELETE(String path, Map queryParams, Map headers, Integer timeout) { - RequestBuilder rb = prepareRequest(RequestBuilder.delete(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.delete(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -495,13 +492,13 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific } Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - certClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connectionManager).build(); + certClient = HttpClients.custom().setConnectionManager(connectionManager).build(); } return certClient; } - private RequestBuilder prepareRequest(RequestBuilder rb, Map queryParams, Map headers, Integer timeout) { + private ClassicRequestBuilder prepareRequest(ClassicRequestBuilder rb, Map queryParams, Map headers) { if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) rb.addHeader(HttpHeaders.ACCEPT, "application/json"); @@ -518,14 +515,19 @@ private RequestBuilder prepareRequest(RequestBuilder rb, Map que rb.addParameter(entry.getKey(), entry.getValue()); }); } + return rb; + } + + private HttpClientContext createContext(Integer timeout) { + HttpClientContext context = HttpClientContext.create(); if (timeout != null) { RequestConfig rconfig = RequestConfig.custom() - .setConnectTimeout(timeout) - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout).build(); - rb.setConfig(rconfig); + .setConnectTimeout(Timeout.ofMilliseconds(timeout)) + .setConnectionRequestTimeout(Timeout.ofMilliseconds(timeout)) + .setResponseTimeout(Timeout.ofMilliseconds(timeout)).build(); + context.setRequestConfig(rconfig); } - return rb; + return context; } private SSLContext getSSLContext(KeyStore keyStorePath, char[] keyStorePassword, String alias, CertificateContext certificateContext) { diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java index 292547d..d161e01 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; +import com.fasterxml.jackson.databind.ObjectMapper; import static com.oneidentity.safeguard.safeguardclient.SafeguardJavaClient.readLine; import com.oneidentity.safeguard.safeguardclient.data.SafeguardAppliance; import com.oneidentity.safeguard.safeguardclient.data.SafeguardApplianceStatus; @@ -783,13 +783,13 @@ public void safeguardListBackups(ISafeguardConnection connection) { try { String response = connection.invokeMethod(Service.Appliance, Method.Get, "Backups", null, null, null, null); - SafeguardBackup[] backups = new Gson().fromJson(response, SafeguardBackup[].class); + SafeguardBackup[] backups = new ObjectMapper().readValue(response, SafeguardBackup[].class); System.out.println(String.format("\t\\Backups response:")); for (SafeguardBackup backup : backups) { System.out.println(String.format("Id: %s - File Name: %s", backup.getId(), backup.getFilename())); } - } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException | java.io.IOException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } From 47186593d3b8665ae1a72d7f9b9cce7c5dbfb0d9 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 14:13:58 -0600 Subject: [PATCH 04/22] add intellij files --- .idea/.gitignore | 12 ++++++++++++ .idea/compiler.xml | 13 +++++++++++++ .idea/encodings.xml | 7 +++++++ .idea/jarRepositories.xml | 20 ++++++++++++++++++++ .idea/misc.xml | 11 +++++++++++ .idea/vcs.xml | 6 ++++++ 6 files changed, 69 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..660d40e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,12 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Misc +copilot.* diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..387675c --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e85157a --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b73630f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From ce253f6ca8b294b6e396a985aea59b6815556fa0 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 16:57:14 -0600 Subject: [PATCH 05/22] updated safeguardjavaclient to be non-interactive for automated testing --- tests/safeguardjavaclient/pom.xml | 28 +- .../safeguardclient/SafeguardJavaClient.java | 239 +++++++++++++----- .../safeguardclient/ToolOptions.java | 89 +++++++ 3 files changed, 291 insertions(+), 65 deletions(-) create mode 100644 tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java diff --git a/tests/safeguardjavaclient/pom.xml b/tests/safeguardjavaclient/pom.xml index 7efd4db..3d87aab 100644 --- a/tests/safeguardjavaclient/pom.xml +++ b/tests/safeguardjavaclient/pom.xml @@ -15,9 +15,15 @@ - commons-cli - commons-cli - 1.5.0 + info.picocli + picocli + 4.7.6 + + + + com.fasterxml.jackson.core + jackson-databind + 2.18.3 @@ -29,16 +35,25 @@ org.slf4j slf4j-simple - 1.7.36 + 2.0.17 + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 1.8 + 1.8 + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -58,7 +73,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.0 + 3.4.2 @@ -69,7 +84,6 @@ - diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index f1aef50..b0b8263 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -1,24 +1,199 @@ package com.oneidentity.safeguard.safeguardclient; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.oneidentity.safeguard.safeguardjava.ISafeguardA2AContext; import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; import com.oneidentity.safeguard.safeguardjava.ISafeguardSessionsConnection; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.data.FullResponse; +import com.oneidentity.safeguard.safeguardjava.data.Method; +import com.oneidentity.safeguard.safeguardjava.data.Service; import com.oneidentity.safeguard.safeguardjava.event.ISafeguardEventListener; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; +import org.apache.hc.core5.http.Header; +import picocli.CommandLine; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; import java.util.Scanner; public class SafeguardJavaClient { + private static final ObjectMapper mapper = new ObjectMapper(); + public static void main(String[] args) { + if (args.length == 0) { + runInteractive(); + return; + } + + ToolOptions opts = new ToolOptions(); + CommandLine cmd = new CommandLine(opts); + + try { + cmd.parseArgs(args); + } catch (CommandLine.ParameterException ex) { + System.err.println("Error: " + ex.getMessage()); + cmd.usage(System.err); + System.exit(1); + return; + } + + if (cmd.isUsageHelpRequested()) { + cmd.usage(System.out); + return; + } + + if (opts.interactive) { + runInteractive(); + return; + } + + try { + ISafeguardConnection connection = createConnection(opts); + + if (opts.tokenLifetime) { + int remaining = connection.getAccessTokenLifetimeRemaining(); + ObjectNode json = mapper.createObjectNode(); + json.put("TokenLifetimeRemaining", remaining); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + if (opts.getToken) { + char[] token = connection.getAccessToken(); + ObjectNode json = mapper.createObjectNode(); + json.put("AccessToken", new String(token)); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + Service service = parseService(opts.service); + Method method = parseMethod(opts.method); + Map headers = parseKeyValuePairs(opts.headers); + Map parameters = parseKeyValuePairs(opts.parameters); + + if (opts.full) { + FullResponse response = connection.invokeMethodFull(service, method, + opts.relativeUrl, opts.body, parameters, headers, null); + ObjectNode json = mapper.createObjectNode(); + json.put("StatusCode", response.getStatusCode()); + ArrayNode headersArray = json.putArray("Headers"); + for (Header h : response.getHeaders()) { + ObjectNode headerObj = mapper.createObjectNode(); + headerObj.put("Name", h.getName()); + headerObj.put("Value", h.getValue()); + headersArray.add(headerObj); + } + json.put("Body", response.getBody()); + System.out.println(mapper.writeValueAsString(json)); + } else { + String result = connection.invokeMethod(service, method, + opts.relativeUrl, opts.body, parameters, headers, null); + System.out.println(result); + } + + System.exit(0); + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + if (opts.verbose) { + ex.printStackTrace(System.err); + } + System.exit(1); + } + } + + private static ISafeguardConnection createConnection(ToolOptions opts) throws Exception { + char[] password = null; + if (opts.readPassword) { + System.err.print("Password: "); + password = new Scanner(System.in).nextLine().toCharArray(); + } + + if (opts.username != null) { + String provider = opts.identityProvider != null ? opts.identityProvider : "local"; + System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider); + return Safeguard.connect(opts.appliance, provider, opts.username, password, null, opts.insecure); + } + + if (opts.certificateFile != null) { + System.err.println("Connecting to " + opts.appliance + " with certificate file " + opts.certificateFile); + return Safeguard.connect(opts.appliance, opts.certificateFile, password, null, opts.insecure, null); + } + + if (opts.thumbprint != null) { + System.err.println("Connecting to " + opts.appliance + " with thumbprint " + opts.thumbprint); + return Safeguard.connect(opts.appliance, opts.thumbprint, null, opts.insecure); + } + + if (opts.accessToken != null) { + System.err.println("Connecting to " + opts.appliance + " with access token"); + return Safeguard.connect(opts.appliance, opts.accessToken.toCharArray(), null, opts.insecure); + } + + if (opts.anonymous) { + System.err.println("Connecting to " + opts.appliance + " anonymously"); + return Safeguard.connect(opts.appliance, null, opts.insecure); + } + + throw new IllegalArgumentException( + "No authentication method specified. Use -u, -c, -t, -k, or -A."); + } + + private static Service parseService(String serviceStr) { + if (serviceStr == null) { + throw new IllegalArgumentException("Service (-s) is required for API invocation"); + } + switch (serviceStr.toLowerCase()) { + case "core": return Service.Core; + case "appliance": return Service.Appliance; + case "notification": return Service.Notification; + case "a2a": return Service.A2A; + default: + throw new IllegalArgumentException("Unknown service: " + serviceStr + + ". Valid values: Core, Appliance, Notification, A2A"); + } + } + + private static Method parseMethod(String methodStr) { + if (methodStr == null) { + throw new IllegalArgumentException("Method (-m) is required for API invocation"); + } + switch (methodStr.toLowerCase()) { + case "get": return Method.Get; + case "post": return Method.Post; + case "put": return Method.Put; + case "delete": return Method.Delete; + default: + throw new IllegalArgumentException("Unknown method: " + methodStr + + ". Valid values: Get, Post, Put, Delete"); + } + } + + private static Map parseKeyValuePairs(String[] pairs) { + Map map = new HashMap<>(); + if (pairs == null) { + return map; + } + for (String pair : pairs) { + int idx = pair.indexOf('='); + if (idx < 0) { + throw new IllegalArgumentException("Invalid Key=Value pair: " + pair); + } + map.put(pair.substring(0, idx), pair.substring(idx + 1)); + } + return map; + } + + private static void runInteractive() { + ISafeguardConnection connection = null; ISafeguardSessionsConnection sessionConnection = null; ISafeguardA2AContext a2aContext = null; @@ -210,44 +385,6 @@ private static Integer displayMenu() { return selection; } - private static CommandLine parseArguments(String[] args) { - - Options options = getOptions(); - CommandLine line = null; - - CommandLineParser parser = new DefaultParser(); - - try { - line = parser.parse(options, args); - - } catch (ParseException ex) { - - System.err.println(ex); - printAppHelp(); - - System.exit(1); - } - - return line; - } - - private static Options getOptions() { - - Options options = new Options(); - - options.addOption("f", "filename", true, - "file name to load data from"); - return options; - } - - private static void printAppHelp() { - - Options options = getOptions(); - - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("SafeguardClientCli", options, true); - } - public static String toJsonString(String name, Object value, boolean prependSep) { if (value != null) { return (prependSep ? ", " : "") + "\"" + name + "\" : " + (value instanceof String ? "\"" + value.toString() + "\"" : value.toString()); @@ -276,18 +413,4 @@ public static String readLine(String format, String defaultStr, Object... args) return defaultStr; return value.trim(); } - - - - - - } - -//class EventHandler implements ISafeguardEventHandler { -// -// @Override -// public void onEventReceived(String eventName, String eventBody) { -// System.out.println("Got the event"); -// } -//} diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java new file mode 100644 index 0000000..77a8685 --- /dev/null +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -0,0 +1,89 @@ +package com.oneidentity.safeguard.safeguardclient; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command(name = "SafeguardJavaTool", mixinStandardHelpOptions = true, + description = "Non-interactive CLI tool for testing SafeguardJava SDK") +public class ToolOptions { + + @Option(names = {"-a", "--appliance"}, required = true, + description = "IP address or hostname of Safeguard appliance") + String appliance; + + @Option(names = {"-x", "--insecure"}, defaultValue = "false", + description = "Ignore validation of Safeguard appliance SSL certificate") + boolean insecure; + + @Option(names = {"-p", "--read-password"}, defaultValue = "false", + description = "Read any required password from console stdin") + boolean readPassword; + + @Option(names = {"-u", "--username"}, + description = "Safeguard username to use to authenticate") + String username; + + @Option(names = {"-i", "--identity-provider"}, + description = "Safeguard identity provider to use for rSTS") + String identityProvider; + + @Option(names = {"-c", "--certificate-file"}, + description = "File path for client certificate") + String certificateFile; + + @Option(names = {"-t", "--thumbprint"}, + description = "Thumbprint for client certificate in cert store") + String thumbprint; + + @Option(names = {"-k", "--access-token"}, + description = "Pre-obtained access token for authentication") + String accessToken; + + @Option(names = {"-A", "--anonymous"}, defaultValue = "false", + description = "Do not authenticate, call API anonymously") + boolean anonymous; + + @Option(names = {"-s", "--service"}, + description = "Safeguard service: Core, Appliance, Notification, A2A") + String service; + + @Option(names = {"-m", "--method"}, + description = "HTTP method: Get, Post, Put, Delete") + String method; + + @Option(names = {"-U", "--relative-url"}, + description = "API endpoint relative URL") + String relativeUrl; + + @Option(names = {"-b", "--body"}, + description = "JSON body as string") + String body; + + @Option(names = {"-f", "--full"}, defaultValue = "false", + description = "Use InvokeMethodFull and output JSON envelope with StatusCode, Headers, Body") + boolean full; + + @Option(names = {"-H", "--header"}, split = ",", + description = "Additional HTTP headers as Key=Value pairs (comma-separated)") + String[] headers; + + @Option(names = {"-P", "--parameter"}, split = ",", + description = "Query parameters as Key=Value pairs (comma-separated)") + String[] parameters; + + @Option(names = {"-T", "--token-lifetime"}, defaultValue = "false", + description = "Output token lifetime remaining as JSON and skip API invocation") + boolean tokenLifetime; + + @Option(names = {"-G", "--get-token"}, defaultValue = "false", + description = "Output the current access token as JSON and exit") + boolean getToken; + + @Option(names = {"-V", "--verbose"}, defaultValue = "false", + description = "Display verbose debug output") + boolean verbose; + + @Option(names = {"--interactive"}, defaultValue = "false", + description = "Run in interactive menu mode (legacy)") + boolean interactive; +} From 27cb91b246f2714fcde1dc4ae832c7f73cea21d9 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Tue, 17 Mar 2026 17:11:35 -0600 Subject: [PATCH 06/22] Added a basic powershell test driver --- TestFramework/Invoke-SafeguardTests.ps1 | 249 +++++ TestFramework/README.md | 107 ++ TestFramework/SafeguardTestFramework.psm1 | 956 ++++++++++++++++++ .../Suites/Suite-AnonymousAccess.ps1 | 31 + TestFramework/Suites/Suite-ApiInvocation.ps1 | 147 +++ TestFramework/Suites/Suite-PasswordAuth.ps1 | 66 ++ .../Suites/Suite-TokenManagement.ps1 | 41 + 7 files changed, 1597 insertions(+) create mode 100644 TestFramework/Invoke-SafeguardTests.ps1 create mode 100644 TestFramework/README.md create mode 100644 TestFramework/SafeguardTestFramework.psm1 create mode 100644 TestFramework/Suites/Suite-AnonymousAccess.ps1 create mode 100644 TestFramework/Suites/Suite-ApiInvocation.ps1 create mode 100644 TestFramework/Suites/Suite-PasswordAuth.ps1 create mode 100644 TestFramework/Suites/Suite-TokenManagement.ps1 diff --git a/TestFramework/Invoke-SafeguardTests.ps1 b/TestFramework/Invoke-SafeguardTests.ps1 new file mode 100644 index 0000000..feeaeff --- /dev/null +++ b/TestFramework/Invoke-SafeguardTests.ps1 @@ -0,0 +1,249 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + SafeguardJava Integration Test Runner + +.DESCRIPTION + Discovers and runs test suites from the Suites/ directory against a live + Safeguard appliance. Each suite follows Setup -> Execute -> Cleanup lifecycle + with continue-on-failure semantics and structured reporting. + +.PARAMETER Appliance + Safeguard appliance network address (required). + +.PARAMETER AdminUserName + Bootstrap admin username. Default: "admin". + +.PARAMETER AdminPassword + Bootstrap admin password. Default: "Admin123". + +.PARAMETER Suite + Run only the specified suite(s) by name. Accepts wildcards. + +.PARAMETER ExcludeSuite + Skip the specified suite(s) by name. Accepts wildcards. + +.PARAMETER ListSuites + List available test suites without running them. + +.PARAMETER ReportPath + Optional path to export JSON test report. + +.PARAMETER SkipBuild + Skip building test projects (use when already built). + +.PARAMETER TestPrefix + Prefix for test objects created on the appliance. Default: "SgJTest". + +.PARAMETER MavenCmd + Path to the Maven command. Defaults to searching PATH then common locations. + +.EXAMPLE + ./Invoke-SafeguardTests.ps1 -Appliance 192.168.117.15 -AdminPassword root4EDMZ + +.EXAMPLE + ./Invoke-SafeguardTests.ps1 -Appliance sg.example.com -Suite PasswordAuth,AnonymousAccess + +.EXAMPLE + ./Invoke-SafeguardTests.ps1 -ListSuites +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $false, Position = 0)] + [string]$Appliance, + + [Parameter()] + [string]$AdminUserName = "admin", + + [Parameter()] + [string]$AdminPassword = "Admin123", + + [Parameter()] + [string[]]$Suite, + + [Parameter()] + [string[]]$ExcludeSuite, + + [Parameter()] + [switch]$ListSuites, + + [Parameter()] + [string]$ReportPath, + + [Parameter()] + [switch]$SkipBuild, + + [Parameter()] + [string]$TestPrefix = "SgJTest", + + [Parameter()] + [string]$MavenCmd +) + +$ErrorActionPreference = "Continue" + +# Find Maven if not specified +if (-not $MavenCmd) { + $MavenCmd = Get-Command mvn -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source + if (-not $MavenCmd) { + $MavenCmd = Get-Command mvn.cmd -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source + } + if (-not $MavenCmd -and $env:M2_HOME) { + $c = Join-Path $env:M2_HOME "bin" "mvn.cmd" + if (Test-Path $c) { $MavenCmd = $c } + } + if (-not $MavenCmd) { + # Search common locations for any installed Maven version + $searchDirs = @( + "$env:USERPROFILE\tools" + "C:\tools" + "C:\Program Files\Apache\Maven" + "C:\Program Files (x86)\Apache\Maven" + ) + foreach ($dir in $searchDirs) { + if (Test-Path $dir) { + $found = Get-ChildItem -Path $dir -Filter "apache-maven-*" -Directory -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | Select-Object -First 1 + if ($found) { + $c = Join-Path $found.FullName "bin" "mvn.cmd" + if (Test-Path $c) { $MavenCmd = $c; break } + } + } + } + } + if (-not $MavenCmd) { + Write-Error "Maven not found. Provide -MavenCmd or add Maven to PATH." + exit 1 + } +} + +# Import the framework module +$frameworkModule = Join-Path $PSScriptRoot "SafeguardTestFramework.psm1" +if (-not (Test-Path $frameworkModule)) { + Write-Error "Framework module not found: $frameworkModule" + exit 1 +} +Import-Module $frameworkModule -Force + +# Discover suite files +$suitesDir = Join-Path $PSScriptRoot "Suites" +if (-not (Test-Path $suitesDir)) { + Write-Error "Suites directory not found: $suitesDir" + exit 1 +} + +$suiteFiles = Get-ChildItem -Path $suitesDir -Filter "Suite-*.ps1" | Sort-Object Name + +if ($suiteFiles.Count -eq 0) { + Write-Warning "No suite files found in $suitesDir" + if ($ListSuites) { exit 0 } +} + +# --- List mode --- +if ($ListSuites) { + Write-Host "" + Write-Host "Available Test Suites:" -ForegroundColor Cyan + Write-Host ("-" * 60) -ForegroundColor DarkGray + foreach ($file in $suiteFiles) { + $def = & $file.FullName + $shortName = $file.BaseName -replace '^Suite-', '' + $tags = if ($def.Tags) { "[$($def.Tags -join ', ')]" } else { "" } + Write-Host " $($shortName.PadRight(30)) $($def.Name)" -ForegroundColor White + if ($def.Description) { + Write-Host " $($def.Description)" -ForegroundColor DarkGray + } + if ($tags) { + Write-Host " Tags: $tags" -ForegroundColor DarkGray + } + } + Write-Host "" + exit 0 +} + +# --- Run mode: Appliance is required --- +if (-not $Appliance) { + Write-Error "The -Appliance parameter is required when running tests. Use -ListSuites to see available suites." + exit 1 +} + +# Filter suites +$selectedSuites = $suiteFiles +if ($Suite) { + $selectedSuites = $selectedSuites | Where-Object { + $shortName = $_.BaseName -replace '^Suite-', '' + $matched = $false + foreach ($pattern in $Suite) { + if ($shortName -like $pattern) { $matched = $true; break } + } + $matched + } +} +if ($ExcludeSuite) { + $selectedSuites = $selectedSuites | Where-Object { + $shortName = $_.BaseName -replace '^Suite-', '' + $excluded = $false + foreach ($pattern in $ExcludeSuite) { + if ($shortName -like $pattern) { $excluded = $true; break } + } + -not $excluded + } +} + +if (-not $selectedSuites -or @($selectedSuites).Count -eq 0) { + Write-Warning "No suites matched the specified filters." + exit 0 +} + +# --- Initialize --- +Write-Host "" +Write-Host ("=" * 66) -ForegroundColor Cyan +Write-Host " SafeguardJava Integration Tests" -ForegroundColor Cyan +Write-Host ("=" * 66) -ForegroundColor Cyan +Write-Host " Appliance: $Appliance" -ForegroundColor White +Write-Host " Maven: $MavenCmd" -ForegroundColor White +Write-Host " Suites: $(@($selectedSuites).Count) selected" -ForegroundColor White +Write-Host ("=" * 66) -ForegroundColor Cyan + +$context = New-SgJTestContext ` + -Appliance $Appliance ` + -AdminUserName $AdminUserName ` + -AdminPassword $AdminPassword ` + -TestPrefix $TestPrefix ` + -MavenCmd $MavenCmd + +# --- Build --- +if (-not $SkipBuild) { + Write-Host "" + Write-Host "Building test projects..." -ForegroundColor Yellow + try { + Build-SgJTestProjects -Context $context + Write-Host " Build complete." -ForegroundColor Green + } + catch { + Write-Host " Build failed: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} + +# --- Global pre-cleanup --- +Write-Host "" +Write-Host "Pre-cleanup: removing stale objects from previous runs..." -ForegroundColor Yellow +Clear-SgJStaleTestEnvironment -Context $context + +# --- Run suites --- +foreach ($suiteFile in $selectedSuites) { + Invoke-SgJTestSuite -SuiteFile $suiteFile.FullName -Context $context +} + +# --- Report --- +$failCount = Write-SgJTestReport -Context $context + +if ($ReportPath) { + Export-SgJTestReport -OutputPath $ReportPath -Context $context +} + +# Exit with appropriate code for CI +if ($failCount -gt 0) { + exit 1 +} +exit 0 diff --git a/TestFramework/README.md b/TestFramework/README.md new file mode 100644 index 0000000..b8c216f --- /dev/null +++ b/TestFramework/README.md @@ -0,0 +1,107 @@ +# SafeguardJava Test Framework + +Integration test framework for the SafeguardJava SDK. Tests run against a live +Safeguard appliance using the SafeguardJavaTool CLI. + +## Prerequisites + +- PowerShell 7.0+ +- Java 8+ (JDK) +- Maven 3.6+ +- A Safeguard appliance with admin credentials + +## Quick Start + +```powershell +# Run all test suites +./TestFramework/Invoke-SafeguardTests.ps1 -Appliance 192.168.1.100 -AdminPassword "YourPassword" + +# Run specific suites +./TestFramework/Invoke-SafeguardTests.ps1 -Appliance sg.example.com -Suite PasswordAuth,AnonymousAccess + +# List available suites +./TestFramework/Invoke-SafeguardTests.ps1 -ListSuites + +# Skip build (if already built) +./TestFramework/Invoke-SafeguardTests.ps1 -Appliance sg.example.com -AdminPassword "pw" -SkipBuild + +# Export JSON report +./TestFramework/Invoke-SafeguardTests.ps1 -Appliance sg.example.com -AdminPassword "pw" -ReportPath results.json + +# Specify Maven path +./TestFramework/Invoke-SafeguardTests.ps1 -Appliance sg.example.com -AdminPassword "pw" -MavenCmd "C:\tools\maven\bin\mvn.cmd" +``` + +## Architecture + +``` +TestFramework/ + Invoke-SafeguardTests.ps1 # Entry point / test runner + SafeguardTestFramework.psm1 # Core framework module + README.md # This file + Suites/ + Suite-AnonymousAccess.ps1 # Anonymous endpoint tests + Suite-PasswordAuth.ps1 # Password authentication tests + Suite-ApiInvocation.ps1 # HTTP methods, PUT, DELETE, Full responses + Suite-TokenManagement.ps1 # Token lifecycle tests +tests/ + safeguardjavaclient/ # Java CLI test tool +``` + +## Writing Test Suites + +Each suite is a `.ps1` file in the `Suites/` directory that returns a hashtable: + +```powershell +@{ + Name = "My Test Suite" + Description = "What this suite tests" + Tags = @("tag1", "tag2") + + Setup = { + param($Context) + # Create test objects, register cleanup + $user = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Body @{ Name = "TestUser"; ... } + + Register-SgJTestCleanup -Description "Delete test user" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx -RelativeUrl "Users/$($user.Id)" + } + } + + Execute = { + param($Context) + Test-SgJAssert "Test name" { $true } + Test-SgJAssertEqual "Values match" "expected" "actual" + Test-SgJAssertNotNull "Has value" $someValue + Test-SgJAssertThrows "Should fail" { throw "error" } + } + + Cleanup = { + param($Context) + # Registered cleanup runs automatically (LIFO) + } +} +``` + +## Available Functions + +### API Invocation +- `Invoke-SgJSafeguardApi` — Call Safeguard API (handles auth, JSON, headers) +- `Invoke-SgJTokenCommand` — Token operations (TokenLifetime, GetToken) +- `Invoke-SgJSafeguardTool` — Low-level tool invocation + +### Assertions +- `Test-SgJAssert` — Boolean assertion with continue-on-failure +- `Test-SgJAssertEqual` — Equality check +- `Test-SgJAssertNotNull` — Null check +- `Test-SgJAssertContains` — Substring check +- `Test-SgJAssertThrows` — Exception check +- `Test-SgJSkip` — Skip with reason + +### Object Management +- `Remove-SgJStaleTestObject` — Remove by name from collection +- `Remove-SgJSafeguardTestObject` — Remove by direct URL +- `Clear-SgJStaleTestEnvironment` — Remove all test-prefixed objects +- `Register-SgJTestCleanup` — Register LIFO cleanup action diff --git a/TestFramework/SafeguardTestFramework.psm1 b/TestFramework/SafeguardTestFramework.psm1 new file mode 100644 index 0000000..5bd77be --- /dev/null +++ b/TestFramework/SafeguardTestFramework.psm1 @@ -0,0 +1,956 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + SafeguardJava Test Framework Module + +.DESCRIPTION + Provides test context management, assertion functions, Java tool invocation, + cleanup registration, and structured reporting for SafeguardJava integration tests. + + All tests run against a live Safeguard appliance. This module invokes the + SafeguardJavaTool CLI via java -jar with proper process management. + + All exported functions use the SgJ noun prefix to avoid conflicts. +#> + +# ============================================================================ +# Module-scoped state +# ============================================================================ + +$script:TestContext = $null + +# ============================================================================ +# Context Management +# ============================================================================ + +function New-SgJTestContext { + <# + .SYNOPSIS + Creates a new test context tracking appliance info, credentials, results, and cleanup. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Appliance, + + [Parameter()] + [string]$AdminUserName = "admin", + + [Parameter()] + [string]$AdminPassword = "Admin123", + + [Parameter()] + [string]$TestPrefix = "SgJTest", + + [Parameter()] + [string]$MavenCmd + ) + + $repoRoot = Split-Path -Parent $PSScriptRoot + $context = [PSCustomObject]@{ + # Connection info + Appliance = $Appliance + AdminUserName = $AdminUserName + AdminPassword = $AdminPassword + + # Naming + TestPrefix = $TestPrefix + + # Paths + RepoRoot = $repoRoot + TestRoot = $PSScriptRoot + ToolDir = (Join-Path $repoRoot "tests" "safeguardjavaclient") + MavenCmd = $MavenCmd + + # Per-suite transient data (reset each suite) + SuiteData = @{} + + # Cleanup stack (LIFO) + CleanupActions = [System.Collections.Generic.Stack[PSCustomObject]]::new() + + # Results + SuiteResults = [System.Collections.Generic.List[PSCustomObject]]::new() + StartTime = [DateTime]::UtcNow + } + + $script:TestContext = $context + return $context +} + +function Get-SgJTestContext { + <# + .SYNOPSIS + Returns the current module-scoped test context. + #> + if (-not $script:TestContext) { + throw "No test context. Call New-SgJTestContext first." + } + return $script:TestContext +} + +# ============================================================================ +# Cleanup Registration +# ============================================================================ + +function Register-SgJTestCleanup { + <# + .SYNOPSIS + Registers an idempotent cleanup action that runs during suite cleanup. + Actions execute in LIFO order. Failures are logged but do not propagate. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Description, + + [Parameter(Mandatory)] + [scriptblock]$Action + ) + + $ctx = Get-SgJTestContext + $ctx.CleanupActions.Push([PSCustomObject]@{ + Description = $Description + Action = $Action + }) +} + +function Invoke-SgJTestCleanup { + <# + .SYNOPSIS + Executes all registered cleanup actions in LIFO order. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $count = $Context.CleanupActions.Count + if ($count -eq 0) { + Write-Host " No cleanup actions registered." -ForegroundColor DarkGray + return + } + + Write-Host " Running $count cleanup action(s)..." -ForegroundColor DarkGray + while ($Context.CleanupActions.Count -gt 0) { + $item = $Context.CleanupActions.Pop() + try { + Write-Host " Cleanup: $($item.Description)" -ForegroundColor DarkGray + & $item.Action $Context + } + catch { + Write-Host " Cleanup ignored failure: $($_.Exception.Message)" -ForegroundColor DarkYellow + } + } +} + +# ============================================================================ +# SafeguardJavaTool Invocation +# ============================================================================ + +function Invoke-SgJSafeguardTool { + <# + .SYNOPSIS + Runs the SafeguardJavaTool via java -jar with proper process management. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$Arguments, + + [Parameter()] + [string]$StdinLine, + + [Parameter()] + [bool]$ParseJson = $true, + + [Parameter()] + [int]$TimeoutSeconds = 120 + ) + + $ctx = Get-SgJTestContext + + $jarPath = Join-Path $ctx.ToolDir "target" "safeguardjavaclient-1.0-SNAPSHOT.jar" + if (-not (Test-Path $jarPath)) { + throw "Test tool jar not found at: $jarPath. Run build first." + } + + $startInfo = [System.Diagnostics.ProcessStartInfo]::new() + $startInfo.FileName = "java" + $startInfo.Arguments = "-jar `"$jarPath`" $Arguments" + $startInfo.UseShellExecute = $false + $startInfo.RedirectStandardOutput = $true + $startInfo.RedirectStandardError = $true + $startInfo.RedirectStandardInput = ($null -ne $StdinLine -and $StdinLine -ne "") + $startInfo.CreateNoWindow = $true + $startInfo.WorkingDirectory = $ctx.ToolDir + + $process = [System.Diagnostics.Process]::new() + $process.StartInfo = $startInfo + + $stdoutBuilder = [System.Text.StringBuilder]::new() + $stderrBuilder = [System.Text.StringBuilder]::new() + + $stdoutEvent = Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -Action { + if ($null -ne $EventArgs.Data) { + $Event.MessageData.AppendLine($EventArgs.Data) | Out-Null + } + } -MessageData $stdoutBuilder + + $stderrEvent = Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -Action { + if ($null -ne $EventArgs.Data) { + $Event.MessageData.AppendLine($EventArgs.Data) | Out-Null + } + } -MessageData $stderrBuilder + + try { + $process.Start() | Out-Null + $process.BeginOutputReadLine() + $process.BeginErrorReadLine() + + if ($startInfo.RedirectStandardInput) { + $process.StandardInput.WriteLine($StdinLine) + $process.StandardInput.Close() + } + + $exited = $process.WaitForExit($TimeoutSeconds * 1000) + if (-not $exited) { + try { $process.Kill() } catch {} + throw "Process timed out after ${TimeoutSeconds}s" + } + + $process.WaitForExit() + } + finally { + Unregister-Event -SourceIdentifier $stdoutEvent.Name -ErrorAction SilentlyContinue + Unregister-Event -SourceIdentifier $stderrEvent.Name -ErrorAction SilentlyContinue + Remove-Job -Name $stdoutEvent.Name -Force -ErrorAction SilentlyContinue + Remove-Job -Name $stderrEvent.Name -Force -ErrorAction SilentlyContinue + } + + $stdout = $stdoutBuilder.ToString().Trim() + $stderr = $stderrBuilder.ToString().Trim() + $exitCode = $process.ExitCode + $process.Dispose() + + if ($exitCode -ne 0) { + $errorDetail = if ($stderr) { $stderr } elseif ($stdout) { $stdout } else { "Exit code $exitCode" } + throw "Tool failed (exit code $exitCode): $errorDetail" + } + + if (-not $ParseJson -or [string]::IsNullOrWhiteSpace($stdout)) { + return $stdout + } + + # Parse JSON from stdout, filtering out noise + $lines = $stdout -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } + + # Try parsing entire output first + try { + return ($stdout | ConvertFrom-Json) + } + catch {} + + # Strip leading non-JSON noise and try again + $jsonStartIndex = -1 + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match '^\s*[\[\{"]') { + $jsonStartIndex = $i + break + } + } + if ($jsonStartIndex -ge 0) { + $jsonRegion = ($lines[$jsonStartIndex..($lines.Count - 1)]) -join "`n" + try { + return ($jsonRegion | ConvertFrom-Json) + } + catch {} + } + + # Try each line — find the last valid JSON line + $jsonResult = $null + foreach ($line in $lines) { + try { + $jsonResult = $line | ConvertFrom-Json + } + catch {} + } + + if ($null -ne $jsonResult) { + return $jsonResult + } + + return $stdout +} + +function Invoke-SgJSafeguardApi { + <# + .SYNOPSIS + Convenience wrapper for calling Safeguard API via SafeguardJavaTool. + #> + [CmdletBinding()] + param( + [Parameter()] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [ValidateSet("Core", "Appliance", "Notification", "A2A")] + [string]$Service, + + [Parameter(Mandatory)] + [ValidateSet("Get", "Post", "Put", "Delete")] + [string]$Method, + + [Parameter(Mandatory)] + [string]$RelativeUrl, + + [Parameter()] + $Body, + + [Parameter()] + [string]$Username, + + [Parameter()] + [string]$Password, + + [Parameter()] + [switch]$Anonymous, + + [Parameter()] + [string]$AccessToken, + + [Parameter()] + [switch]$Full, + + [Parameter()] + [hashtable]$Headers, + + [Parameter()] + [hashtable]$Parameters, + + [Parameter()] + [bool]$ParseJson = $true + ) + + if (-not $Context) { $Context = Get-SgJTestContext } + + $toolArgs = "-a $($Context.Appliance) -x -s $Service -m $Method -U `"$RelativeUrl`"" + + $stdinLine = $null + + if ($Anonymous) { + $toolArgs += " -A" + } + elseif ($AccessToken) { + $toolArgs += " -k `"$AccessToken`"" + } + else { + $effectiveUser = if ($Username) { $Username } else { $Context.AdminUserName } + $effectivePass = if ($Password) { $Password } else { $Context.AdminPassword } + $provider = "local" + $toolArgs += " -u $effectiveUser -i $provider -p" + $stdinLine = $effectivePass + } + + if ($Body) { + $bodyStr = if ($Body -is [hashtable] -or $Body -is [System.Collections.IDictionary] -or $Body -is [PSCustomObject]) { + (ConvertTo-Json -Depth 12 $Body -Compress).Replace('"', "'") + } + else { + [string]$Body + } + $toolArgs += " -b `"$bodyStr`"" + } + + if ($Full) { $toolArgs += " -f" } + + if ($Headers -and $Headers.Count -gt 0) { + $headerPairs = ($Headers.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "," + $toolArgs += " -H `"$headerPairs`"" + } + + if ($Parameters -and $Parameters.Count -gt 0) { + $paramPairs = ($Parameters.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "," + $toolArgs += " -P `"$paramPairs`"" + } + + Write-Verbose "Invoke-SgJSafeguardApi: $toolArgs" + + return Invoke-SgJSafeguardTool -Arguments $toolArgs -StdinLine $stdinLine -ParseJson $ParseJson +} + +function Invoke-SgJTokenCommand { + <# + .SYNOPSIS + Runs a token management command via SafeguardJavaTool. + #> + [CmdletBinding()] + param( + [Parameter()] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [ValidateSet("TokenLifetime", "GetToken")] + [string]$Command, + + [Parameter()] + [string]$Username, + + [Parameter()] + [string]$Password + ) + + if (-not $Context) { $Context = Get-SgJTestContext } + + $effectiveUser = if ($Username) { $Username } else { $Context.AdminUserName } + $effectivePass = if ($Password) { $Password } else { $Context.AdminPassword } + + $toolArgs = "-a $($Context.Appliance) -x -u $effectiveUser -i local -p" + + switch ($Command) { + "TokenLifetime" { $toolArgs += " -T" } + "GetToken" { $toolArgs += " -G" } + } + + return Invoke-SgJSafeguardTool -Arguments $toolArgs -StdinLine $effectivePass +} + +# ============================================================================ +# Object Management +# ============================================================================ + +function Remove-SgJStaleTestObject { + <# + .SYNOPSIS + Removes a test object by name from a collection, if it exists. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [string]$Collection, + + [Parameter(Mandatory)] + [string]$Name + ) + + try { + $items = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "${Collection}?filter=Name eq '${Name}'" + $itemList = @($items) + foreach ($item in $itemList) { + if ($item.Name -eq $Name) { + Write-Host " Removing stale $Collection object: $Name (Id=$($item.Id))" -ForegroundColor DarkYellow + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "${Collection}/$($item.Id)" -ParseJson $false + } + } + } + catch { + Write-Verbose "Pre-cleanup of $Collection/$Name skipped: $($_.Exception.Message)" + } +} + +function Remove-SgJSafeguardTestObject { + <# + .SYNOPSIS + Removes a Safeguard object by its direct URL. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [string]$RelativeUrl, + + [Parameter()] + [string]$Username, + + [Parameter()] + [string]$Password + ) + + $params = @{ + Context = $Context + Service = "Core" + Method = "Delete" + RelativeUrl = $RelativeUrl + ParseJson = $false + } + if ($Username) { $params["Username"] = $Username } + if ($Password) { $params["Password"] = $Password } + + try { + Invoke-SgJSafeguardApi @params + } + catch { + Write-Verbose "Cleanup of $RelativeUrl skipped: $($_.Exception.Message)" + } +} + +function Clear-SgJStaleTestEnvironment { + <# + .SYNOPSIS + Removes all objects with the test prefix from previous runs. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $prefix = $Context.TestPrefix + foreach ($collection in @("AssetAccounts", "Assets", "Users")) { + try { + $items = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "${collection}?filter=Name contains '${prefix}'" + $itemList = @($items) + foreach ($item in $itemList) { + Write-Host " Removing stale: $collection/$($item.Name) (Id=$($item.Id))" -ForegroundColor DarkYellow + try { + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "${collection}/$($item.Id)" -ParseJson $false + } + catch { + Write-Verbose " Failed to remove $collection/$($item.Id): $($_.Exception.Message)" + } + } + } + catch { + Write-Verbose "Pre-cleanup of $collection skipped: $($_.Exception.Message)" + } + } +} + +# ============================================================================ +# Assertion Functions +# ============================================================================ + +function Test-SgJAssert { + <# + .SYNOPSIS + Runs a test assertion with continue-on-failure semantics. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Mandatory, Position = 1)] + [scriptblock]$Test + ) + + $ctx = Get-SgJTestContext + $sw = [System.Diagnostics.Stopwatch]::StartNew() + try { + $result = & $Test + $sw.Stop() + if ($result) { + Write-Host " PASS $Name" -ForegroundColor Green + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Pass" + Duration = $sw.Elapsed.TotalSeconds + Error = $null + }) + } + else { + Write-Host " FAIL $Name" -ForegroundColor Red + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Fail" + Duration = $sw.Elapsed.TotalSeconds + Error = "Assertion returned false" + }) + } + } + catch { + $sw.Stop() + Write-Host " FAIL $Name" -ForegroundColor Red + Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Fail" + Duration = $sw.Elapsed.TotalSeconds + Error = $_.Exception.Message + }) + } +} + +function Test-SgJAssertEqual { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Mandatory, Position = 1)] + $Expected, + + [Parameter(Mandatory, Position = 2)] + $Actual + ) + + Test-SgJAssert $Name { + if ($Expected -ne $Actual) { + throw "Expected '$Expected' but got '$Actual'" + } + $true + } +} + +function Test-SgJAssertNotNull { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Position = 1)] + $Value + ) + + Test-SgJAssert $Name { + if ($null -eq $Value) { + throw "Expected non-null value" + } + $true + } +} + +function Test-SgJAssertContains { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Mandatory, Position = 1)] + [string]$Haystack, + + [Parameter(Mandatory, Position = 2)] + [string]$Needle + ) + + Test-SgJAssert $Name { + if (-not $Haystack.Contains($Needle)) { + throw "String does not contain '$Needle'" + } + $true + } +} + +function Test-SgJAssertThrows { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Mandatory, Position = 1)] + [scriptblock]$Test + ) + + $ctx = Get-SgJTestContext + $sw = [System.Diagnostics.Stopwatch]::StartNew() + try { + & $Test + $sw.Stop() + Write-Host " FAIL $Name (expected exception, none thrown)" -ForegroundColor Red + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Fail" + Duration = $sw.Elapsed.TotalSeconds + Error = "Expected exception but none was thrown" + }) + } + catch { + $sw.Stop() + Write-Host " PASS $Name (threw: $($_.Exception.Message))" -ForegroundColor Green + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Pass" + Duration = $sw.Elapsed.TotalSeconds + Error = $null + }) + } +} + +function Test-SgJSkip { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Name, + + [Parameter(Mandatory, Position = 1)] + [string]$Reason + ) + + $ctx = Get-SgJTestContext + Write-Host " SKIP $Name ($Reason)" -ForegroundColor Yellow + $ctx.SuiteResults[-1].Assertions.Add([PSCustomObject]@{ + Name = $Name + Status = "Skip" + Duration = 0 + Error = $Reason + }) +} + +# ============================================================================ +# Suite Execution +# ============================================================================ + +function Invoke-SgJTestSuite { + <# + .SYNOPSIS + Runs a single test suite file through the Setup → Execute → Cleanup lifecycle. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$SuiteFile, + + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $def = & $SuiteFile + $shortName = (Split-Path -Leaf $SuiteFile) -replace '^Suite-', '' -replace '\.ps1$', '' + + # Reset per-suite state + $Context.SuiteData = @{} + $Context.CleanupActions.Clear() + + # Create result entry + $suiteResult = [PSCustomObject]@{ + Name = $shortName + FullName = $def.Name + Status = "Running" + StartTime = [DateTime]::UtcNow + Duration = 0 + Assertions = [System.Collections.Generic.List[PSCustomObject]]::new() + SetupError = $null + } + $Context.SuiteResults.Add($suiteResult) + + Write-Host "" + Write-Host ("─" * 60) -ForegroundColor DarkGray + Write-Host " Suite: $($def.Name)" -ForegroundColor Cyan + if ($def.Description) { + Write-Host " $($def.Description)" -ForegroundColor DarkGray + } + Write-Host ("─" * 60) -ForegroundColor DarkGray + + $sw = [System.Diagnostics.Stopwatch]::StartNew() + + # --- Setup --- + Write-Host " Setup..." -ForegroundColor Yellow + try { + & $def.Setup $Context + Write-Host " Setup complete." -ForegroundColor Green + } + catch { + $sw.Stop() + $suiteResult.SetupError = $_.Exception.Message + $suiteResult.Status = "SetupFailed" + $suiteResult.Duration = $sw.Elapsed.TotalSeconds + Write-Host " Setup FAILED: $($_.Exception.Message)" -ForegroundColor Red + # Still run cleanup + Write-Host " Cleanup (after setup failure)..." -ForegroundColor Yellow + Invoke-SgJTestCleanup -Context $Context + return + } + + # --- Execute --- + Write-Host " Execute..." -ForegroundColor Yellow + try { + & $def.Execute $Context + } + catch { + Write-Host " Execute phase error: $($_.Exception.Message)" -ForegroundColor Red + } + + # --- Cleanup --- + Write-Host " Cleanup..." -ForegroundColor Yellow + try { + & $def.Cleanup $Context + } + catch { + Write-Host " Suite cleanup error: $($_.Exception.Message)" -ForegroundColor DarkYellow + } + Invoke-SgJTestCleanup -Context $Context + + $sw.Stop() + $suiteResult.Duration = $sw.Elapsed.TotalSeconds + + # Determine suite status + $fails = @($suiteResult.Assertions | Where-Object { $_.Status -eq "Fail" }) + $suiteResult.Status = if ($fails.Count -gt 0) { "Failed" } else { "Passed" } +} + +# ============================================================================ +# Reporting +# ============================================================================ + +function Write-SgJTestReport { + <# + .SYNOPSIS + Writes a summary report to the console and returns the failure count. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $totalPass = 0 + $totalFail = 0 + $totalSkip = 0 + $totalSetupFail = 0 + + Write-Host "" + Write-Host ("=" * 66) -ForegroundColor Cyan + Write-Host " Test Results" -ForegroundColor Cyan + Write-Host ("=" * 66) -ForegroundColor Cyan + + foreach ($suite in $Context.SuiteResults) { + if ($suite.Status -eq "SetupFailed") { + $totalSetupFail++ + Write-Host " SETUP FAIL $($suite.FullName) ($([math]::Round($suite.Duration, 1))s)" -ForegroundColor Red + Write-Host " $($suite.SetupError)" -ForegroundColor DarkRed + continue + } + + $pass = @($suite.Assertions | Where-Object { $_.Status -eq "Pass" }).Count + $fail = @($suite.Assertions | Where-Object { $_.Status -eq "Fail" }).Count + $skip = @($suite.Assertions | Where-Object { $_.Status -eq "Skip" }).Count + $totalPass += $pass + $totalFail += $fail + $totalSkip += $skip + + $color = if ($fail -gt 0) { "Red" } else { "Green" } + $status = if ($fail -gt 0) { "FAIL" } else { "PASS" } + Write-Host " $status $($suite.FullName): $pass passed, $fail failed, $skip skipped ($([math]::Round($suite.Duration, 1))s)" -ForegroundColor $color + } + + Write-Host ("─" * 66) -ForegroundColor DarkGray + $totalDuration = [math]::Round(([DateTime]::UtcNow - $Context.StartTime).TotalSeconds, 1) + Write-Host " Total: $totalPass passed, $totalFail failed, $totalSkip skipped, $totalSetupFail setup failures ($totalDuration`s)" -ForegroundColor White + Write-Host ("=" * 66) -ForegroundColor Cyan + + return $totalFail + $totalSetupFail +} + +function Export-SgJTestReport { + <# + .SYNOPSIS + Exports test results to a JSON file. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string]$OutputPath, + + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $report = @{ + Appliance = $Context.Appliance + StartTime = $Context.StartTime.ToString("o") + EndTime = [DateTime]::UtcNow.ToString("o") + Suites = @($Context.SuiteResults | ForEach-Object { + @{ + Name = $_.Name + FullName = $_.FullName + Status = $_.Status + Duration = $_.Duration + SetupError = $_.SetupError + Assertions = @($_.Assertions | ForEach-Object { + @{ + Name = $_.Name + Status = $_.Status + Duration = $_.Duration + Error = $_.Error + } + }) + } + }) + } + + $report | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8 + Write-Host " Report exported to: $OutputPath" -ForegroundColor Green +} + +# ============================================================================ +# Build +# ============================================================================ + +function Build-SgJTestProjects { + <# + .SYNOPSIS + Builds the SDK and test tool. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context + ) + + $mvn = $Context.MavenCmd + + # Install SDK to local repo + Write-Host " Building SDK..." -ForegroundColor DarkGray + $result = & $mvn -f "$($Context.RepoRoot)/pom.xml" install -DskipTests -q 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "SDK build failed: $result" + } + + # Build and package the test tool + Write-Host " Building test tool..." -ForegroundColor DarkGray + $result = & $mvn -f "$($Context.ToolDir)/pom.xml" clean package -q 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "Test tool build failed: $result" + } +} + +# ============================================================================ +# Exports +# ============================================================================ + +Export-ModuleMember -Function @( + # Context + 'New-SgJTestContext' + 'Get-SgJTestContext' + + # Cleanup + 'Register-SgJTestCleanup' + 'Invoke-SgJTestCleanup' + + # Tool invocation + 'Invoke-SgJSafeguardTool' + 'Invoke-SgJSafeguardApi' + 'Invoke-SgJTokenCommand' + + # Object management + 'Remove-SgJStaleTestObject' + 'Remove-SgJSafeguardTestObject' + 'Clear-SgJStaleTestEnvironment' + + # Assertions + 'Test-SgJAssert' + 'Test-SgJAssertEqual' + 'Test-SgJAssertNotNull' + 'Test-SgJAssertContains' + 'Test-SgJAssertThrows' + 'Test-SgJSkip' + + # Suite execution + 'Invoke-SgJTestSuite' + + # Reporting + 'Write-SgJTestReport' + 'Export-SgJTestReport' + + # Build + 'Build-SgJTestProjects' +) diff --git a/TestFramework/Suites/Suite-AnonymousAccess.ps1 b/TestFramework/Suites/Suite-AnonymousAccess.ps1 new file mode 100644 index 0000000..73d0b6a --- /dev/null +++ b/TestFramework/Suites/Suite-AnonymousAccess.ps1 @@ -0,0 +1,31 @@ +@{ + Name = "Anonymous Access" + Description = "Tests unauthenticated access to the Notification service" + Tags = @("core", "anonymous") + + Setup = { + param($Context) + # No setup needed - anonymous endpoints require no credentials or test objects. + } + + Execute = { + param($Context) + + Test-SgJAssert "Anonymous Notification Status endpoint is reachable" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Notification -Method Get -RelativeUrl "Status" -Anonymous + $null -ne $result + } + + Test-SgJAssert "Anonymous response contains appliance state" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Notification -Method Get -RelativeUrl "Status" -Anonymous -ParseJson $false + $null -ne $result -and $result.Length -gt 0 + } + } + + Cleanup = { + param($Context) + # Nothing to clean up. + } +} diff --git a/TestFramework/Suites/Suite-ApiInvocation.ps1 b/TestFramework/Suites/Suite-ApiInvocation.ps1 new file mode 100644 index 0000000..7c4b933 --- /dev/null +++ b/TestFramework/Suites/Suite-ApiInvocation.ps1 @@ -0,0 +1,147 @@ +@{ + Name = "API Invocation Patterns" + Description = "Tests HTTP methods, query parameters, PUT updates, InvokeMethodFull, and custom headers" + Tags = @("api", "core") + + Setup = { + param($Context) + + $prefix = $Context.TestPrefix + $adminUser = "${prefix}_ApiAdmin" + $adminPassword = "Test1234ApiAdmin!@#" + + # Pre-cleanup + Remove-SgJStaleTestObject -Context $Context -Collection "AssetAccounts" -Name "${prefix}_ApiAccount" + Remove-SgJStaleTestObject -Context $Context -Collection "Assets" -Name "${prefix}_ApiAsset" + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiDeleteMe" + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name $adminUser + + # Create admin user for privileged operations + $admin = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Body @{ + PrimaryAuthenticationProvider = @{ Id = -1 } + Name = $adminUser + AdminRoles = @('GlobalAdmin','Auditor','AssetAdmin','ApplianceAdmin','PolicyAdmin','UserAdmin','HelpdeskAdmin','OperationsAdmin') + } + $Context.SuiteData["AdminUserId"] = $admin.Id + $Context.SuiteData["AdminUser"] = $adminUser + $Context.SuiteData["AdminPassword"] = $adminPassword + Register-SgJTestCleanup -Description "Delete API admin user" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "Users/$($Ctx.SuiteData['AdminUserId'])" + } + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Put ` + -RelativeUrl "Users/$($admin.Id)/Password" -Body "'$adminPassword'" -ParseJson $false + + # Create an asset for testing non-User endpoints + $asset = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Assets" ` + -Username $adminUser -Password $adminPassword ` + -Body @{ + Name = "${prefix}_ApiAsset" + Description = "test asset for API invocation" + PlatformId = 188 + AssetPartitionId = -1 + NetworkAddress = "fake.api.address.com" + } + $Context.SuiteData["AssetId"] = $asset.Id + Register-SgJTestCleanup -Description "Delete test asset" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "Assets/$($Ctx.SuiteData['AssetId'])" ` + -Username $Ctx.SuiteData['AdminUser'] -Password $Ctx.SuiteData['AdminPassword'] + } + } + + Execute = { + param($Context) + + $adminUser = $Context.SuiteData["AdminUser"] + $adminPassword = $Context.SuiteData["AdminPassword"] + $prefix = $Context.TestPrefix + + # --- GET returns data --- + Test-SgJAssert "GET Users returns a list" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Users" + $items = @($result) + $items.Count -ge 1 + } + + # --- DELETE removes an object --- + Test-SgJAssert "DELETE removes an object" { + $tempUser = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Body @{ + PrimaryAuthenticationProvider = @{ Id = -1 } + Name = "${prefix}_ApiDeleteMe" + } + $tempId = $tempUser.Id + + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "Users/$tempId" -ParseJson $false + + $found = $true + try { + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Users/$tempId" + } + catch { + $found = $false + } + -not $found + } + + # --- PUT update --- + Test-SgJAssert "PUT updates an existing object" { + $assetId = $Context.SuiteData["AssetId"] + $newDesc = "updated by ApiInvocation test" + + $before = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Assets/$assetId" ` + -Username $adminUser -Password $adminPassword + + $updated = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Put ` + -RelativeUrl "Assets/$assetId" ` + -Username $adminUser -Password $adminPassword ` + -Body @{ + Id = $assetId + Name = $before.Name + Description = $newDesc + PlatformId = $before.PlatformId + AssetPartitionId = $before.AssetPartitionId + NetworkAddress = $before.NetworkAddress + } + + $updated.Description -eq $newDesc + } + + # --- InvokeMethodFull: GET returns 200 --- + Test-SgJAssert "Full response GET returns StatusCode 200" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -Full + $result.StatusCode -eq 200 + } + + # --- InvokeMethodFull: response contains Body --- + Test-SgJAssert "Full response Body contains user identity" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -Full + $body = $result.Body + if ($body -is [string]) { $body = $body | ConvertFrom-Json } + $null -ne $body.Name + } + + # --- GET against Notification service (anonymous) --- + Test-SgJAssert "GET against Notification service (anonymous) works" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Notification -Method Get ` + -RelativeUrl "Status" -Anonymous -ParseJson $false + $null -ne $result -and $result.Length -gt 0 + } + } + + Cleanup = { + param($Context) + # Registered cleanup handles everything. + } +} diff --git a/TestFramework/Suites/Suite-PasswordAuth.ps1 b/TestFramework/Suites/Suite-PasswordAuth.ps1 new file mode 100644 index 0000000..a286419 --- /dev/null +++ b/TestFramework/Suites/Suite-PasswordAuth.ps1 @@ -0,0 +1,66 @@ +@{ + Name = "Password Authentication" + Description = "Tests password-based authentication and basic admin API access" + Tags = @("auth", "core") + + Setup = { + param($Context) + + $prefix = $Context.TestPrefix + $testUser = "${prefix}_PwdAuthUser" + $testPassword = "Test1234Password!@#" + + # Pre-cleanup: remove stale objects from previous failed runs + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name $testUser + + # Create a test user with admin roles + $user = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Body @{ + PrimaryAuthenticationProvider = @{ Id = -1 } + Name = $testUser + AdminRoles = @('Auditor') + } + $Context.SuiteData["UserId"] = $user.Id + $Context.SuiteData["UserName"] = $testUser + $Context.SuiteData["UserPassword"] = $testPassword + + # Register cleanup IMMEDIATELY after creation + Register-SgJTestCleanup -Description "Delete password auth test user" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "Users/$($Ctx.SuiteData['UserId'])" + } + + # Set password + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Put ` + -RelativeUrl "Users/$($user.Id)/Password" -Body "'$testPassword'" -ParseJson $false + } + + Execute = { + param($Context) + + Test-SgJAssert "Bootstrap admin can connect and call Me endpoint" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" + $null -ne $result -and $null -ne $result.Id + } + + Test-SgJAssert "Test user can authenticate with password" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -Username $Context.SuiteData["UserName"] ` + -Password $Context.SuiteData["UserPassword"] + $result.Name -eq $Context.SuiteData["UserName"] + } + + Test-SgJAssert "Token lifetime is positive after authentication" { + $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime + $result.TokenLifetimeRemaining -gt 0 + } + } + + Cleanup = { + param($Context) + # Registered cleanup handles user deletion. + } +} diff --git a/TestFramework/Suites/Suite-TokenManagement.ps1 b/TestFramework/Suites/Suite-TokenManagement.ps1 new file mode 100644 index 0000000..9b5029b --- /dev/null +++ b/TestFramework/Suites/Suite-TokenManagement.ps1 @@ -0,0 +1,41 @@ +@{ + Name = "Token Management" + Description = "Tests access token retrieval and token lifetime operations" + Tags = @("auth", "token") + + Setup = { + param($Context) + # No setup needed - uses bootstrap admin credentials. + } + + Execute = { + param($Context) + + Test-SgJAssert "Token lifetime is positive after fresh connection" { + $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime + $null -ne $result.TokenLifetimeRemaining -and $result.TokenLifetimeRemaining -gt 0 + } + + Test-SgJAssert "GetToken returns a non-empty access token" { + $result = Invoke-SgJTokenCommand -Context $Context -Command GetToken + $null -ne $result.AccessToken -and $result.AccessToken.Length -gt 0 + } + + Test-SgJAssert "Access token can be used for subsequent API call" { + # Get a token + $tokenResult = Invoke-SgJTokenCommand -Context $Context -Command GetToken + $token = $tokenResult.AccessToken + + # Use it to call Me + $meResult = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -AccessToken $token + $null -ne $meResult -and $null -ne $meResult.Id + } + } + + Cleanup = { + param($Context) + # Nothing to clean up. + } +} From a0772e0daf23eedd0210766da6712b8d0ecec52e Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 08:31:20 -0600 Subject: [PATCH 07/22] Build system updates for SafeguardJava --- README.md | 57 +++++++++++++++++++++++++++++++++++++------ azure-pipelines.yml | 24 +++++++++++++++--- pom.xml | 25 ++++++------------- settings/settings.xml | 9 +++++-- 4 files changed, 86 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 698709c..789752b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ [![GitHub](https://img.shields.io/github/license/OneIdentity/SafeguardJava.svg)](https://github.com/OneIdentity/SafeguardJava/blob/master/LICENSE) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.oneidentity.safeguard/safeguardjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.oneidentity.safeguard/safeguardjava) -[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/r/https/oss.sonatype.org/com.oneidentity.safeguard/safeguardjava.svg)](https://oss.sonatype.org/content/repositories/releases/com/oneidentity/safeguard/safeguardjava/) -[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.oneidentity.safeguard/safeguardjava.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/oneidentity/safeguard/safeguardjava/) +[![Maven Central](https://img.shields.io/maven-central/v/com.oneidentity.safeguard/safeguardjava)](https://central.sonatype.com/artifact/com.oneidentity.safeguard/safeguardjava) # SafeguardJava @@ -16,7 +14,7 @@ One Identity open source projects are supported through [One Identity GitHub iss ## Default API Update -SafeguardDotNet will use v4 API by default starting with version 7.0. It is +SafeguardJava will use v4 API by default starting with version 7.0. It is possible to continue using the v3 API by passing in the apiVersion parameter when creating a connection or A2A context. @@ -197,14 +195,59 @@ public class CertificateValidator implements HostnameVerifier { ``` -### Building SafeguardJava +### Installation + +SafeguardJava is available from [Maven Central](https://central.sonatype.com/artifact/com.oneidentity.safeguard/safeguardjava) +and [GitHub Packages](https://github.com/OneIdentity/SafeguardJava/packages). JARs are also +available for direct download from [GitHub Releases](https://github.com/OneIdentity/SafeguardJava/releases). + +**Maven Central** (no additional repository configuration needed): + +```xml + + com.oneidentity.safeguard + safeguardjava + 7.5.0 + +``` -Building SafeguardJava requires Java JDK 8 or greater and Maven 3.0.5 or greater. The following dependency should be added to your POM file: +**GitHub Packages** (requires adding the GitHub Packages repository): ```xml + + + github + https://maven.pkg.github.com/OneIdentity/SafeguardJava + + + com.oneidentity.safeguard safeguardjava - 7.3.0 + 7.5.0 ``` + +### Building SafeguardJava + +Building SafeguardJava requires Java JDK 8 or greater and Maven 3.0.5 or greater. + +```bash +mvn clean package +``` + +### Publishing + +SafeguardJava is published to Maven Central via the +[Sonatype Central Portal](https://central.sonatype.com) and to GitHub Packages. +Release builds are triggered automatically by the Azure Pipeline when changes are +pushed to `master` or `release-*` branches. + +Publishing credentials and signing keys are stored in Azure Key Vault and injected +at build time. To configure publishing for a new environment: + +1. Generate a user token at [central.sonatype.com](https://central.sonatype.com) + and store it as `SonatypeUserToken` / `SonatypeRepositoryPassword` in your + Azure Key Vault +2. Store a GitHub PAT with `write:packages` scope as `GitHubPackagesToken` +3. Import the GPG signing key and code signing certificate into the Key Vault diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7e5f175..78581ee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,7 +12,7 @@ pool: # Maven Build Variables: variables: - version: '7.5.0.$(Build.BuildId)-SNAPSHOT' + version: '8.2.0.$(Build.BuildId)-SNAPSHOT' targetDir: 'target' codeSigningCertFileName: 'OneIdentityCodeSigning.pfx' issuerKeyStorePath: 'settings/signingstore.jks' @@ -32,7 +32,7 @@ steps: azureSubscription: 'OneIdentity.RD.SBox.Safeguard-ServiceConnection' keyVaultName: 'SafeguardBuildSecrets' secretsFilter: '*' - displayName: 'Get Sonatype password from Sandbox Azure Key Vault' + displayName: 'Get Sonatype Central Portal credentials from Azure Key Vault' condition: and(succeeded(), eq(variables.isReleaseBranch, true)) - task: AzureKeyVault@1 @@ -105,7 +105,25 @@ steps: publishJUnitResults: true testResultsFiles: '**/surefire-reports/TEST-*.xml' goals: 'deploy' - displayName: 'Build and deploy SafeguardJava $(version)' + displayName: 'Build and deploy SafeguardJava $(version) to Maven Central' + condition: and(succeeded(), eq(variables.isReleaseBranch, true)) + +- task: Bash@3 + inputs: + targetType: 'inline' + script: | + mvn deploy:deploy-file \ + -DrepositoryId=github \ + -Durl=https://maven.pkg.github.com/OneIdentity/SafeguardJava \ + -DpomFile=pom.xml \ + -Dfile=target/safeguardjava-$(version).jar \ + -Dsources=target/safeguardjava-$(version)-sources.jar \ + -Djavadoc=target/safeguardjava-$(version)-javadoc.jar \ + -Drevision=$(version) \ + -D githubusername=OneIdentity \ + -D githubtoken=$(GitHubPackagesToken) \ + --settings $(Build.SourcesDirectory)/settings/settings.xml + displayName: 'Publish to GitHub Packages' condition: and(succeeded(), eq(variables.isReleaseBranch, true)) - task: DeleteFiles@1 diff --git a/pom.xml b/pom.xml index 475a7d0..9ce43a1 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ UTF-8 - 7.5.0-SNAPSHOT + 8.2.0-SNAPSHOT ./signingcert.pfx 1 secret @@ -130,14 +130,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - false + central + false + validated @@ -241,16 +241,7 @@ - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + scm:git:git://github.com/OneIdentity/SafeguardJava diff --git a/settings/settings.xml b/settings/settings.xml index e911447..c27cb66 100644 --- a/settings/settings.xml +++ b/settings/settings.xml @@ -2,10 +2,15 @@ - ossrh + central ${sonatypeusername} ${sonatypepassword} + + github + ${githubusername} + ${githubtoken} + ${gpgkeyname} ${signingkeystorepassword} @@ -14,7 +19,7 @@ - ossrh + central true From 7df72217d769efdcbc0ada1fc9f2db06aa4ae9ad Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 16:32:03 -0600 Subject: [PATCH 08/22] implement request timeout default --- .../safeguardjava/restclient/RestClient.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index c1d0cce..b602997 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -72,6 +72,9 @@ public class RestClient { + /** Default timeout in milliseconds for HTTP requests (100 seconds, matching SafeguardDotNet). */ + public static final int DEFAULT_TIMEOUT_MS = 100_000; + private CloseableHttpClient client = null; private BasicCookieStore cookieStore = new BasicCookieStore(); @@ -520,13 +523,12 @@ private ClassicRequestBuilder prepareRequest(ClassicRequestBuilder rb, Map Date: Fri, 27 Mar 2026 17:03:54 -0600 Subject: [PATCH 09/22] Add PKCE authentication and resource owner grant management Implement OAuth2 PKCE (Proof Key for Code Exchange) authentication flow to achieve feature parity with SafeguardDotNet. This enables authentication against Safeguard appliances where the resource owner password grant type has been disabled. SDK changes: - Add PkceAuthenticator implementing the 6-step rSTS LoginController flow (init, primary auth, secondary auth/MFA, generate claims, extract code, exchange token) - Add AgentBasedLoginUtils with PKCE helpers (code verifier, code challenge, CSRF token generation, authorization code exchange, login response) - Add Safeguard.connectPkce() factory methods (3 overloads supporting ignoreSsl, HostnameVerifier callback, and optional MFA password) Test tool changes: - Add --pkce flag for PKCE authentication in the CLI test tool - Add -R/--resource-owner flag to enable/disable the `Allowed OAuth2 Grant Types` setting via PKCE, matching SafeguardDotNet's pattern - Fix test tool dependency version (7.5.0 -> 8.2.0) Test framework changes: - Add preflight check that detects whether resource owner password grant is enabled, enables it via PKCE if needed, and restores the original setting after all tests complete - Add Suite-PkceAuth with 5 tests exercising PKCE authentication in isolation (Me endpoint, token lifetime, token retrieval, token reuse, Settings read) - Add optional -Pkce switch to Invoke-SgJSafeguardApi and Invoke-SgJTokenCommand for explicit PKCE usage in individual tests All 19 tests pass (5 suites) against a Safeguard appliance with resource owner password grant disabled by default. --- TestFramework/Invoke-SafeguardTests.ps1 | 63 ++- TestFramework/SafeguardTestFramework.psm1 | 16 +- TestFramework/Suites/Suite-PkceAuth.ps1 | 49 ++ .../safeguardjava/AgentBasedLoginUtils.java | 165 +++++++ .../safeguard/safeguardjava/Safeguard.java | 95 ++++ .../authentication/PkceAuthenticator.java | 455 ++++++++++++++++++ tests/safeguardjavaclient/pom.xml | 2 +- .../safeguardclient/SafeguardJavaClient.java | 94 +++- .../safeguardclient/ToolOptions.java | 8 + 9 files changed, 941 insertions(+), 6 deletions(-) create mode 100644 TestFramework/Suites/Suite-PkceAuth.ps1 create mode 100644 src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java create mode 100644 src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java diff --git a/TestFramework/Invoke-SafeguardTests.ps1 b/TestFramework/Invoke-SafeguardTests.ps1 index feeaeff..f9bb616 100644 --- a/TestFramework/Invoke-SafeguardTests.ps1 +++ b/TestFramework/Invoke-SafeguardTests.ps1 @@ -32,6 +32,10 @@ .PARAMETER SkipBuild Skip building test projects (use when already built). +.PARAMETER Pkce + Use PKCE OAuth2 flow for password authentication instead of resource + owner password grant. Required when the appliance disables password grant. + .PARAMETER TestPrefix Prefix for test objects created on the appliance. Default: "SgJTest". @@ -73,6 +77,9 @@ param( [Parameter()] [switch]$SkipBuild, + [Parameter()] + [switch]$Pkce, + [Parameter()] [string]$TestPrefix = "SgJTest", @@ -202,6 +209,9 @@ Write-Host ("=" * 66) -ForegroundColor Cyan Write-Host " Appliance: $Appliance" -ForegroundColor White Write-Host " Maven: $MavenCmd" -ForegroundColor White Write-Host " Suites: $(@($selectedSuites).Count) selected" -ForegroundColor White +if ($Pkce) { + Write-Host " PKCE: Enabled" -ForegroundColor Yellow +} Write-Host ("=" * 66) -ForegroundColor Cyan $context = New-SgJTestContext ` @@ -209,7 +219,8 @@ $context = New-SgJTestContext ` -AdminUserName $AdminUserName ` -AdminPassword $AdminPassword ` -TestPrefix $TestPrefix ` - -MavenCmd $MavenCmd + -MavenCmd $MavenCmd ` + -Pkce:$Pkce # --- Build --- if (-not $SkipBuild) { @@ -225,6 +236,43 @@ if (-not $SkipBuild) { } } +# --- Preflight: Check resource owner password grant --- +Write-Host "" +Write-Host "Preflight: checking resource owner password grant..." -ForegroundColor Yellow +$rogEnabled = $false +try { + $rstsResponse = Invoke-WebRequest -Uri "https://$Appliance/RSTS/oauth2/token" ` + -Method Post -ContentType "application/x-www-form-urlencoded" ` + -Body "grant_type=password&username=$AdminUserName&password=$AdminPassword&scope=rsts:sts:primaryproviderid:local" ` + -SkipCertificateCheck -SkipHttpErrorCheck -ErrorAction Stop + $rstsBody = $rstsResponse.Content | ConvertFrom-Json + if ($rstsResponse.StatusCode -eq 200 -and $rstsBody.access_token) { + Write-Host " Resource owner grant is enabled." -ForegroundColor Green + $rogEnabled = $true + } elseif ($rstsBody.error_description -match "not allowed") { + Write-Host " Resource owner grant is disabled. Enabling via PKCE..." -ForegroundColor Yellow + } else { + Write-Host " Unexpected RSTS response ($($rstsResponse.StatusCode)): $($rstsResponse.Content)" -ForegroundColor Red + exit 1 + } +} catch { + Write-Host " Failed to reach RSTS: $($_.Exception.Message)" -ForegroundColor Red + exit 1 +} + +$resourceOwnerWasDisabled = $false +if (-not $rogEnabled) { + try { + $pkceResult = Invoke-SgJSafeguardTool -Arguments "-a $Appliance -x -u $AdminUserName -i local -p --pkce -R true" ` + -StdinLine $AdminPassword -ParseJson $true + Write-Host " Enabled resource owner grant (was: '$($pkceResult.PreviousValue)')." -ForegroundColor Green + $resourceOwnerWasDisabled = $true + } catch { + Write-Host " Failed to enable resource owner grant: $($_.Exception.Message)" -ForegroundColor Red + exit 1 + } +} + # --- Global pre-cleanup --- Write-Host "" Write-Host "Pre-cleanup: removing stale objects from previous runs..." -ForegroundColor Yellow @@ -235,6 +283,19 @@ foreach ($suiteFile in $selectedSuites) { Invoke-SgJTestSuite -SuiteFile $suiteFile.FullName -Context $context } +# --- Restore resource owner grant setting --- +if ($resourceOwnerWasDisabled) { + Write-Host "" + Write-Host "Restoring: disabling resource owner grant (was disabled before tests)..." -ForegroundColor Yellow + try { + $null = Invoke-SgJSafeguardTool -Arguments "-a $Appliance -x -u $AdminUserName -i local -p --pkce -R false" ` + -StdinLine $AdminPassword -ParseJson $true + Write-Host " Resource owner grant restored to disabled." -ForegroundColor Green + } catch { + Write-Host " Warning: failed to restore resource owner grant: $($_.Exception.Message)" -ForegroundColor DarkYellow + } +} + # --- Report --- $failCount = Write-SgJTestReport -Context $context diff --git a/TestFramework/SafeguardTestFramework.psm1 b/TestFramework/SafeguardTestFramework.psm1 index 5bd77be..314c45f 100644 --- a/TestFramework/SafeguardTestFramework.psm1 +++ b/TestFramework/SafeguardTestFramework.psm1 @@ -43,7 +43,10 @@ function New-SgJTestContext { [string]$TestPrefix = "SgJTest", [Parameter()] - [string]$MavenCmd + [string]$MavenCmd, + + [Parameter()] + [switch]$Pkce ) $repoRoot = Split-Path -Parent $PSScriptRoot @@ -61,6 +64,7 @@ function New-SgJTestContext { TestRoot = $PSScriptRoot ToolDir = (Join-Path $repoRoot "tests" "safeguardjavaclient") MavenCmd = $MavenCmd + Pkce = [bool]$Pkce # Per-suite transient data (reset each suite) SuiteData = @{} @@ -319,6 +323,9 @@ function Invoke-SgJSafeguardApi { [Parameter()] [string]$AccessToken, + [Parameter()] + [switch]$Pkce, + [Parameter()] [switch]$Full, @@ -349,6 +356,7 @@ function Invoke-SgJSafeguardApi { $effectivePass = if ($Password) { $Password } else { $Context.AdminPassword } $provider = "local" $toolArgs += " -u $effectiveUser -i $provider -p" + if ($Pkce) { $toolArgs += " --pkce" } $stdinLine = $effectivePass } @@ -397,7 +405,10 @@ function Invoke-SgJTokenCommand { [string]$Username, [Parameter()] - [string]$Password + [string]$Password, + + [Parameter()] + [switch]$Pkce ) if (-not $Context) { $Context = Get-SgJTestContext } @@ -406,6 +417,7 @@ function Invoke-SgJTokenCommand { $effectivePass = if ($Password) { $Password } else { $Context.AdminPassword } $toolArgs = "-a $($Context.Appliance) -x -u $effectiveUser -i local -p" + if ($Pkce) { $toolArgs += " --pkce" } switch ($Command) { "TokenLifetime" { $toolArgs += " -T" } diff --git a/TestFramework/Suites/Suite-PkceAuth.ps1 b/TestFramework/Suites/Suite-PkceAuth.ps1 new file mode 100644 index 0000000..972b0f6 --- /dev/null +++ b/TestFramework/Suites/Suite-PkceAuth.ps1 @@ -0,0 +1,49 @@ +@{ + Name = "PKCE Authentication" + Description = "Tests PKCE (Proof Key for Code Exchange) authentication in isolation" + Tags = @("auth", "pkce") + + Setup = { + param($Context) + # No setup needed — uses bootstrap admin credentials via PKCE + } + + Execute = { + param($Context) + + Test-SgJAssert "PKCE bootstrap admin can call Me endpoint" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" -Pkce + $null -ne $result -and $result.Name -eq $Context.AdminUserName.ToLower() + } + + Test-SgJAssert "PKCE token lifetime is positive" { + $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime -Pkce + $result.TokenLifetimeRemaining -gt 0 + } + + Test-SgJAssert "PKCE GetToken returns a non-empty access token" { + $result = Invoke-SgJTokenCommand -Context $Context -Command GetToken -Pkce + $null -ne $result.AccessToken -and $result.AccessToken.Length -gt 0 + } + + Test-SgJAssert "PKCE access token can be used for subsequent API call" { + $tokenResult = Invoke-SgJTokenCommand -Context $Context -Command GetToken -Pkce + $meResult = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -AccessToken $tokenResult.AccessToken + $null -ne $meResult -and $null -ne $meResult.Id + } + + Test-SgJAssert "PKCE can read appliance settings" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Settings" -Pkce + $result -is [array] -and $result.Count -gt 0 + } + } + + Cleanup = { + param($Context) + # No cleanup needed. + } +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java new file mode 100644 index 0000000..febac19 --- /dev/null +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java @@ -0,0 +1,165 @@ +package com.oneidentity.safeguard.safeguardjava; + +import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; +import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Map; +import java.util.logging.Logger; +import javax.net.ssl.HostnameVerifier; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; + +/** + * Utility class for agent-based and PKCE OAuth2 login flows. + * Provides helpers for generating PKCE code verifiers, code challenges, + * CSRF tokens, and completing the authorization code exchange. + */ +public final class AgentBasedLoginUtils { + + private static final Logger logger = Logger.getLogger(AgentBasedLoginUtils.class.getName()); + + /** Standard redirect URI for installed applications. */ + public static final String REDIRECT_URI = "urn:InstalledApplication"; + + /** Redirect URI for TCP listener-based authentication. */ + public static final String REDIRECT_URI_TCP_LISTENER = "urn:InstalledApplicationTcpListener"; + + private AgentBasedLoginUtils() { + } + + /** + * Generates a cryptographically random code verifier for PKCE OAuth2 flow. + * + * @return A base64url-encoded code verifier string. + */ + public static String oAuthCodeVerifier() { + byte[] bytes = new byte[60]; + new SecureRandom().nextBytes(bytes); + return toBase64Url(bytes); + } + + /** + * Generates a PKCE code challenge from a code verifier using SHA-256. + * + * @param codeVerifier The code verifier string. + * @return A base64url-encoded SHA-256 hash of the code verifier. + */ + public static String oAuthCodeChallenge(String codeVerifier) { + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + byte[] hash = sha256.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII)); + return toBase64Url(hash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } + + /** + * Generates a cryptographically random CSRF token. + * + * @return A base64url-encoded random token string. + */ + public static String generateCsrfToken() { + byte[] bytes = new byte[32]; + new SecureRandom().nextBytes(bytes); + return toBase64Url(bytes); + } + + /** + * Posts the OAuth2 authorization code to complete the PKCE flow and obtain + * an RSTS access token. + * + * @param appliance Network address of the Safeguard appliance. + * @param authorizationCode The authorization code from the authorization endpoint. + * @param codeVerifier The PKCE code verifier matching the original code challenge. + * @param redirectUri The redirect URI used in the authorization request. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @return The RSTS access token as a char array. + * @throws SafeguardForJavaException If the token exchange fails. + */ + public static char[] postAuthorizationCodeFlow(String appliance, String authorizationCode, + String codeVerifier, String redirectUri, boolean ignoreSsl, + HostnameVerifier validationCallback) throws SafeguardForJavaException { + + String rstsUrl = String.format("https://%s/RSTS", appliance); + RestClient rstsClient = new RestClient(rstsUrl, ignoreSsl, validationCallback); + + String body = String.format( + "{\"grant_type\":\"authorization_code\",\"redirect_uri\":\"%s\",\"code\":\"%s\",\"code_verifier\":\"%s\"}", + redirectUri, authorizationCode, codeVerifier); + + CloseableHttpResponse response = rstsClient.execPOST("oauth2/token", null, null, null, + new com.oneidentity.safeguard.safeguardjava.data.JsonBody(body)); + + if (response == null) { + throw new SafeguardForJavaException( + String.format("Unable to connect to RSTS service %s", rstsUrl)); + } + + String reply = Utils.getResponse(response); + if (!Utils.isSuccessful(response.getCode())) { + throw new SafeguardForJavaException( + "Error exchanging authorization code for RSTS token, Error: " + + String.format("%d %s", response.getCode(), reply)); + } + + Map map = Utils.parseResponse(reply); + if (!map.containsKey("access_token")) { + throw new SafeguardForJavaException("RSTS token response did not contain an access_token"); + } + + return map.get("access_token").toCharArray(); + } + + /** + * Posts the RSTS access token to the Safeguard API to obtain a Safeguard user token. + * + * @param appliance Network address of the Safeguard appliance. + * @param rstsAccessToken The RSTS access token from the OAuth2 flow. + * @param apiVersion Target API version to use. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @return A map containing the login response (includes UserToken and Status). + * @throws SafeguardForJavaException If the token exchange fails. + */ + public static Map postLoginResponse(String appliance, char[] rstsAccessToken, + int apiVersion, boolean ignoreSsl, + HostnameVerifier validationCallback) throws SafeguardForJavaException { + + String coreUrl = String.format("https://%s/service/core/v%d", appliance, apiVersion); + RestClient coreClient = new RestClient(coreUrl, ignoreSsl, validationCallback); + + String body = String.format("{\"StsAccessToken\":\"%s\"}", new String(rstsAccessToken)); + + CloseableHttpResponse response = coreClient.execPOST("Token/LoginResponse", null, null, null, + new com.oneidentity.safeguard.safeguardjava.data.JsonBody(body)); + + if (response == null) { + throw new SafeguardForJavaException( + String.format("Unable to connect to web service %s", coreUrl)); + } + + String reply = Utils.getResponse(response); + if (!Utils.isSuccessful(response.getCode())) { + throw new SafeguardForJavaException( + "Error exchanging RSTS token for Safeguard API access token, Error: " + + String.format("%d %s", response.getCode(), reply)); + } + + return Utils.parseResponse(reply); + } + + /** + * Converts a byte array to a base64url-encoded string (no padding). + */ + private static String toBase64Url(byte[] data) { + // Use java.util.Base64 which is available in Java 8+ + String base64 = java.util.Base64.getEncoder().encodeToString(data); + // Convert to base64url: replace + with -, / with _, remove trailing = + return base64.replace('+', '-').replace('/', '_').replaceAll("=+$", ""); + } +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java index dc8e9e3..57eea8f 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java @@ -5,6 +5,7 @@ import com.oneidentity.safeguard.safeguardjava.authentication.CertificateAuthenticator; import com.oneidentity.safeguard.safeguardjava.authentication.IAuthenticationMechanism; import com.oneidentity.safeguard.safeguardjava.authentication.PasswordAuthenticator; +import com.oneidentity.safeguard.safeguardjava.authentication.PkceAuthenticator; import com.oneidentity.safeguard.safeguardjava.event.ISafeguardEventListener; import com.oneidentity.safeguard.safeguardjava.event.PersistentSafeguardA2AEventListener; import com.oneidentity.safeguard.safeguardjava.event.PersistentSafeguardEventListener; @@ -139,6 +140,100 @@ public static ISafeguardConnection connect(String networkAddress, String provide false, validationCallback)); } + /** + * Connect to Safeguard API using PKCE (Proof Key for Code Exchange) authentication. + * This method programmatically handles the OAuth2/PKCE flow without launching a browser. + * Use this method when the resource owner password grant type is not allowed. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name to use for authentication. + * @param password User password to use for authentication. + * @param apiVersion Target API version to use. + * @param ignoreSsl Ignore server certificate validation. + * @return Reusable Safeguard API connection. + * @throws ObjectDisposedException Object has already been disposed. + * @throws ArgumentException Invalid argument. + * @throws SafeguardForJavaException General Safeguard for Java exception. + */ + public static ISafeguardConnection connectPkce(String networkAddress, String provider, String username, + char[] password, Integer apiVersion, Boolean ignoreSsl) + throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { + int version = DEFAULTAPIVERSION; + if (apiVersion != null) { + version = apiVersion; + } + + boolean sslIgnore = false; + if (ignoreSsl != null) { + sslIgnore = ignoreSsl; + } + + return getConnection(new PkceAuthenticator(networkAddress, provider, username, password, version, + sslIgnore, null)); + } + + /** + * Connect to Safeguard API using PKCE (Proof Key for Code Exchange) authentication. + * This method programmatically handles the OAuth2/PKCE flow without launching a browser. + * Use this method when the resource owner password grant type is not allowed. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name to use for authentication. + * @param password User password to use for authentication. + * @param apiVersion Target API version to use. + * @param validationCallback Callback function to be executed during SSL certificate validation. + * @return Reusable Safeguard API connection. + * @throws ObjectDisposedException Object has already been disposed. + * @throws ArgumentException Invalid argument. + * @throws SafeguardForJavaException General Safeguard for Java exception. + */ + public static ISafeguardConnection connectPkce(String networkAddress, String provider, String username, + char[] password, HostnameVerifier validationCallback, Integer apiVersion) + throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { + int version = DEFAULTAPIVERSION; + if (apiVersion != null) { + version = apiVersion; + } + + return getConnection(new PkceAuthenticator(networkAddress, provider, username, password, version, + false, validationCallback)); + } + + /** + * Connect to Safeguard API using PKCE authentication with multi-factor authentication support. + * This method programmatically handles the OAuth2/PKCE flow with secondary authentication (MFA). + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name to use for authentication. + * @param password User password to use for authentication. + * @param secondaryPassword One-time password or code for MFA (null if not required). + * @param apiVersion Target API version to use. + * @param ignoreSsl Ignore server certificate validation. + * @return Reusable Safeguard API connection. + * @throws ObjectDisposedException Object has already been disposed. + * @throws ArgumentException Invalid argument. + * @throws SafeguardForJavaException General Safeguard for Java exception. + */ + public static ISafeguardConnection connectPkce(String networkAddress, String provider, String username, + char[] password, char[] secondaryPassword, Integer apiVersion, Boolean ignoreSsl) + throws ObjectDisposedException, ArgumentException, SafeguardForJavaException { + int version = DEFAULTAPIVERSION; + if (apiVersion != null) { + version = apiVersion; + } + + boolean sslIgnore = false; + if (ignoreSsl != null) { + sslIgnore = ignoreSsl; + } + + return getConnection(new PkceAuthenticator(networkAddress, provider, username, password, + secondaryPassword, version, sslIgnore, null)); + } + /** * Connect to Safeguard API using a certificate from the keystore. The * appropriate keystore must have been loaded in the java process. diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java new file mode 100644 index 0000000..c9655fc --- /dev/null +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java @@ -0,0 +1,455 @@ +package com.oneidentity.safeguard.safeguardjava.authentication; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.oneidentity.safeguard.safeguardjava.AgentBasedLoginUtils; +import com.oneidentity.safeguard.safeguardjava.Utils; +import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; +import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; +import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; +import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; +import java.io.IOException; +import java.net.URLEncoder; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Authenticator that uses PKCE (Proof Key for Code Exchange) OAuth2 flow + * to authenticate against Safeguard without requiring the password grant type. + *

+ * This authenticator programmatically simulates the browser-based OAuth2/PKCE + * flow by directly interacting with the rSTS login controller endpoints. + */ +public class PkceAuthenticator extends AuthenticatorBase { + + private static final Logger logger = Logger.getLogger(PkceAuthenticator.class.getName()); + + // rSTS login controller step numbers (from rSTS Login.js) + private static final String STEP_INIT = "1"; + private static final String STEP_PRIMARY_AUTH = "3"; + private static final String STEP_SECONDARY_INIT = "7"; + private static final String STEP_SECONDARY_AUTH = "5"; + private static final String STEP_GENERATE_CLAIMS = "6"; + + private boolean disposed; + + private final String provider; + private String resolvedProviderId; + private final String username; + private final char[] password; + private final char[] secondaryPassword; + + /** + * Creates a PKCE authenticator for primary authentication only. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name for authentication. + * @param password User password for authentication. + * @param apiVersion Target API version. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @throws ArgumentException If required arguments are null. + */ + public PkceAuthenticator(String networkAddress, String provider, String username, + char[] password, int apiVersion, boolean ignoreSsl, + HostnameVerifier validationCallback) throws ArgumentException { + this(networkAddress, provider, username, password, null, apiVersion, ignoreSsl, validationCallback); + } + + /** + * Creates a PKCE authenticator with support for multi-factor authentication. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name for authentication. + * @param password User password for authentication. + * @param secondaryPassword One-time password for MFA (null if not required). + * @param apiVersion Target API version. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @throws ArgumentException If required arguments are null. + */ + public PkceAuthenticator(String networkAddress, String provider, String username, + char[] password, char[] secondaryPassword, int apiVersion, boolean ignoreSsl, + HostnameVerifier validationCallback) throws ArgumentException { + super(networkAddress, apiVersion, ignoreSsl, validationCallback); + this.provider = provider; + this.username = username; + if (password == null) { + throw new ArgumentException("The password parameter can not be null"); + } + this.password = password.clone(); + this.secondaryPassword = secondaryPassword != null ? secondaryPassword.clone() : null; + } + + @Override + public String getId() { + return "PKCE"; + } + + @Override + protected char[] getRstsTokenInternal() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("PkceAuthenticator"); + } + + // Resolve the provider to an rSTS provider ID if needed + if (resolvedProviderId == null) { + resolvedProviderId = resolveIdentityProvider(provider); + } + + String csrfToken = AgentBasedLoginUtils.generateCsrfToken(); + String codeVerifier = AgentBasedLoginUtils.oAuthCodeVerifier(); + String codeChallenge = AgentBasedLoginUtils.oAuthCodeChallenge(codeVerifier); + String redirectUri = AgentBasedLoginUtils.REDIRECT_URI; + + CloseableHttpClient httpClient = createPkceHttpClient(getNetworkAddress(), csrfToken); + + try { + String pkceUrl = String.format( + "https://%s/RSTS/UserLogin/LoginController?response_type=code&code_challenge_method=S256&code_challenge=%s&redirect_uri=%s&loginRequestStep=", + getNetworkAddress(), codeChallenge, redirectUri); + + String primaryFormData = String.format( + "directoryComboBox=%s&usernameTextbox=%s&passwordTextbox=%s&csrfTokenTextbox=%s", + URLEncoder.encode(resolvedProviderId, "UTF-8"), + URLEncoder.encode(username, "UTF-8"), + URLEncoder.encode(new String(password), "UTF-8"), + URLEncoder.encode(csrfToken, "UTF-8")); + + // Step 1: Initialize + logger.log(Level.FINE, "Calling RSTS for provider initialization"); + rstsFormPost(httpClient, pkceUrl + STEP_INIT, primaryFormData); + + // Step 3: Primary authentication + logger.log(Level.FINE, "Calling RSTS for primary authentication"); + String primaryBody = rstsFormPost(httpClient, pkceUrl + STEP_PRIMARY_AUTH, primaryFormData); + + // Handle secondary authentication (MFA) if required + handleSecondaryAuthentication(httpClient, pkceUrl, primaryFormData, primaryBody); + + // Step 6: Generate claims + logger.log(Level.FINE, "Calling RSTS for generate claims"); + String claimsBody = rstsFormPost(httpClient, pkceUrl + STEP_GENERATE_CLAIMS, primaryFormData); + + // Extract authorization code from claims response + String authorizationCode = extractAuthorizationCode(claimsBody); + + // Exchange authorization code for RSTS access token + logger.log(Level.FINE, "Redeeming RSTS authorization code"); + char[] rstsAccessToken = AgentBasedLoginUtils.postAuthorizationCodeFlow( + getNetworkAddress(), authorizationCode, codeVerifier, redirectUri, + isIgnoreSsl(), getValidationCallback()); + + return rstsAccessToken; + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("PKCE authentication failed", e); + } finally { + try { + httpClient.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close PKCE HTTP client", e); + } + } + } + + private void handleSecondaryAuthentication(CloseableHttpClient httpClient, String pkceUrl, + String primaryFormData, String primaryAuthBody) throws SafeguardForJavaException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode primaryResponse; + try { + primaryResponse = mapper.readTree(primaryAuthBody); + } catch (Exception e) { + return; // Non-JSON response means no secondary auth info + } + + if (primaryResponse == null) { + return; + } + + JsonNode secondaryProviderNode = primaryResponse.get("SecondaryProviderID"); + if (secondaryProviderNode == null || secondaryProviderNode.isNull() + || Utils.isNullOrEmpty(secondaryProviderNode.asText())) { + return; // No MFA required + } + + String secondaryProviderId = secondaryProviderNode.asText(); + logger.log(Level.FINE, "Secondary authentication required, provider: {0}", secondaryProviderId); + + if (secondaryPassword == null) { + throw new SafeguardForJavaException( + String.format("Multi-factor authentication is required (provider: %s) " + + "but no secondary password was provided.", secondaryProviderId)); + } + + try { + // Step 7: Secondary provider initialization + logger.log(Level.FINE, "Calling RSTS for secondary provider initialization"); + String initBody = rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_INIT, primaryFormData); + + // Parse MFA state from secondary init response + String mfaState = ""; + try { + JsonNode initResponse = mapper.readTree(initBody); + if (initResponse != null && initResponse.has("State")) { + mfaState = initResponse.get("State").asText(""); + } + } catch (Exception e) { + // Proceed without state + } + + // Step 5: Submit secondary password (OTP) + String mfaFormData = primaryFormData + + "&secondaryLoginTextbox=" + URLEncoder.encode(new String(secondaryPassword), "UTF-8") + + "&secondaryAuthenticationStateTextbox=" + URLEncoder.encode(mfaState, "UTF-8"); + + logger.log(Level.FINE, "Calling RSTS for secondary authentication"); + rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_AUTH, mfaFormData); + + logger.log(Level.FINE, "Secondary authentication completed successfully"); + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("Secondary authentication failed", e); + } + } + + private String extractAuthorizationCode(String response) throws SafeguardForJavaException { + ObjectMapper mapper = new ObjectMapper(); + try { + JsonNode jsonObject = mapper.readTree(response); + JsonNode relyingPartyUrlNode = jsonObject.get("RelyingPartyUrl"); + + if (relyingPartyUrlNode == null || relyingPartyUrlNode.isNull() + || Utils.isNullOrEmpty(relyingPartyUrlNode.asText())) { + throw new SafeguardForJavaException( + "rSTS response did not contain a RelyingPartyUrl. " + + "The authentication process may be incomplete."); + } + + String relyingPartyUrl = relyingPartyUrlNode.asText(); + + // Parse the authorization code from the query string. + // The URL may be a URN (e.g., urn:InstalledApplication?code=xxx) + // or a standard HTTP URL, so we parse the query part manually. + int queryStart = relyingPartyUrl.indexOf('?'); + if (queryStart >= 0) { + String query = relyingPartyUrl.substring(queryStart + 1); + for (String param : query.split("&")) { + String[] pair = param.split("=", 2); + if (pair.length == 2 && "code".equals(pair[0])) { + return java.net.URLDecoder.decode(pair[1], "UTF-8"); + } + } + } + + throw new SafeguardForJavaException("rSTS response did not contain an authorization code"); + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("Failed to parse authorization code from rSTS response", e); + } + } + + private String resolveIdentityProvider(String provider) throws SafeguardForJavaException { + // If provider is null/empty or "local", use the local provider ID + if (Utils.isNullOrEmpty(provider) || provider.equalsIgnoreCase("local")) { + return "local"; + } + + // Use the existing resolveProviderToScope to find the matching provider, + // but we need the RstsProviderId, not the scope. For PKCE we resolve it + // by querying AuthenticationProviders and matching. + try { + String coreUrl = String.format("https://%s/service/core/v%d", getNetworkAddress(), getApiVersion()); + RestClient client = new RestClient(coreUrl, isIgnoreSsl(), getValidationCallback()); + + java.util.Map headers = new java.util.HashMap<>(); + headers.put(HttpHeaders.ACCEPT, "application/json"); + + CloseableHttpResponse response = client.execGET("AuthenticationProviders", null, headers, null); + + if (response == null) { + throw new SafeguardForJavaException("Unable to connect to Safeguard to resolve identity provider"); + } + + String reply = Utils.getResponse(response); + if (!Utils.isSuccessful(response.getCode())) { + throw new SafeguardForJavaException( + "Error requesting authentication providers, Error: " + + String.format("%d %s", response.getCode(), reply)); + } + + ObjectMapper mapper = new ObjectMapper(); + JsonNode providers = mapper.readTree(reply); + + if (providers != null && providers.isArray()) { + for (JsonNode p : providers) { + String rstsProviderId = p.has("RstsProviderId") ? p.get("RstsProviderId").asText() : ""; + String name = p.has("Name") ? p.get("Name").asText() : ""; + + if (rstsProviderId.equalsIgnoreCase(provider) || name.equalsIgnoreCase(provider)) { + return rstsProviderId; + } + } + // Broad match + for (JsonNode p : providers) { + String rstsProviderId = p.has("RstsProviderId") ? p.get("RstsProviderId").asText() : ""; + if (rstsProviderId.toLowerCase().contains(provider.toLowerCase())) { + return rstsProviderId; + } + } + } + + throw new SafeguardForJavaException( + String.format("Unable to find identity provider matching '%s'", provider)); + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("Unable to resolve identity provider", e); + } + } + + /** + * Posts form data to an rSTS login controller URL and returns the response body. + */ + private String rstsFormPost(CloseableHttpClient httpClient, String url, String formData) + throws SafeguardForJavaException { + try { + HttpPost post = new HttpPost(url); + post.setHeader(HttpHeaders.ACCEPT, "application/json"); + post.setEntity(new StringEntity(formData, ContentType.APPLICATION_FORM_URLENCODED)); + + CloseableHttpResponse response = httpClient.execute(post); + String body = ""; + if (response.getEntity() != null) { + body = EntityUtils.toString(response.getEntity()); + } + + int statusCode = response.getCode(); + // rSTS returns 203 for some secondary auth states — treat as non-error + if (statusCode >= 400) { + String errorMessage = !Utils.isNullOrEmpty(body) ? body.trim() : String.valueOf(statusCode); + throw new SafeguardForJavaException( + String.format("rSTS authentication error: %s (HTTP %d)", errorMessage, statusCode)); + } + + return body; + } catch (SafeguardForJavaException e) { + throw e; + } catch (ParseException | IOException e) { + throw new SafeguardForJavaException("Failed to communicate with rSTS login controller", e); + } + } + + private CloseableHttpClient createPkceHttpClient(String appliance, String csrfToken) { + try { + SSLConnectionSocketFactory sslsf; + if (isIgnoreSsl()) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] certs, String authType) { } + public void checkServerTrusted(X509Certificate[] certs, String authType) { } + }}, new java.security.SecureRandom()); + sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + } else if (getValidationCallback() != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] certs, String authType) { } + public void checkServerTrusted(X509Certificate[] certs, String authType) { } + }}, new java.security.SecureRandom()); + sslsf = new SSLConnectionSocketFactory(sslContext, getValidationCallback()); + } else { + sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("https", sslsf) + .build(); + BasicHttpClientConnectionManager connectionManager = + new BasicHttpClientConnectionManager(socketFactoryRegistry); + + // Set up cookie store with CSRF token + BasicCookieStore cookieStore = new BasicCookieStore(); + BasicClientCookie csrfCookie = new BasicClientCookie("CsrfToken", csrfToken); + csrfCookie.setDomain(appliance); + csrfCookie.setPath("/RSTS"); + cookieStore.addCookie(csrfCookie); + + return HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultCookieStore(cookieStore) + .build(); + } catch (Exception e) { + throw new RuntimeException("Failed to create PKCE HTTP client", e); + } + } + + @Override + public Object cloneObject() throws SafeguardForJavaException { + try { + PkceAuthenticator auth = new PkceAuthenticator(getNetworkAddress(), provider, username, + password, secondaryPassword, getApiVersion(), isIgnoreSsl(), getValidationCallback()); + auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); + return auth; + } catch (ArgumentException ex) { + logger.log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public void dispose() { + super.dispose(); + if (password != null) { + Arrays.fill(password, '0'); + } + if (secondaryPassword != null) { + Arrays.fill(secondaryPassword, '0'); + } + disposed = true; + } + + @Override + protected void finalize() throws Throwable { + try { + if (password != null) { + Arrays.fill(password, '0'); + } + if (secondaryPassword != null) { + Arrays.fill(secondaryPassword, '0'); + } + } finally { + disposed = true; + super.finalize(); + } + } +} diff --git a/tests/safeguardjavaclient/pom.xml b/tests/safeguardjavaclient/pom.xml index 3d87aab..6b22c5c 100644 --- a/tests/safeguardjavaclient/pom.xml +++ b/tests/safeguardjavaclient/pom.xml @@ -29,7 +29,7 @@ com.oneidentity.safeguard safeguardjava - 7.5.0-SNAPSHOT + 8.2.0-SNAPSHOT diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index b0b8263..3d312d8 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -1,5 +1,6 @@ package com.oneidentity.safeguard.safeguardclient; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -57,6 +58,12 @@ public static void main(String[] args) { try { ISafeguardConnection connection = createConnection(opts); + if (opts.resourceOwner != null) { + setResourceOwnerGrant(connection, opts.resourceOwner); + System.exit(0); + return; + } + if (opts.tokenLifetime) { int remaining = connection.getAccessTokenLifetimeRemaining(); ObjectNode json = mapper.createObjectNode(); @@ -117,10 +124,18 @@ private static ISafeguardConnection createConnection(ToolOptions opts) throws Ex password = new Scanner(System.in).nextLine().toCharArray(); } + // Resource owner toggle always uses PKCE + boolean usePkce = opts.pkce || opts.resourceOwner != null; + if (opts.username != null) { String provider = opts.identityProvider != null ? opts.identityProvider : "local"; - System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider); - return Safeguard.connect(opts.appliance, provider, opts.username, password, null, opts.insecure); + if (usePkce) { + System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider + " (PKCE)"); + return Safeguard.connectPkce(opts.appliance, provider, opts.username, password, (Integer) null, opts.insecure); + } else { + System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider); + return Safeguard.connect(opts.appliance, provider, opts.username, password, null, opts.insecure); + } } if (opts.certificateFile != null) { @@ -192,6 +207,81 @@ private static Map parseKeyValuePairs(String[] pairs) { return map; } + private static final String GRANT_TYPE_SETTING_NAME = "Allowed OAuth2 Grant Types"; + + private static void setResourceOwnerGrant(ISafeguardConnection connection, boolean enable) + throws Exception { + String settingsJson = connection.invokeMethod(Service.Core, Method.Get, "Settings", + null, null, null, null); + JsonNode settings = mapper.readTree(settingsJson); + + JsonNode grantSetting = null; + if (settings.isArray()) { + for (JsonNode node : settings) { + JsonNode nameNode = node.get("Name"); + if (nameNode != null && GRANT_TYPE_SETTING_NAME.equals(nameNode.asText())) { + grantSetting = node; + break; + } + } + } + if (grantSetting == null) { + throw new RuntimeException("Setting '" + GRANT_TYPE_SETTING_NAME + "' not found"); + } + + String currentValue = grantSetting.has("Value") && !grantSetting.get("Value").isNull() + ? grantSetting.get("Value").asText() : ""; + + String[] parts = currentValue.split(","); + java.util.List grantTypes = new java.util.ArrayList<>(); + for (String part : parts) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + grantTypes.add(trimmed); + } + } + + boolean hasResourceOwner = false; + for (String gt : grantTypes) { + if (gt.equalsIgnoreCase("ResourceOwner")) { + hasResourceOwner = true; + break; + } + } + + if (enable && !hasResourceOwner) { + grantTypes.add("ResourceOwner"); + } else if (!enable && hasResourceOwner) { + java.util.Iterator it = grantTypes.iterator(); + while (it.hasNext()) { + if (it.next().equalsIgnoreCase("ResourceOwner")) { + it.remove(); + } + } + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < grantTypes.size(); i++) { + if (i > 0) sb.append(", "); + sb.append(grantTypes.get(i)); + } + String newValue = sb.toString(); + + ObjectNode body = mapper.createObjectNode(); + body.put("Value", newValue); + connection.invokeMethod(Service.Core, Method.Put, + "Settings/" + java.net.URLEncoder.encode(GRANT_TYPE_SETTING_NAME, "UTF-8") + .replace("+", "%20"), + mapper.writeValueAsString(body), null, null, null); + + ObjectNode envelope = mapper.createObjectNode(); + envelope.put("Setting", GRANT_TYPE_SETTING_NAME); + envelope.put("PreviousValue", currentValue); + envelope.put("NewValue", newValue); + envelope.put("ResourceOwnerEnabled", enable); + System.out.println(mapper.writeValueAsString(envelope)); + } + private static void runInteractive() { ISafeguardConnection connection = null; diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java index 77a8685..5a26664 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -79,6 +79,14 @@ public class ToolOptions { description = "Output the current access token as JSON and exit") boolean getToken; + @Option(names = {"--pkce"}, defaultValue = "false", + description = "Use PKCE (Proof Key for Code Exchange) authentication instead of password grant") + boolean pkce; + + @Option(names = {"-R", "--resource-owner"}, arity = "1", + description = "Enable (true) or disable (false) the resource owner password grant type and exit") + Boolean resourceOwner; + @Option(names = {"-V", "--verbose"}, defaultValue = "false", description = "Display verbose debug output") boolean verbose; From e079251e3b951f145734eb145bae574c45c6d357 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 17:27:57 -0600 Subject: [PATCH 10/22] Replace java.util.logging with SLF4J across all SDK sources Migrate 16 files from JUL to SLF4J. Level mapping: FINEST->trace, FINE->debug, INFO->info, WARNING->warn, SEVERE->error. Fix incorrect class references in several logger calls. Consumers can now choose their preferred logging backend via standard SLF4J bindings. --- .../safeguardjava/AgentBasedLoginUtils.java | 5 +-- .../safeguardjava/SafeguardA2AContext.java | 26 +++++++------- .../safeguardjava/SafeguardConnection.java | 34 ++++++++++--------- .../SafeguardSessionsConnection.java | 14 ++++---- .../safeguard/safeguardjava/Utils.java | 9 ++--- .../authentication/AuthenticatorBase.java | 8 +++-- .../authentication/PasswordAuthenticator.java | 8 +++-- .../authentication/PkceAuthenticator.java | 26 +++++++------- .../safeguardjava/data/JoinRequest.java | 8 +++-- .../event/EventHandlerRegistry.java | 24 +++++-------- .../event/EventHandlerRunnable.java | 9 ++--- .../PersistentSafeguardA2AEventListener.java | 10 +++--- .../PersistentSafeguardEventListener.java | 8 +++-- .../PersistentSafeguardEventListenerBase.java | 19 +++++------ .../event/SafeguardEventListener.java | 21 ++++++------ .../safeguardjava/restclient/RestClient.java | 30 +++++++--------- 16 files changed, 133 insertions(+), 126 deletions(-) diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java index febac19..05fb2c9 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java @@ -8,7 +8,8 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Map; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -19,7 +20,7 @@ */ public final class AgentBasedLoginUtils { - private static final Logger logger = Logger.getLogger(AgentBasedLoginUtils.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(AgentBasedLoginUtils.class); /** Standard redirect URI for installed applications. */ public static final String REDIRECT_URI = "urn:InstalledApplication"; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java index 336f854..36093c2 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardA2AContext.java @@ -20,8 +20,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.oneidentity.safeguard.safeguardjava.event.ISafeguardEventHandler; import com.oneidentity.safeguard.safeguardjava.event.PersistentSafeguardA2AEventListener; import java.io.IOException; @@ -33,6 +33,8 @@ public class SafeguardA2AContext implements ISafeguardA2AContext { + private static final Logger logger = LoggerFactory.getLogger(SafeguardA2AContext.class); + private boolean disposed; private final String networkAddress; @@ -181,7 +183,7 @@ public char[] retrievePassword(char[] apiKey) throws ObjectDisposedException, Sa char[] password = parseJsonString(reply).toCharArray(); - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A password."); + logger.info("Successfully retrieved A2A password."); return password; } @@ -217,7 +219,7 @@ public void SetPassword(char[] apiKey, char[] password) throws ObjectDisposedExc + String.format("%s %s", response.getCode(), reply)); } - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A password."); + logger.info("Successfully set A2A password."); } @Override @@ -253,7 +255,7 @@ public char[] retrievePrivateKey(char[] apiKey, KeyFormat keyFormat) throws Obje char[] privateKey = parseJsonString(reply).toCharArray(); - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A private key."); + logger.info("Successfully retrieved A2A private key."); return privateKey; } @@ -301,7 +303,7 @@ public void SetPrivateKey(char[] apiKey, char[] privateKey, char[] password, Key + String.format("%s %s", response.getCode(), reply)); } - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully set A2A private key."); + logger.info("Successfully set A2A private key."); return; } @@ -367,7 +369,7 @@ public ISafeguardEventListener getA2AEventListener(char[] apiKey, ISafeguardEven eventListener.registerEventHandler("AssetAccountPasswordUpdated", handler); eventListener.registerEventHandler("AssetAccountSshKeyUpdated", handler); eventListener.registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Event listener successfully created for Safeguard A2A context."); + logger.info("Event listener successfully created for Safeguard A2A context."); return eventListener; } @@ -387,7 +389,7 @@ public ISafeguardEventListener getA2AEventListener(List apiKeys, ISafegu eventListener.registerEventHandler("AssetAccountPasswordUpdated", handler); eventListener.registerEventHandler("AssetAccountSshKeyUpdated", handler); eventListener.registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Event listener successfully created for Safeguard A2A context."); + logger.info("Event listener successfully created for Safeguard A2A context."); return eventListener; } @@ -458,7 +460,7 @@ public String brokerAccessRequest(char[] apiKey, IBrokeredAccessRequest accessRe + String.format("%s %s", response.getCode(), reply)); } - Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully created A2A access request."); + logger.info("Successfully created A2A access request."); return reply; } @@ -491,7 +493,7 @@ private List parseA2ARegistationResponse(String response) { A2ARegistration[] registrations = mapper.readValue(response, A2ARegistration[].class); return Arrays.asList(registrations); } catch (IOException ex) { - Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; @@ -505,7 +507,7 @@ private List parseA2ARetrievableAccountResponse(S A2ARetrievableAccountInternal[] accounts = mapper.readValue(response, A2ARetrievableAccountInternal[].class); return Arrays.asList(accounts); } catch (IOException ex) { - Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; @@ -519,7 +521,7 @@ private List parseApiKeySecretResponse(String response) { ApiKeySecretInternal[] apiKeySecrets = mapper.readValue(response, ApiKeySecretInternal[].class); return Arrays.asList(apiKeySecrets); } catch (IOException ex) { - Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java index 04ab166..ccb470a 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java @@ -18,14 +18,16 @@ import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.stream.Collectors; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; class SafeguardConnection implements ISafeguardConnection { + private static final Logger logger = LoggerFactory.getLogger(SafeguardConnection.class); + private boolean disposed; private final IAuthenticationMechanism authenticationMechanism; @@ -61,9 +63,9 @@ public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, Saf int lifetime = authenticationMechanism.getAccessTokenLifetimeRemaining(); if (lifetime > 0) { String msg = String.format("Access token lifetime remaining (in minutes): %d", lifetime); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, msg); + logger.trace(msg); } else - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Access token invalid or server unavailable"); + logger.trace("Access token invalid or server unavailable"); return lifetime; } @@ -73,7 +75,7 @@ public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJav throw new ObjectDisposedException("SafeguardConnection"); } authenticationMechanism.refreshAccessToken(); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Successfully obtained a new access token"); + logger.trace("Successfully obtained a new access token"); } @Override @@ -168,7 +170,7 @@ public FullResponse JoinSps(ISafeguardSessionsConnection spsConnection, String c request.setSpp_api_token(authenticationMechanism.getAccessToken()); request.setSpp_cert_chain(certificateChain); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Sending join request."); + logger.trace("Sending join request."); FullResponse joinResponse = spsConnection.invokeMethodFull(Method.Post, "cluster/spp", request.toJson()); logResponseDetails(joinResponse); @@ -181,7 +183,7 @@ public SafeguardEventListener getEventListener() throws ObjectDisposedException, SafeguardEventListener eventListener = new SafeguardEventListener( String.format("https://%s/service/event", authenticationMechanism.getNetworkAddress()), authenticationMechanism.getAccessToken(), authenticationMechanism.isIgnoreSsl(), authenticationMechanism.getValidationCallback()); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Event listener successfully created for Safeguard connection."); + logger.trace("Event listener successfully created for Safeguard connection."); return eventListener; } @@ -215,32 +217,32 @@ public void logOut() throws ObjectDisposedException { return; try { this.invokeMethodFull(Service.Core, Method.Post, "Token/Logout", null, null, null, null); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Successfully logged out"); + logger.trace("Successfully logged out"); } catch (Exception ex) { - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Exception occurred during logout", ex); + logger.trace("Exception occurred during logout", ex); } authenticationMechanism.clearAccessToken(); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Cleared access token"); + logger.trace("Cleared access token"); } static void logRequestDetails(Method method, String uri, Map parameters, Map headers) { String msg = String.format("Invoking method: %s %s", method.toString().toUpperCase(), uri); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, msg); + logger.trace(msg); msg = parameters == null ? "None" : parameters.keySet().stream().map(key -> key + "=" + parameters.get(key)).collect(Collectors.joining(", ", "{", "}")); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, " Query parameters: {0}", msg); + logger.trace(" Query parameters: {}", msg); msg = headers == null ? "None" : headers.keySet().stream().map(key -> key + "=" + headers.get(key)).collect(Collectors.joining(", ", "{", "}")); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, " Additional headers: {0}", msg); + logger.trace(" Additional headers: {}", msg); } static void logResponseDetails(FullResponse fullResponse) { - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, "Reponse status code: {0}", fullResponse.getStatusCode()); + logger.trace("Reponse status code: {}", fullResponse.getStatusCode()); String msg = fullResponse.getHeaders() == null ? "None" : fullResponse.getHeaders().stream().map(header -> header.getName() + "=" + header.getValue()).collect(Collectors.joining(", ", "{", "}")); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, " Response headers: {0}", msg); + logger.trace(" Response headers: {}", msg); msg = fullResponse.getBody() == null ? "None" : String.format("%d",fullResponse.getBody().length()); - Logger.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, " Body size: {0}", msg); + logger.trace(" Body size: {}", msg); } protected RestClient getClientForService(Service service) throws SafeguardForJavaException { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java index c47107f..4fd16dc 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java @@ -11,8 +11,8 @@ import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import org.apache.hc.core5.http.Header; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -22,6 +22,8 @@ */ class SafeguardSessionsConnection implements ISafeguardSessionsConnection { + private static final Logger logger = LoggerFactory.getLogger(SafeguardSessionsConnection.class); + private boolean disposed; private RestClient client; @@ -35,7 +37,7 @@ public SafeguardSessionsConnection(String networkAddress, String username, Map headers = new HashMap<>(); - Logger.getLogger(SafeguardSessionsConnection.class.getName()).log(Level.FINEST, "Starting authentication."); + logger.trace("Starting authentication."); logRequestDetails(Method.Get, client.getBaseURL() + "/" + "authentication", null, null); CloseableHttpResponse response = client.execGET("authentication", null, null, null); @@ -56,7 +58,7 @@ public SafeguardSessionsConnection(String networkAddress, String username, client.addSessionId(authCookie.getValue()); } - Logger.getLogger(SafeguardSessionsConnection.class.getName()).log(Level.FINEST, String.format("Response content: $s", reply)); + logger.trace(String.format("Response content: $s", reply)); } @Override @@ -91,7 +93,7 @@ public FullResponse invokeMethodFull(Method method, String relativeUrl, String b throw new ArgumentException("Parameter relativeUrl may not be null or empty"); } - Logger.getLogger(SafeguardSessionsConnection.class.getName()).log(Level.FINEST, String.format("Invoking method on sps: $s", relativeUrl)); + logger.trace(String.format("Invoking method on sps: $s", relativeUrl)); CloseableHttpResponse response = null; @@ -123,7 +125,7 @@ public FullResponse invokeMethodFull(Method method, String relativeUrl, String b + String.format("%d %s", response.getCode(), reply)); } - Logger.getLogger(SafeguardSessionsConnection.class.getName()).log(Level.FINEST, String.format("Invoking method finished: $s", reply)); + logger.trace(String.format("Invoking method finished: $s", reply)); FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), reply); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java index f727e10..749d93a 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java @@ -2,14 +2,13 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.oneidentity.safeguard.safeguardjava.authentication.PasswordAuthenticator; import java.io.IOException; import java.security.Provider; import java.security.Security; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -17,6 +16,8 @@ public class Utils { + private static final Logger logger = LoggerFactory.getLogger(Utils.class); + private static String OS = null; private Utils() { @@ -41,7 +42,7 @@ public static Map parseResponse(String response) { map = mapper.readValue(response, new TypeReference>() { }); } catch (IOException ex) { - Logger.getLogger(PasswordAuthenticator.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return map; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java index 3b3f2ed..b96005b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java @@ -14,14 +14,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; abstract class AuthenticatorBase implements IAuthenticationMechanism { + private static final Logger logger = LoggerFactory.getLogger(AuthenticatorBase.class); + private boolean disposed; private final String networkAddress; @@ -267,7 +269,7 @@ private List parseLoginResponse(String response) { providers.add(p); } } catch (IOException ex) { - Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return providers; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java index fda6bc7..6f78cb6 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java @@ -7,13 +7,15 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import java.util.Arrays; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class PasswordAuthenticator extends AuthenticatorBase { + private static final Logger logger = LoggerFactory.getLogger(PasswordAuthenticator.class); + private boolean disposed; private final String provider; @@ -78,7 +80,7 @@ public Object cloneObject() throws SafeguardForJavaException auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); return auth; } catch (ArgumentException ex) { - Logger.getLogger(PasswordAuthenticator.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java index c9655fc..f401579 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java @@ -12,8 +12,8 @@ import java.net.URLEncoder; import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; @@ -45,7 +45,7 @@ */ public class PkceAuthenticator extends AuthenticatorBase { - private static final Logger logger = Logger.getLogger(PkceAuthenticator.class.getName()); + private static final Logger logger = LoggerFactory.getLogger(PkceAuthenticator.class); // rSTS login controller step numbers (from rSTS Login.js) private static final String STEP_INIT = "1"; @@ -142,25 +142,25 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar URLEncoder.encode(csrfToken, "UTF-8")); // Step 1: Initialize - logger.log(Level.FINE, "Calling RSTS for provider initialization"); + logger.debug("Calling RSTS for provider initialization"); rstsFormPost(httpClient, pkceUrl + STEP_INIT, primaryFormData); // Step 3: Primary authentication - logger.log(Level.FINE, "Calling RSTS for primary authentication"); + logger.debug("Calling RSTS for primary authentication"); String primaryBody = rstsFormPost(httpClient, pkceUrl + STEP_PRIMARY_AUTH, primaryFormData); // Handle secondary authentication (MFA) if required handleSecondaryAuthentication(httpClient, pkceUrl, primaryFormData, primaryBody); // Step 6: Generate claims - logger.log(Level.FINE, "Calling RSTS for generate claims"); + logger.debug("Calling RSTS for generate claims"); String claimsBody = rstsFormPost(httpClient, pkceUrl + STEP_GENERATE_CLAIMS, primaryFormData); // Extract authorization code from claims response String authorizationCode = extractAuthorizationCode(claimsBody); // Exchange authorization code for RSTS access token - logger.log(Level.FINE, "Redeeming RSTS authorization code"); + logger.debug("Redeeming RSTS authorization code"); char[] rstsAccessToken = AgentBasedLoginUtils.postAuthorizationCodeFlow( getNetworkAddress(), authorizationCode, codeVerifier, redirectUri, isIgnoreSsl(), getValidationCallback()); @@ -174,7 +174,7 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar try { httpClient.close(); } catch (IOException e) { - logger.log(Level.WARNING, "Failed to close PKCE HTTP client", e); + logger.warn("Failed to close PKCE HTTP client", e); } } } @@ -200,7 +200,7 @@ private void handleSecondaryAuthentication(CloseableHttpClient httpClient, Strin } String secondaryProviderId = secondaryProviderNode.asText(); - logger.log(Level.FINE, "Secondary authentication required, provider: {0}", secondaryProviderId); + logger.debug("Secondary authentication required, provider: {}", secondaryProviderId); if (secondaryPassword == null) { throw new SafeguardForJavaException( @@ -210,7 +210,7 @@ private void handleSecondaryAuthentication(CloseableHttpClient httpClient, Strin try { // Step 7: Secondary provider initialization - logger.log(Level.FINE, "Calling RSTS for secondary provider initialization"); + logger.debug("Calling RSTS for secondary provider initialization"); String initBody = rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_INIT, primaryFormData); // Parse MFA state from secondary init response @@ -229,10 +229,10 @@ private void handleSecondaryAuthentication(CloseableHttpClient httpClient, Strin + "&secondaryLoginTextbox=" + URLEncoder.encode(new String(secondaryPassword), "UTF-8") + "&secondaryAuthenticationStateTextbox=" + URLEncoder.encode(mfaState, "UTF-8"); - logger.log(Level.FINE, "Calling RSTS for secondary authentication"); + logger.debug("Calling RSTS for secondary authentication"); rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_AUTH, mfaFormData); - logger.log(Level.FINE, "Secondary authentication completed successfully"); + logger.debug("Secondary authentication completed successfully"); } catch (SafeguardForJavaException e) { throw e; } catch (Exception e) { @@ -421,7 +421,7 @@ public Object cloneObject() throws SafeguardForJavaException { auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); return auth; } catch (ArgumentException ex) { - logger.log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java index 42f6c58..7e69134 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java @@ -4,11 +4,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JoinRequest implements JsonObject { + private static final Logger logger = LoggerFactory.getLogger(JoinRequest.class); + private String spp; private char[] spp_api_token; private String spp_cert_chain; @@ -46,7 +48,7 @@ public String toJson() throws SafeguardForJavaException { try { return ow.writeValueAsString(this); } catch (JsonProcessingException ex) { - Logger.getLogger(JoinRequest.class.getName()).log(Level.FINEST, null, ex); + logger.trace("Exception occurred", ex); throw new SafeguardForJavaException("Failed to convert request to json", ex); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java index f15b045..d0ee33d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java @@ -6,20 +6,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class EventHandlerRegistry { private static final Map> delegateRegistry = new HashMap<>(); - private final Logger logger = Logger.getLogger(getClass().getName()); + private static final Logger logger = LoggerFactory.getLogger(EventHandlerRegistry.class); private void handleEvent(String eventName, JsonElement eventBody) { if (!delegateRegistry.containsKey(eventName)) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.FINEST, - String.format("No handlers registered for event %s", eventName)); + logger.trace("No handlers registered for event {}", eventName); return; } @@ -28,10 +27,8 @@ private void handleEvent(String eventName, JsonElement eventBody) List handlers = delegateRegistry.get(eventName); for (ISafeguardEventHandler handler : handlers) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.INFO, - String.format("Calling handler for event %s", eventName)); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Event %s has body %s", eventName, eventBody)); + logger.info("Calling handler for event {}", eventName); + logger.warn("Event {} has body {}", eventName, eventBody); final EventHandlerRunnable handlerRunnable = new EventHandlerRunnable(handler, eventName, eventBody.toString()); final EventHandlerThread eventHandlerThread = new EventHandlerThread(handlerRunnable) { @@ -60,8 +57,7 @@ private Map parseEvents(JsonElement eventObject) { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Unable to parse event object %s", eventObject.toString())); + logger.warn("Unable to parse event object {}", eventObject.toString()); return null; } } @@ -74,8 +70,7 @@ public void handleEvent(JsonElement eventObject) for (Map.Entry eventInfo : events.entrySet()) { if (eventInfo.getKey() == null) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Found null event with body %s", eventInfo.getValue())); + logger.warn("Found null event with body {}", eventInfo.getValue()); continue; } handleEvent(eventInfo.getKey(), eventInfo.getValue()); @@ -89,7 +84,6 @@ public void registerEventHandler(String eventName, ISafeguardEventHandler handle } delegateRegistry.get(eventName).add(handler); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Registered a handler for event %s", eventName)); + logger.warn("Registered a handler for event {}", eventName); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java index fe63a6c..5cca70d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java @@ -1,10 +1,12 @@ package com.oneidentity.safeguard.safeguardjava.event; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class EventHandlerRunnable implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(EventHandlerRunnable.class); + private final ISafeguardEventHandler handler; private final String eventName; private final String eventBody; @@ -23,8 +25,7 @@ public void run() { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - "An error occured while calling onEventReceived"); + logger.warn("An error occured while calling onEventReceived"); } } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java index 7c31bcf..5f95cbb 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java @@ -7,8 +7,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class DefaultSafeguardEventHandler implements ISafeguardEventHandler { @@ -19,6 +19,8 @@ public void onEventReceived(String eventName, String eventBody) { public class PersistentSafeguardA2AEventListener extends PersistentSafeguardEventListenerBase { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardA2AEventListener.class); + private boolean disposed; private final ISafeguardA2AContext a2AContext; @@ -36,7 +38,7 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, char registerEventHandler("AssetAccountPasswordUpdated", handler); registerEventHandler("AssetAccountSshKeyUpdated", handler); registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); + logger.trace("Persistent A2A event listener successfully created."); } public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List apiKeys, ISafeguardEventHandler handler) @@ -56,7 +58,7 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List registerEventHandler("AssetAccountPasswordUpdated", handler); registerEventHandler("AssetAccountSshKeyUpdated", handler); registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); + logger.trace("Persistent A2A event listener successfully created."); } @Override diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java index 6fdb5d7..3dec584 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java @@ -4,18 +4,20 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PersistentSafeguardEventListener extends PersistentSafeguardEventListenerBase { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardEventListener.class); + private boolean disposed; private final ISafeguardConnection connection; public PersistentSafeguardEventListener(ISafeguardConnection connection) { this.connection = connection; - Logger.getLogger(PersistentSafeguardEventListener.class.getName()).log(Level.FINEST, "Persistent event listener successfully created."); + logger.trace("Persistent event listener successfully created."); } @Override diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java index 504e470..9ee9ef2 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java @@ -3,11 +3,13 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class PersistentSafeguardEventListenerBase implements ISafeguardEventListener { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardEventListenerBase.class); + private boolean disposed; private SafeguardEventListener eventListener; @@ -59,8 +61,7 @@ public void run() { if (eventListener != null) { eventListener.dispose(); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.FINEST, - "Attempting to connect and start internal event listener."); + logger.trace("Attempting to connect and start internal event listener."); eventListener = reconnectEventListener(); eventListener.setEventHandlerRegistry(eventHandlerRegistry); eventListener.SetEventListenerStateCallback(eventListenerStateCallback); @@ -68,10 +69,8 @@ public void run() { eventListener.setDisconnectHandler(new PersistentReconnectAndStartHandler()); break; } catch (ObjectDisposedException | SafeguardForJavaException | ArgumentException ex) { - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.WARNING, - "Internal event listener connection error (see debug for more information), sleeping for 5 seconds..."); - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.FINEST, - "Internal event listener connection error."); + logger.warn("Internal event listener connection error (see debug for more information), sleeping for 5 seconds..."); + logger.trace("Internal event listener connection error."); try { Thread.sleep(5000); } catch (InterruptedException ex1) { @@ -98,7 +97,7 @@ public void start() throws ObjectDisposedException { if (disposed) { throw new ObjectDisposedException("PersistentSafeguardEventListener"); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.INFO, "Internal event listener requested to start."); + logger.info("Internal event listener requested to start."); persistentReconnectAndStart(); } @@ -107,7 +106,7 @@ public void stop() throws ObjectDisposedException, SafeguardForJavaException { if (disposed) { throw new ObjectDisposedException("PersistentSafeguardEventListener"); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.INFO, "Internal event listener requested to stop."); + logger.info("Internal event listener requested to stop."); this.isCancellationRequested = true; if (eventListener != null) { eventListener.stop(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java index 52b20f4..d862977 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java @@ -24,8 +24,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -47,6 +47,8 @@ public void func() throws SafeguardEventListenerDisconnectedException { public class SafeguardEventListener implements ISafeguardEventListener, AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(SafeguardEventListener.class); + private boolean disposed; private final String eventUrl; @@ -182,11 +184,11 @@ public void start() throws ObjectDisposedException, SafeguardForJavaException, S public void invoke(Exception exception){ try { if(exception != null) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.WARNING, "SignalR error detected!", exception); + logger.warn("SignalR error detected!", exception); } handleDisconnect(); } catch (SafeguardEventListenerDisconnectedException ex) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } } }); @@ -261,7 +263,7 @@ private void handleDisconnect() throws SafeguardEventListenerDisconnectedExcepti if(!this.isStarted()) { return; } - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, "SignalR disconnect detected, calling handler..."); + logger.warn("SignalR disconnect detected, calling handler..."); CallEventListenerStateCallback(SafeguardEventListenerState.Disconnected); disconnectHandler.func(); } @@ -352,8 +354,7 @@ private void ConfigureHttpClientBuilder(Builder builder) } catch(Exception error) { - String msg = String.format("Error setting client authentication certificate: %s", error.getMessage()); - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, msg); + logger.error("Error setting client authentication certificate: {}", error.getMessage()); } } @@ -383,13 +384,13 @@ private void ConfigureHttpClientBuilder(Builder builder) builder.sslSocketFactory(sslContext.getSocketFactory(), x509tm); } catch(NoSuchAlgorithmException ex) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } catch(KeyManagementException ex){ - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } catch(KeyStoreException ex){ - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index b602997..c9d70db 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -31,10 +31,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -83,7 +81,7 @@ public class RestClient { private boolean ignoreSsl = false; private HostnameVerifier validationCallback = null; - Logger logger = Logger.getLogger(getClass().getName()); + private static final Logger logger = LoggerFactory.getLogger(RestClient.class); public RestClient(String connectionAddr, boolean ignoreSsl, HostnameVerifier validationCallback) { @@ -104,13 +102,9 @@ public RestClient(String connectionAddr, String userName, char[] password, boole private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ignoreSsl, HostnameVerifier validationCallback) { - // Used to produce debug output + // Used to produce debug output - enable SLF4J debug level instead if (false) { - Handler handlerObj = new ConsoleHandler(); - handlerObj.setLevel(Level.ALL); - logger.addHandler(handlerObj); - logger.setLevel(Level.ALL); - logger.setUseParentHandlers(false); + // Debug logging is now controlled via SLF4J configuration } this.ignoreSsl = ignoreSsl; @@ -120,7 +114,7 @@ private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ign URL aUrl = new URL(connectionAddr); this.hostDomain = aUrl.getHost(); } catch (MalformedURLException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Invalid URL", ex); + logger.error("Invalid URL", ex); } SSLConnectionSocketFactory sslsf = null; @@ -143,7 +137,7 @@ private URI getBaseURI(String segments) { try { return new URI(serverUrl+"/"+segments); } catch (URISyntaxException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Invalid URI", ex); + logger.error("Invalid URI", ex); } return null; } @@ -171,7 +165,7 @@ private Map parseKeyValue(String value) { public void addSessionId(String cookieValue) { if (cookieValue == null) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Session cookie cannot be null"); + logger.error("Session cookie cannot be null"); return; } @@ -207,7 +201,7 @@ public void addSessionId(String cookieValue) { } } catch (Exception ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Failed to set session cookie.", ex); + logger.error("Failed to set session cookie.", ex); } } @@ -472,7 +466,7 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific try { clientKs = KeyStore.getInstance("JKS"); } catch (KeyStoreException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Could not get instance of JDK, trying PKCS12", ex); + logger.error("Could not get instance of JDK, trying PKCS12", ex); clientKs = KeyStore.getInstance("PKCS12"); } clientKs.load(in, keyPass); @@ -480,9 +474,9 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific in.close(); } } catch (FileNotFoundException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } SSLConnectionSocketFactory sslsf = null; From 3a47c072a883d52e5c1a880dde1eb52b7227dbb5 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 22:41:32 -0600 Subject: [PATCH 11/22] SpotBugs -- fix buggy constructor calls / dead variable --- pom.xml | 20 ++++++++++++ .../safeguard/safeguardjava/Safeguard.java | 32 +++++++++---------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 9ce43a1..4e7385b 100644 --- a/pom.xml +++ b/pom.xml @@ -154,6 +154,26 @@ + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.6.6 + + Max + Medium + spotbugs-exclude.xml + true + + + + check + verify + + check + + + + diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java index 57eea8f..d9faced 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Safeguard.java @@ -844,7 +844,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new PasswordAuthenticator(networkAddress, provider, username, password, version, ignoreSsl, null))); + new PasswordAuthenticator(networkAddress, provider, username, password, version, sslIgnore, null))); } /** @@ -907,7 +907,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, certificatePath, certificatePassword, version, ignoreSsl, null))); + new CertificateAuthenticator(networkAddress, certificatePath, certificatePassword, version, sslIgnore, null))); } /** @@ -967,7 +967,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, ignoreSsl, null))); + new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, sslIgnore, null))); } /** @@ -1026,7 +1026,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection(new CertificateAuthenticator(networkAddress, - keystorePath, keystorePassword, certificateAlias, version, ignoreSsl, null))); + keystorePath, keystorePassword, certificateAlias, version, sslIgnore, null))); } /** @@ -1087,7 +1087,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, certificatePath, certificatePassword, version, ignoreSsl, null, provider))); + new CertificateAuthenticator(networkAddress, certificatePath, certificatePassword, version, sslIgnore, null, provider))); } /** @@ -1157,7 +1157,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, thumbprint, version, ignoreSsl, null, null))); + new CertificateAuthenticator(networkAddress, thumbprint, version, sslIgnore, null, null))); } /** @@ -1235,7 +1235,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, thumbprint, version, ignoreSsl, null, provider))); + new CertificateAuthenticator(networkAddress, thumbprint, version, sslIgnore, null, provider))); } /** @@ -1305,7 +1305,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection( - new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, ignoreSsl, null, provider))); + new CertificateAuthenticator(networkAddress, certificateData, certificatePassword, certificateAlias, version, sslIgnore, null, provider))); } /** @@ -1368,7 +1368,7 @@ public static ISafeguardEventListener getPersistentEventListener(String networkA } return new PersistentSafeguardEventListener(getConnection(new CertificateAuthenticator(networkAddress, - keystorePath, keystorePassword, certificateAlias, version, ignoreSsl, null, provider))); + keystorePath, keystorePassword, certificateAlias, version, sslIgnore, null, provider))); } /** @@ -1668,7 +1668,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe } return new PersistentSafeguardA2AEventListener( - new SafeguardA2AContext(networkAddress, certificateAlias, keystorePath, keystorePassword, version, ignoreSsl, null), + new SafeguardA2AContext(networkAddress, certificateAlias, keystorePath, keystorePassword, version, sslIgnore, null), apiKey, handler); } @@ -1747,7 +1747,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe return new PersistentSafeguardA2AEventListener( new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, - ignoreSsl, null), apiKey, handler); + sslIgnore, null), apiKey, handler); } /** @@ -1823,7 +1823,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe return new PersistentSafeguardA2AEventListener( new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, - ignoreSsl, null), apiKey, handler); + sslIgnore, null), apiKey, handler); } /** @@ -1899,7 +1899,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List } return new PersistentSafeguardA2AEventListener( - new SafeguardA2AContext(networkAddress, certificateAlias, keystorePath, keystorePassword, version, ignoreSsl, null), + new SafeguardA2AContext(networkAddress, certificateAlias, keystorePath, keystorePassword, version, sslIgnore, null), apiKeys, handler); } @@ -1978,7 +1978,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List return new PersistentSafeguardA2AEventListener( new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version, - ignoreSsl, null), apiKeys, handler); + sslIgnore, null), apiKeys, handler); } /** @@ -2085,7 +2085,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List } return new PersistentSafeguardA2AEventListener( - new SafeguardA2AContext(networkAddress, version, ignoreSsl, + new SafeguardA2AContext(networkAddress, version, sslIgnore, thumbprint, null), apiKeys, handler); */ } @@ -2163,7 +2163,7 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List return new PersistentSafeguardA2AEventListener( new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version, - ignoreSsl, null), apiKeys, handler); + sslIgnore, null), apiKeys, handler); } /** From 3662e61314e35204f6c0e1992135e7c601b1c29f Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 22:49:57 -0600 Subject: [PATCH 12/22] SpotBugs -- you should only call new SecureRandom once if possible --- .../safeguard/safeguardjava/AgentBasedLoginUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java index 05fb2c9..6fc35f0 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java @@ -28,6 +28,8 @@ public final class AgentBasedLoginUtils { /** Redirect URI for TCP listener-based authentication. */ public static final String REDIRECT_URI_TCP_LISTENER = "urn:InstalledApplicationTcpListener"; + private static final SecureRandom RANDOM = new SecureRandom(); + private AgentBasedLoginUtils() { } @@ -38,7 +40,7 @@ private AgentBasedLoginUtils() { */ public static String oAuthCodeVerifier() { byte[] bytes = new byte[60]; - new SecureRandom().nextBytes(bytes); + RANDOM.nextBytes(bytes); return toBase64Url(bytes); } @@ -65,7 +67,7 @@ public static String oAuthCodeChallenge(String codeVerifier) { */ public static String generateCsrfToken() { byte[] bytes = new byte[32]; - new SecureRandom().nextBytes(bytes); + RANDOM.nextBytes(bytes); return toBase64Url(bytes); } From 345237e2883149787861a409004f8705a8df8ed0 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 22:54:39 -0600 Subject: [PATCH 13/22] Use the disposed field properly and throw objectdisposedexception when checked --- .../safeguardjava/PersistentSafeguardConnection.java | 1 - .../authentication/AccessTokenAuthenticator.java | 4 ---- .../authentication/AnonymousAuthenticator.java | 4 ---- .../authentication/ManagementServiceAuthenticator.java | 9 +++++++++ .../safeguardjava/data/A2ARetrievableAccount.java | 3 --- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java index e50f16c..bea0018 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java @@ -13,7 +13,6 @@ class PersistentSafeguardConnection implements ISafeguardConnection { private final ISafeguardConnection _connection; - private boolean disposed; public PersistentSafeguardConnection(ISafeguardConnection connection) { _connection = connection; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java index 94deb45..2f0e9d1 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java @@ -6,8 +6,6 @@ public class AccessTokenAuthenticator extends AuthenticatorBase { - private boolean disposed; - public AccessTokenAuthenticator(String networkAddress, char[] accessToken, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { @@ -38,14 +36,12 @@ public Object cloneObject() throws SafeguardForJavaException { public void dispose() { super.dispose(); - disposed = true; } @Override protected void finalize() throws Throwable { try { } finally { - disposed = true; super.finalize(); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java index 6422204..ff2c620 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java @@ -11,8 +11,6 @@ public class AnonymousAuthenticator extends AuthenticatorBase { - private boolean disposed; - public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); @@ -64,14 +62,12 @@ public Object cloneObject() throws SafeguardForJavaException { @Override public void dispose() { super.dispose(); - disposed = true; } @Override protected void finalize() throws Throwable { try { } finally { - disposed = true; super.finalize(); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java index 7a77ce6..d6beaf5 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java @@ -63,16 +63,25 @@ public void clearAccessToken() { @Override public char[] getAccessToken() throws ObjectDisposedException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } return null; } @Override public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } return 0; } @Override public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } throw new SafeguardForJavaException("Anonymous connection cannot be used to get an API access token, Error: Unsupported operation"); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java index e85c3c0..a62ad96 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java @@ -10,7 +10,6 @@ */ @JsonIgnoreProperties public class A2ARetrievableAccount implements IA2ARetrievableAccount { - private boolean disposed; private String applicationName; private String description; @@ -159,7 +158,6 @@ public void dispose() if (apiKey != null) { Arrays.fill(apiKey, '0'); } - disposed = true; apiKey = null; } @@ -170,7 +168,6 @@ protected void finalize() throws Throwable { Arrays.fill(apiKey, '0'); } } finally { - disposed = true; super.finalize(); } } From d95a75f7b720c30df3116aecc40afd96d45a91d1 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 23:18:48 -0600 Subject: [PATCH 14/22] Fixed spotbug code problems in streaming and added tests for streaming --- TestFramework/SafeguardTestFramework.psm1 | 5 ++ TestFramework/Suites/Suite-Streaming.ps1 | 88 +++++++++++++++++++ .../safeguardjava/SpsStreamingRequest.java | 5 +- .../safeguardjava/StreamingRequest.java | 7 +- .../event/SafeguardEventListener.java | 19 ++-- .../safeguardjava/restclient/RestClient.java | 21 +++-- .../safeguardclient/SafeguardJavaClient.java | 27 +++++- .../safeguardclient/ToolOptions.java | 4 + 8 files changed, 154 insertions(+), 22 deletions(-) create mode 100644 TestFramework/Suites/Suite-Streaming.ps1 diff --git a/TestFramework/SafeguardTestFramework.psm1 b/TestFramework/SafeguardTestFramework.psm1 index 314c45f..692448b 100644 --- a/TestFramework/SafeguardTestFramework.psm1 +++ b/TestFramework/SafeguardTestFramework.psm1 @@ -329,6 +329,9 @@ function Invoke-SgJSafeguardApi { [Parameter()] [switch]$Full, + [Parameter()] + [string]$File, + [Parameter()] [hashtable]$Headers, @@ -372,6 +375,8 @@ function Invoke-SgJSafeguardApi { if ($Full) { $toolArgs += " -f" } + if ($File) { $toolArgs += " -F `"$File`"" } + if ($Headers -and $Headers.Count -gt 0) { $headerPairs = ($Headers.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "," $toolArgs += " -H `"$headerPairs`"" diff --git a/TestFramework/Suites/Suite-Streaming.ps1 b/TestFramework/Suites/Suite-Streaming.ps1 new file mode 100644 index 0000000..a2c15d8 --- /dev/null +++ b/TestFramework/Suites/Suite-Streaming.ps1 @@ -0,0 +1,88 @@ +@{ + Name = "Streaming Upload and Download" + Description = "Tests streaming file download (downloadStream) and upload (uploadStream) via backup endpoints" + Tags = @("streaming", "appliance") + + Setup = { + param($Context) + } + + Execute = { + param($Context) + + $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "sgj-streaming-test-$(Get-Random)" + New-Item -ItemType Directory -Path $tempDir -Force | Out-Null + $downloadPath = Join-Path $tempDir "backup-download.sgb" + + try { + # --- Step 1: Trigger a backup --- + $backup = Invoke-SgJSafeguardApi -Context $Context -Service Appliance -Method Post ` + -RelativeUrl "Backups" + $backupId = $backup.Id + + Test-SgJAssert "Trigger backup: received backup ID" { + $null -ne $backupId + } + + # --- Step 2: Poll until backup is complete (5 min timeout) --- + $timeout = (Get-Date).AddMinutes(5) + $backupComplete = $false + while ((Get-Date) -lt $timeout) { + $backupInfo = Invoke-SgJSafeguardApi -Context $Context -Service Appliance -Method Get ` + -RelativeUrl "Backups/$backupId" + if ($backupInfo.Status -eq "Complete") { + $backupComplete = $true + break + } + Start-Sleep -Seconds 5 + } + + Test-SgJAssert "Backup completed within timeout" { + $backupComplete -eq $true + } + + # --- Step 3: Streaming download --- + $downloadResult = Invoke-SgJSafeguardApi -Context $Context -Service Appliance -Method Get ` + -RelativeUrl "Backups/$backupId/Download" ` + -File $downloadPath -ParseJson $false + + Test-SgJAssert "Streaming download: backup file created" { + Test-Path $downloadPath + } + + $fileSize = (Get-Item $downloadPath).Length + + Test-SgJAssert "Streaming download: backup file is non-empty" { + $fileSize -gt 0 + } + + # --- Step 4: Streaming upload --- + $uploadResult = Invoke-SgJSafeguardApi -Context $Context -Service Appliance -Method Post ` + -RelativeUrl "Backups/Upload" ` + -File $downloadPath + + Test-SgJAssert "Streaming upload: response received" { + $null -ne $uploadResult + } + + } finally { + # Cleanup temp files + if (Test-Path $tempDir) { + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue + } + # Cleanup backup if created + if ($backupId) { + try { + Invoke-SgJSafeguardApi -Context $Context -Service Appliance -Method Delete ` + -RelativeUrl "Backups/$backupId" -ParseJson $false + } catch { + Write-Verbose "Cleanup: could not delete backup $backupId - $($_.Exception.Message)" + } + } + } + } + + Cleanup = { + param($Context) + } +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java index a94972a..1faadf0 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java @@ -12,9 +12,12 @@ import java.io.OutputStream; import java.util.Map; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class SpsStreamingRequest implements ISpsStreamingRequest { + private static final Logger logger = LoggerFactory.getLogger(SpsStreamingRequest.class); private final Integer DefaultBufferSize = 81920; private RestClient client; @@ -149,7 +152,7 @@ public void downloadStream(String relativeUrl, String outputFilePath, IProgressC } catch (Exception ex) { throw new SafeguardForJavaException(String.format("Unable to download %s", outputFilePath), ex); } finally { - if (output != null) try { output.close(); } catch (IOException logOrIgnore) {} + if (output != null) try { output.close(); } catch (IOException ex) { logger.debug("Error closing output stream", ex); } if (streamResponse != null) streamResponse.dispose(); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java index e4f3073..6cf473a 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamingRequest.java @@ -13,11 +13,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; class StreamingRequest implements IStreamingRequest { + private static final Logger logger = LoggerFactory.getLogger(StreamingRequest.class); private final Integer DefaultBufferSize = 81920; private final SafeguardConnection safeguardConnection; private final IAuthenticationMechanism authenticationMechanism; @@ -111,8 +114,8 @@ public void downloadStream(Service service, String relativeUrl, String outputFil } catch (Exception ex) { throw new SafeguardForJavaException(String.format("Unable to download %s", outputFilePath), ex); } finally { - if (output != null) try { output.close(); } catch (IOException logOrIgnore) {} - if (input != null) try { input.close(); } catch (IOException logOrIgnore) {} + if (output != null) try { output.close(); } catch (IOException ex) { logger.debug("Error closing output stream", ex); } + if (input != null) try { input.close(); } catch (IOException ex) { logger.debug("Error closing input stream", ex); } } FullResponse fullResponse = new FullResponse(response.getCode(), response.getHeaders(), null); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java index d862977..2ca3463 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java @@ -303,9 +303,14 @@ private HubConnection CreateConnection(String eventUrl) throws SafeguardForJavaE if (apiKey != null) authKey = new String(apiKey); else if (apiKeys != null) { - for (char[] key : apiKeys) - authKey += new String(key) + " "; - authKey = authKey.trim(); + StringBuilder authKeyBuilder = new StringBuilder(); + for (char[] key : apiKeys) { + if (authKeyBuilder.length() > 0) { + authKeyBuilder.append(' '); + } + authKeyBuilder.append(key); + } + authKey = authKeyBuilder.toString(); } if (authKey.isEmpty()) @@ -338,10 +343,10 @@ private void ConfigureHttpClientBuilder(Builder builder) // If we have a client certificate, set it into the KeyStore/KeyManager try{ KeyStore keyStore = KeyStore.getInstance("PKCS12"); - InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) - : new ByteArrayInputStream(clientCertificate.getCertificateData()); - - keyStore.load(inputStream, clientCertificate.getCertificatePassword()); + try (InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) + : new ByteArrayInputStream(clientCertificate.getCertificateData())) { + keyStore.load(inputStream, clientCertificate.getCertificatePassword()); + } KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, clientCertificate.getCertificatePassword()); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index c9d70db..0c56b4b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -449,7 +449,6 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific || certificateContext.getCertificateData() != null || certificateContext.getCertificateThumbprint() != null) { - InputStream in; KeyStore clientKs = null; List aliases = null; char[] keyPass = certificateContext.getCertificatePassword(); @@ -461,17 +460,17 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific aliases = new ArrayList<>(); aliases = Collections.list(clientKs.aliases()); } else { - in = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) - : new ByteArrayInputStream(certificateContext.getCertificateData()); - try { - clientKs = KeyStore.getInstance("JKS"); - } catch (KeyStoreException ex) { - logger.error("Could not get instance of JDK, trying PKCS12", ex); - clientKs = KeyStore.getInstance("PKCS12"); + try (InputStream in2 = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) + : new ByteArrayInputStream(certificateContext.getCertificateData())) { + try { + clientKs = KeyStore.getInstance("JKS"); + } catch (KeyStoreException ex) { + logger.error("Could not get instance of JDK, trying PKCS12", ex); + clientKs = KeyStore.getInstance("PKCS12"); + } + clientKs.load(in2, keyPass); + aliases = Collections.list(clientKs.aliases()); } - clientKs.load(in, keyPass); - aliases = Collections.list(clientKs.aliases()); - in.close(); } } catch (FileNotFoundException ex) { logger.error("Exception occurred", ex); diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index 3d312d8..b10fe0d 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -16,8 +16,10 @@ import picocli.CommandLine; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import java.util.Scanner; @@ -87,7 +89,10 @@ public static void main(String[] args) { Map headers = parseKeyValuePairs(opts.headers); Map parameters = parseKeyValuePairs(opts.parameters); - if (opts.full) { + if (opts.file != null) { + String result = handleStreamingRequest(opts, connection, service, method, headers, parameters); + System.out.println(result); + } else if (opts.full) { FullResponse response = connection.invokeMethodFull(service, method, opts.relativeUrl, opts.body, parameters, headers, null); ObjectNode json = mapper.createObjectNode(); @@ -117,6 +122,26 @@ public static void main(String[] args) { } } + private static String handleStreamingRequest(ToolOptions opts, ISafeguardConnection connection, + Service service, Method method, Map headers, + Map parameters) throws Exception { + if (method == Method.Post) { + byte[] fileContent = Files.readAllBytes(new File(opts.file).toPath()); + return connection.getStreamingRequest().uploadStream(service, opts.relativeUrl, + fileContent, null, parameters, headers); + } else if (method == Method.Get) { + File outFile = new File(opts.file); + if (outFile.exists()) { + throw new IllegalStateException("File exists, remove it first: " + opts.file); + } + connection.getStreamingRequest().downloadStream(service, opts.relativeUrl, + opts.file, null, parameters, headers); + return "Download written to " + opts.file; + } else { + throw new IllegalArgumentException("Streaming is not supported for HTTP method: " + opts.method); + } + } + private static ISafeguardConnection createConnection(ToolOptions opts) throws Exception { char[] password = null; if (opts.readPassword) { diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java index 5a26664..3693150 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -91,6 +91,10 @@ public class ToolOptions { description = "Display verbose debug output") boolean verbose; + @Option(names = {"-F", "--file"}, + description = "File path for streaming upload (POST) or download (GET)") + String file; + @Option(names = {"--interactive"}, defaultValue = "false", description = "Run in interactive menu mode (legacy)") boolean interactive; From f21f7b99e2b8e96c6b00d0fdc023377f5bcdc931 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Fri, 27 Mar 2026 23:23:36 -0600 Subject: [PATCH 15/22] Added various bug fixes from SpotBugs findings --- .../safeguardjava/CertificateUtilities.java | 2 +- .../safeguardjava/SafeguardConnection.java | 2 +- .../SafeguardSessionsConnection.java | 3 --- .../authentication/AccessTokenAuthenticator.java | 8 -------- .../authentication/AnonymousAuthenticator.java | 8 -------- .../authentication/CertificateAuthenticator.java | 3 --- .../authentication/PkceAuthenticator.java | 4 ++-- .../restclient/OutputStreamProgress.java | 15 ++++++++------- 8 files changed, 12 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java index 9c53a2d..14254ee 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java @@ -18,7 +18,7 @@ public class CertificateUtilities { private CertificateUtilities() { } - public static String WINDOWSKEYSTORE = "Windows-MY"; + public static final String WINDOWSKEYSTORE = "Windows-MY"; public static String getClientCertificateAliasFromStore(String thumbprint) throws SafeguardForJavaException { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java index ccb470a..fbf0253 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardConnection.java @@ -239,7 +239,7 @@ static void logRequestDetails(Method method, String uri, Map par static void logResponseDetails(FullResponse fullResponse) { logger.trace("Reponse status code: {}", fullResponse.getStatusCode()); - String msg = fullResponse.getHeaders() == null ? "None" : fullResponse.getHeaders().stream().map(header -> header.getName() + "=" + header.getValue()).collect(Collectors.joining(", ", "{", "}")); + String msg = fullResponse.getHeaders().stream().map(header -> header.getName() + "=" + header.getValue()).collect(Collectors.joining(", ", "{", "}")); logger.trace(" Response headers: {}", msg); msg = fullResponse.getBody() == null ? "None" : String.format("%d",fullResponse.getBody().length()); logger.trace(" Body size: {}", msg); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java index 4fd16dc..a74d952 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java @@ -9,7 +9,6 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; -import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,8 +34,6 @@ public SafeguardSessionsConnection(String networkAddress, String username, String spsApiUrl = String.format("https://%s/api", networkAddress); client = new RestClient(spsApiUrl, username, password, ignoreSsl, validationCallback); - Map headers = new HashMap<>(); - logger.trace("Starting authentication."); logRequestDetails(Method.Get, client.getBaseURL() + "/" + "authentication", null, null); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java index 2f0e9d1..54d9233 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java @@ -38,12 +38,4 @@ public void dispose() super.dispose(); } - @Override - protected void finalize() throws Throwable { - try { - } finally { - super.finalize(); - } - } - } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java index ff2c620..c5b7457 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java @@ -64,12 +64,4 @@ public void dispose() { super.dispose(); } - @Override - protected void finalize() throws Throwable { - try { - } finally { - super.finalize(); - } - } - } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java index c7206b2..e79251c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java @@ -118,9 +118,6 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar String content = Utils.getResponse(response); if (!Utils.isSuccessful(response.getCode())) { - String msg = Utils.isNullOrEmpty(clientCertificate.getCertificateAlias()) ? - String.format("file=%s", clientCertificate.getCertificatePath()) : String.format("alias=%s", clientCertificate.getCertificateAlias()); - throw new SafeguardForJavaException("Error using client_credentials grant_type with " + clientCertificate.toString() + String.format(", Error: %d %s", response.getCode(), content)); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java index f401579..38d8741 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java @@ -220,7 +220,7 @@ private void handleSecondaryAuthentication(CloseableHttpClient httpClient, Strin if (initResponse != null && initResponse.has("State")) { mfaState = initResponse.get("State").asText(""); } - } catch (Exception e) { + } catch (IOException e) { // Proceed without state } @@ -235,7 +235,7 @@ private void handleSecondaryAuthentication(CloseableHttpClient httpClient, Strin logger.debug("Secondary authentication completed successfully"); } catch (SafeguardForJavaException e) { throw e; - } catch (Exception e) { + } catch (IOException e) { throw new SafeguardForJavaException("Secondary authentication failed", e); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java index fa22106..b51622f 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java @@ -4,6 +4,7 @@ import com.oneidentity.safeguard.safeguardjava.data.TransferProgress; import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicLong; public class OutputStreamProgress extends OutputStream { @@ -11,17 +12,17 @@ public class OutputStreamProgress extends OutputStream { private final IProgressCallback progressCallback; private final TransferProgress transferProgress = new TransferProgress(); private int lastSentPercent = 5; - private volatile long bytesWritten=0; + private final AtomicLong bytesWritten = new AtomicLong(0); public OutputStreamProgress(OutputStream outstream, IProgressCallback progressCallback, long totalBytes) { this.outstream = outstream; this.progressCallback = progressCallback; this.transferProgress.setBytesTotal(totalBytes); - this.transferProgress.setBytesTransferred(bytesWritten); + this.transferProgress.setBytesTransferred(0); } private void sendProgress() { - transferProgress.setBytesTransferred(bytesWritten); + transferProgress.setBytesTransferred(bytesWritten.get()); if (transferProgress.getPercentComplete() >= lastSentPercent) { lastSentPercent += 5; progressCallback.checkProgress(transferProgress); @@ -31,7 +32,7 @@ private void sendProgress() { @Override public void write(int b) throws IOException { outstream.write(b); - bytesWritten++; + bytesWritten.incrementAndGet(); if (progressCallback != null) { sendProgress(); } @@ -40,7 +41,7 @@ public void write(int b) throws IOException { @Override public void write(byte[] b) throws IOException { outstream.write(b); - bytesWritten += b.length; + bytesWritten.addAndGet(b.length); if (progressCallback != null) { sendProgress(); } @@ -49,7 +50,7 @@ public void write(byte[] b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { outstream.write(b, off, len); - bytesWritten += len; + bytesWritten.addAndGet(len); if (progressCallback != null) { sendProgress(); } @@ -66,6 +67,6 @@ public void close() throws IOException { } public long getWrittenLength() { - return bytesWritten; + return bytesWritten.get(); } } From 8030a0a64381f2245d05ce2933d5a9b41a77909a Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sat, 28 Mar 2026 07:40:14 -0600 Subject: [PATCH 16/22] Added test coverage for cert auth and sps api calls --- TestFramework/Invoke-SafeguardTests.ps1 | 24 +++ TestFramework/SafeguardTestFramework.psm1 | 153 ++++++++++++++++++ .../Suites/Suite-CertificateAuth.ps1 | 119 ++++++++++++++ TestFramework/Suites/Suite-SpsIntegration.ps1 | 46 ++++++ .../TestData/CERTS/IntermediateCA.cer | Bin 0 -> 947 bytes .../TestData/CERTS/IntermediateCA.pem | 22 +++ .../TestData/CERTS/IntermediateCA.pfx | Bin 0 -> 2742 bytes .../TestData/CERTS/IntermediateCA.pvk | Bin 0 -> 1212 bytes TestFramework/TestData/CERTS/README.md | 6 + TestFramework/TestData/CERTS/RootCA.cer | Bin 0 -> 939 bytes TestFramework/TestData/CERTS/RootCA.pem | 22 +++ TestFramework/TestData/CERTS/RootCA.pfx | Bin 0 -> 2734 bytes TestFramework/TestData/CERTS/RootCA.pvk | Bin 0 -> 1212 bytes TestFramework/TestData/CERTS/UserCert.cer | Bin 0 -> 989 bytes TestFramework/TestData/CERTS/UserCert.pem | 23 +++ TestFramework/TestData/CERTS/UserCert.pfx | Bin 0 -> 2782 bytes TestFramework/TestData/CERTS/UserCert.pvk | Bin 0 -> 1212 bytes spotbugs-exclude.xml | 35 ++++ .../safeguardclient/SafeguardJavaClient.java | 39 +++++ .../safeguardclient/ToolOptions.java | 4 + 20 files changed, 493 insertions(+) create mode 100644 TestFramework/Suites/Suite-CertificateAuth.ps1 create mode 100644 TestFramework/Suites/Suite-SpsIntegration.ps1 create mode 100644 TestFramework/TestData/CERTS/IntermediateCA.cer create mode 100644 TestFramework/TestData/CERTS/IntermediateCA.pem create mode 100644 TestFramework/TestData/CERTS/IntermediateCA.pfx create mode 100644 TestFramework/TestData/CERTS/IntermediateCA.pvk create mode 100644 TestFramework/TestData/CERTS/README.md create mode 100644 TestFramework/TestData/CERTS/RootCA.cer create mode 100644 TestFramework/TestData/CERTS/RootCA.pem create mode 100644 TestFramework/TestData/CERTS/RootCA.pfx create mode 100644 TestFramework/TestData/CERTS/RootCA.pvk create mode 100644 TestFramework/TestData/CERTS/UserCert.cer create mode 100644 TestFramework/TestData/CERTS/UserCert.pem create mode 100644 TestFramework/TestData/CERTS/UserCert.pfx create mode 100644 TestFramework/TestData/CERTS/UserCert.pvk create mode 100644 spotbugs-exclude.xml diff --git a/TestFramework/Invoke-SafeguardTests.ps1 b/TestFramework/Invoke-SafeguardTests.ps1 index f9bb616..2a5ae6a 100644 --- a/TestFramework/Invoke-SafeguardTests.ps1 +++ b/TestFramework/Invoke-SafeguardTests.ps1 @@ -36,6 +36,15 @@ Use PKCE OAuth2 flow for password authentication instead of resource owner password grant. Required when the appliance disables password grant. +.PARAMETER SpsAppliance + SPS (Safeguard for Privileged Sessions) appliance address for SPS integration tests. + +.PARAMETER SpsUserName + SPS admin username. Default: "admin". + +.PARAMETER SpsPassword + SPS admin password. + .PARAMETER TestPrefix Prefix for test objects created on the appliance. Default: "SgJTest". @@ -80,6 +89,15 @@ param( [Parameter()] [switch]$Pkce, + [Parameter()] + [string]$SpsAppliance, + + [Parameter()] + [string]$SpsUserName = "admin", + + [Parameter()] + [string]$SpsPassword, + [Parameter()] [string]$TestPrefix = "SgJTest", @@ -212,12 +230,18 @@ Write-Host " Suites: $(@($selectedSuites).Count) selected" -ForegroundColor if ($Pkce) { Write-Host " PKCE: Enabled" -ForegroundColor Yellow } +if ($SpsAppliance) { + Write-Host " SPS: $SpsAppliance" -ForegroundColor White +} Write-Host ("=" * 66) -ForegroundColor Cyan $context = New-SgJTestContext ` -Appliance $Appliance ` -AdminUserName $AdminUserName ` -AdminPassword $AdminPassword ` + -SpsAppliance $SpsAppliance ` + -SpsUserName $SpsUserName ` + -SpsPassword $SpsPassword ` -TestPrefix $TestPrefix ` -MavenCmd $MavenCmd ` -Pkce:$Pkce diff --git a/TestFramework/SafeguardTestFramework.psm1 b/TestFramework/SafeguardTestFramework.psm1 index 692448b..ae86292 100644 --- a/TestFramework/SafeguardTestFramework.psm1 +++ b/TestFramework/SafeguardTestFramework.psm1 @@ -39,6 +39,15 @@ function New-SgJTestContext { [Parameter()] [string]$AdminPassword = "Admin123", + [Parameter()] + [string]$SpsAppliance, + + [Parameter()] + [string]$SpsUserName, + + [Parameter()] + [string]$SpsPassword, + [Parameter()] [string]$TestPrefix = "SgJTest", @@ -50,12 +59,24 @@ function New-SgJTestContext { ) $repoRoot = Split-Path -Parent $PSScriptRoot + $testDataDir = Join-Path $PSScriptRoot "TestData" "CERTS" $context = [PSCustomObject]@{ # Connection info Appliance = $Appliance AdminUserName = $AdminUserName AdminPassword = $AdminPassword + # SPS connection info + SpsAppliance = $SpsAppliance + SpsUserName = $SpsUserName + SpsPassword = $SpsPassword + + # Certificate test data paths + UserPfx = (Join-Path $testDataDir "UserCert.pfx") + UserCert = (Join-Path $testDataDir "UserCert.pem") + RootCert = (Join-Path $testDataDir "RootCA.pem") + CaCert = (Join-Path $testDataDir "IntermediateCA.pem") + # Naming TestPrefix = $TestPrefix @@ -323,6 +344,12 @@ function Invoke-SgJSafeguardApi { [Parameter()] [string]$AccessToken, + [Parameter()] + [string]$CertificateFile, + + [Parameter()] + [string]$CertificatePassword, + [Parameter()] [switch]$Pkce, @@ -354,6 +381,13 @@ function Invoke-SgJSafeguardApi { elseif ($AccessToken) { $toolArgs += " -k `"$AccessToken`"" } + elseif ($CertificateFile) { + $toolArgs += " -c `"$CertificateFile`"" + if ($CertificatePassword) { + $toolArgs += " -p" + $stdinLine = $CertificatePassword + } + } else { $effectiveUser = if ($Username) { $Username } else { $Context.AdminUserName } $effectivePass = if ($Password) { $Password } else { $Context.AdminPassword } @@ -432,6 +466,90 @@ function Invoke-SgJTokenCommand { return Invoke-SgJSafeguardTool -Arguments $toolArgs -StdinLine $effectivePass } +function Invoke-SgJSafeguardSessions { + <# + .SYNOPSIS + Convenience wrapper for calling SPS API via SafeguardJavaTool --sps mode. + #> + [CmdletBinding()] + param( + [Parameter()] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [ValidateSet("Get", "Post", "Put", "Delete")] + [string]$Method, + + [Parameter(Mandatory)] + [string]$RelativeUrl, + + [Parameter()] + $Body, + + [Parameter()] + [switch]$Full, + + [Parameter()] + [bool]$ParseJson = $true + ) + + if (-not $Context) { $Context = Get-SgJTestContext } + + $toolArgs = "-a $($Context.SpsAppliance) -x --sps -m $Method -U `"$RelativeUrl`" -u $($Context.SpsUserName) -p" + + if ($Body) { + $bodyStr = if ($Body -is [hashtable] -or $Body -is [System.Collections.IDictionary] -or $Body -is [PSCustomObject]) { + (ConvertTo-Json -Depth 12 $Body -Compress).Replace('"', "'") + } + else { + [string]$Body + } + $toolArgs += " -b `"$bodyStr`"" + } + + if ($Full) { $toolArgs += " -f" } + + Write-Verbose "Invoke-SgJSafeguardSessions: $toolArgs" + + return Invoke-SgJSafeguardTool -Arguments $toolArgs -StdinLine $Context.SpsPassword -ParseJson $ParseJson +} + +function Test-SgJSpsConfigured { + <# + .SYNOPSIS + Returns $true if SPS appliance parameters are configured in the test context. + #> + [CmdletBinding()] + param( + [Parameter()] + [PSCustomObject]$Context + ) + + if (-not $Context) { $Context = Get-SgJTestContext } + + return (-not [string]::IsNullOrEmpty($Context.SpsAppliance) -and + -not [string]::IsNullOrEmpty($Context.SpsUserName) -and + -not [string]::IsNullOrEmpty($Context.SpsPassword)) +} + +function Test-SgJCertsConfigured { + <# + .SYNOPSIS + Returns $true if test certificate files are present. + #> + [CmdletBinding()] + param( + [Parameter()] + [PSCustomObject]$Context + ) + + if (-not $Context) { $Context = Get-SgJTestContext } + + return ((Test-Path $Context.UserPfx) -and + (Test-Path $Context.RootCert) -and + (Test-Path $Context.CaCert)) +} + # ============================================================================ # Object Management # ============================================================================ @@ -508,6 +626,37 @@ function Remove-SgJSafeguardTestObject { } } +function Remove-SgJStaleTestCert { + <# + .SYNOPSIS + Removes a trusted certificate by thumbprint, if it exists. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [PSCustomObject]$Context, + + [Parameter(Mandatory)] + [string]$Thumbprint + ) + + try { + $certs = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "TrustedCertificates?filter=Thumbprint ieq '$Thumbprint'" + $certList = @($certs) + foreach ($cert in $certList) { + if ($cert.Thumbprint -and $cert.Thumbprint -ieq $Thumbprint) { + Write-Host " Removing stale trusted cert: $Thumbprint (Id=$($cert.Id))" -ForegroundColor DarkYellow + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "TrustedCertificates/$($cert.Id)" -ParseJson $false + } + } + } + catch { + Write-Verbose "Pre-cleanup of cert $Thumbprint skipped: $($_.Exception.Message)" + } +} + function Clear-SgJStaleTestEnvironment { <# .SYNOPSIS @@ -947,10 +1096,14 @@ Export-ModuleMember -Function @( 'Invoke-SgJSafeguardTool' 'Invoke-SgJSafeguardApi' 'Invoke-SgJTokenCommand' + 'Invoke-SgJSafeguardSessions' + 'Test-SgJSpsConfigured' + 'Test-SgJCertsConfigured' # Object management 'Remove-SgJStaleTestObject' 'Remove-SgJSafeguardTestObject' + 'Remove-SgJStaleTestCert' 'Clear-SgJStaleTestEnvironment' # Assertions diff --git a/TestFramework/Suites/Suite-CertificateAuth.ps1 b/TestFramework/Suites/Suite-CertificateAuth.ps1 new file mode 100644 index 0000000..d6d2f70 --- /dev/null +++ b/TestFramework/Suites/Suite-CertificateAuth.ps1 @@ -0,0 +1,119 @@ +@{ + Name = "Certificate Authentication" + Description = "Tests certificate-based authentication via PFX file" + Tags = @("auth", "certificate") + + Setup = { + param($Context) + + if (-not (Test-SgJCertsConfigured -Context $Context)) { + $Context.SuiteData["Skipped"] = $true + return + } + + $prefix = $Context.TestPrefix + $certUser = "${prefix}_CertUser" + + # Compute thumbprints using .NET X509Certificate2 + $userThumbprint = (New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( + $Context.UserPfx, "a")).Thumbprint + $rootThumbprint = (New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( + $Context.RootCert)).Thumbprint + $caThumbprint = (New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( + $Context.CaCert)).Thumbprint + + $Context.SuiteData["UserThumbprint"] = $userThumbprint + $Context.SuiteData["RootThumbprint"] = $rootThumbprint + $Context.SuiteData["CaThumbprint"] = $caThumbprint + $Context.SuiteData["CertUserName"] = $certUser + + # Pre-cleanup: remove stale objects from previous failed runs + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name $certUser + Remove-SgJStaleTestCert -Context $Context -Thumbprint $caThumbprint + Remove-SgJStaleTestCert -Context $Context -Thumbprint $rootThumbprint + + # 1. Upload Root CA as trusted certificate + $rootCertData = [string](Get-Content -Raw $Context.RootCert) + $rootCert = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "TrustedCertificates" ` + -Body @{ Base64CertificateData = $rootCertData } + $Context.SuiteData["RootCertId"] = $rootCert.Id + Register-SgJTestCleanup -Description "Delete Root CA trust" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "TrustedCertificates/$($Ctx.SuiteData['RootCertId'])" + } + + # 2. Upload Intermediate CA as trusted certificate + $caCertData = [string](Get-Content -Raw $Context.CaCert) + $caCert = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "TrustedCertificates" ` + -Body @{ Base64CertificateData = $caCertData } + $Context.SuiteData["CaCertId"] = $caCert.Id + Register-SgJTestCleanup -Description "Delete Intermediate CA trust" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "TrustedCertificates/$($Ctx.SuiteData['CaCertId'])" + } + + # 3. Create certificate user mapped to user cert thumbprint + $cUser = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" ` + -Body @{ + PrimaryAuthenticationProvider = @{ + Id = -2 + Identity = $userThumbprint + } + Name = $certUser + } + $Context.SuiteData["CertUserId"] = $cUser.Id + Register-SgJTestCleanup -Description "Delete certificate user" -Action { + param($Ctx) + Remove-SgJSafeguardTestObject -Context $Ctx ` + -RelativeUrl "Users/$($Ctx.SuiteData['CertUserId'])" + } + } + + Execute = { + param($Context) + + if ($Context.SuiteData["Skipped"]) { + Test-SgJSkip "Auth as cert user from PFX file" "Test certificates not found" + Test-SgJSkip "Cert user identity matches expected name" "Test certificates not found" + return + } + + Test-SgJAssert "Auth as cert user from PFX file" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -CertificateFile $Context.UserPfx -CertificatePassword "a" + $null -ne $result + } + + Test-SgJAssert "Cert user identity matches expected name" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -CertificateFile $Context.UserPfx -CertificatePassword "a" + $result.Name -eq $Context.SuiteData["CertUserName"] + } + + Test-SgJAssert "Cert auth Me response contains user Id" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -CertificateFile $Context.UserPfx -CertificatePassword "a" + $result.Id -eq $Context.SuiteData["CertUserId"] + } + + Test-SgJAssert "Cert auth full response has status 200" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -CertificateFile $Context.UserPfx -CertificatePassword "a" -Full + $result.StatusCode -eq 200 + } + } + + Cleanup = { + param($Context) + # Registered cleanup handles everything. + } +} diff --git a/TestFramework/Suites/Suite-SpsIntegration.ps1 b/TestFramework/Suites/Suite-SpsIntegration.ps1 new file mode 100644 index 0000000..f15955c --- /dev/null +++ b/TestFramework/Suites/Suite-SpsIntegration.ps1 @@ -0,0 +1,46 @@ +@{ + Name = "SPS Integration" + Description = "Tests Safeguard for Privileged Sessions API connectivity" + Tags = @("sps") + + Setup = { + param($Context) + if (-not (Test-SgJSpsConfigured -Context $Context)) { + $Context.SuiteData["Skipped"] = $true + } + } + + Execute = { + param($Context) + + if ($Context.SuiteData["Skipped"]) { + Test-SgJSkip "SPS authentication" "SPS appliance not configured" + Test-SgJSkip "SPS firmware slots query" "SPS appliance not configured" + Test-SgJSkip "SPS full response has status 200" "SPS appliance not configured" + return + } + + Test-SgJAssert "SPS authentication" { + $result = Invoke-SgJSafeguardSessions -Context $Context ` + -Method Get -RelativeUrl "configuration/management/email" + $null -ne $result + } + + Test-SgJAssert "SPS firmware slots query" { + $result = Invoke-SgJSafeguardSessions -Context $Context ` + -Method Get -RelativeUrl "firmware/slots" + $null -ne $result + } + + Test-SgJAssert "SPS full response has status 200" { + $result = Invoke-SgJSafeguardSessions -Context $Context ` + -Method Get -RelativeUrl "firmware/slots" -Full + $result.StatusCode -eq 200 + } + } + + Cleanup = { + param($Context) + # No objects created. + } +} diff --git a/TestFramework/TestData/CERTS/IntermediateCA.cer b/TestFramework/TestData/CERTS/IntermediateCA.cer new file mode 100644 index 0000000000000000000000000000000000000000..10e20ac2cf962cd04b1739625b60b0c6a0703ed1 GIT binary patch literal 947 zcmXqLVqS01#58>YGZP~dlfdJroMzSmKYQJ~JR3jddvhD`vTK{DLJEFr1IC5HS! zgMeISVYZjmZSnLY+_VG_6#E{19KB2KLb#li>Zl`k>Q-5`|%ionJYYIR^Oj@*WmTO%XUS_ z;>9lu6wPc=)V>yC!1s5}LuQ|+hvojP-_CJo{i;t#VqNDr#_apEj_vZK;tj0}l!6X+ z+lpr>UH!mV#Jm49%k=o>CruW*%hqjib$KnjaoO(q(sKUc!7l7K|4v~2vt{;L`7=MV z%tbG^Enu-f^PnvKJ>!PrpzYz2H=b^5eQ;Lv)GBGnUFfMNFGidBK z;0FewtS}?ve->5)W*}uy0TN(jDKyA$5U{zm?~Y0E!i|Sg?e&&xhL%yKlq077Ti|f%+#*4^N226jzC}U)peQnah%hi+Te4BQj-SGjRcB(MbeqW0%YwA)G zUT%A7Hp}0$_4l=t&9_&p+qP9a-KUo#-4`Dj>Cd5K`P=ZMhl)SXiiICOtL{7Wh2`n{ z4Y%$wb5}n1zTqXjz)I?We}4H|k3y;UE$gD)@7OmVhVKSLwyx9Fk^);w8`&;DF5 z>fGk-_ue8~PDW!{OZ3deC$~(T$W&3k$Gh?N9GAk|(^OZhy{KB8AbKYJ(#n_H&Mdxq z_oiQJ9>@K++)0WvkEq;vZL4uS>56iArNpMA^OX)#f+X&HeW8T|DJ`cKtNH--_E_-&vvz0Dy~Wn*aa+ literal 0 HcmV?d00001 diff --git a/TestFramework/TestData/CERTS/IntermediateCA.pem b/TestFramework/TestData/CERTS/IntermediateCA.pem new file mode 100644 index 0000000..57e9d52 --- /dev/null +++ b/TestFramework/TestData/CERTS/IntermediateCA.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQ4+UJNjtQ+Y1HikmB8m9LCzANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0MQ0wCwYDVQQL +EwRUZXN0MQ8wDQYDVQQDEwZSb290Q0EwHhcNMTkwNTAyMjEyODU5WhcNMzkwNTAy +MjEyODU4WjBPMQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0 +MQ0wCwYDVQQLEwRUZXN0MRcwFQYDVQQDEw5JbnRlcm1lZGlhdGVDQTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5OR8dcEJmoSJl7357dMOu+0z5yxl8X +0xBymYQhK9ZUMA79rOEDTOXDHvyvtwjcr6ryxF1FnEFcvvyuBtOSc7CFoCJSwYs9 +F2gi1fABcg2/8wSXX4Pkgjgtpq60RUTrHbGmu58bHk9XU0QH2f2QBfy0m60fzPhq +NxXThqAEP8zgdmfvAbBzUrdXWdjltoXgzRXaoT5Ai6PzQg7+76L6vUYZnByoItgz +1FSy7rZYQs3k7EfpAC1FLYw/ywVhF01MsvHmzFuEyrMRnMJM8KhFGWocW5lhPHCO +Ylc6hO5tjt99qRAKvEanRmV/C1ta9Yr0qUhMsnS/S17YCLGNZjoFEqECAwEAAaOB +jjCBizAPBgNVHRMBAf8EBTADAQH/MHgGA1UdAQRxMG+AEDzavtw0U6GxwmU/Lqcp +V3WhSTBHMQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0MQ0w +CwYDVQQLEwRUZXN0MQ8wDQYDVQQDEwZSb290Q0GCEBIUnUQHbpCKS4n0RcV1AdEw +DQYJKoZIhvcNAQELBQADggEBAJvWkqHTe5Oc9pbPB0HgDitlEwK/TTi0rH5kYOm2 +6TaaT0mF+9bJg9urJz2GeOW+LmQbjl9ZWU8ILDn7MclIJE8MqKHw8yW+wvQE5e+w +2t4DC3nnS9hKE6A6Gv+Pb3etSHEa74SuW0fcP4PAWeAnD5goavsVotA7DGpB5vzX +com2S07vOGseHCimhFuZo8m0kZECeH+8S4HbnERx25YlqyboeqNgFcxX0qnptsyj +1d3ZTmVuCN/tC2IhmcQk3Os9KMdi1CNXeRiyxZ8iwNIkMwhlH5mvGe2sH8KhvtGn +1puaBzCfrzoG1p32/umwD7/3Z70a0cpNza+WLvshtuvcpCM= +-----END CERTIFICATE----- diff --git a/TestFramework/TestData/CERTS/IntermediateCA.pfx b/TestFramework/TestData/CERTS/IntermediateCA.pfx new file mode 100644 index 0000000000000000000000000000000000000000..bd7b62917dc9aef9047319b4fff9389ca44e8e29 GIT binary patch literal 2742 zcmZWqc|6qX8vhN$7$dUpYqlZGV2s@$`@Uo+jO7SJ6B>qStl46+XAKE)ET@YQNA@i# z*@_T4I9W51a;NjT_ngoD+&|v;{XXC2eg1nM1c|YQ21JV>G2-Z*+%Q7J`nfHIm*Wr zbPMrcLj;Wr&oY%L5@4KvNJdVmbV&~IfA&=*h6SBYoqkd47(Gsu702nic0Iw(S6P}6 zr#~$iI^u^iNAKD1c<@_FzwA+PFmu%{npb}G7H{$kYFL<+qawiD-R{?2o}Kh*p$dxh zn>4>|yRqM`)ll)6!+MZt9)I4eM0C%KFxr!T>EECCeuMAA_*#R1lzZval&KwTD#zZC zwZIli7ugt%>j6(vrRSAmR3G$zgyxPC!Zq_UeF|5ul*uVyf;Viu+AQLaBbXF&<{3`7 z9t9c{wS8MbhFibgmAm=mXw04k_(0j1b=Pa3*cBU=9a$}VUHxwUlV?MW)+y1OrXnReZgR0b3h@C zrO$&aQ$aiG8HdihzdIx1KHgP^EpNH*4)io#VVv5p>C_~cO^J-8fc*& zVcWM%b?)*sW`+6a!rQ_Yt;>~CV*C4d-btzFvXqOeN3g0lC@<7{3shm~8{XsE3N_4f zA3AZ`-qg~9&_hWWMUAhmjf)v7C0V@9=chi0vzNE|_+Xn1c%myT*N|=6_@O1DjL3It zO&H`_^WOZEQ^7^fJBtamHnlL_$ude=XMFn94WR*h@&Q7a!6~2P8*^pD8+s&>A}Y$@ z>w%e3XLgv!vm`-F)w2A8Gi&4FY6m^LRfl=Q*h&Qcyj)mQH?&q(ZOY3l3a7)awIJ;1 z(@xfg18U^8tWJH;ar+eqWFJ0c zWq|(G-Vx^;*AH0_zQY0>W-W94-GhS_HWi4n(u5sYys2)hz`Z@Bu#F&HKE-z}!?DNO z+Q>pY{L)*}XMIsgmX~HBTm)LW(Y-9f48gO#+J;-!MZb9xq>| zANh={^l9ik1ts)-Dl|jO71epxA&pKuESccXY|j8tXIpsCY*Y$%(BhKqS0#(9j-_>w zE~_fytCEN?r>E;1-B^sJ{epUGdI21zoCB}G{84)C6n#nMgvW}ngjUe>Yaj#0HlxaW!^^yBW0sa317=wh1Xm!*gelCDG5*X+OHL0qT zb$j{+P274xi;XYSCPPpw?mOFbp^JXia2+_`(CY7SDNX(t%Ow+rcME944l~~urt7ak z3g~W4taCb5zmGVN{?f>H-B_9&G*3USIMi^?tE~ZVr|Z_0it@Vup_@%#=<(=<6poEo zl(f(LgLAec*tw*pShBh8`^D);Wj3F zrqb0mzM*4%NLMb(*V`P{drslUwC;A*HjeH)56&%Kz`fxSTsMLzGCd>LA6It$ko?!J zR9IHrEz~ihkFoaS$`RSeO-0b`K85!&;d!%6c(Sk@NbV@Jzb*fs)sg1sEAL_)-&y_n z$&@BxBwyB`{UElDSg(QJ9OUgx!sI9i#71_qY7NC3oF zdTs_8Md-7KC_$~h&4$OmtLtv@5Q<|CHU?x8fPfgq><|xmtxvxi))f$Urgm9 z_vDJ{4&G?WpQ>VsWy_O8U|n(T*XMls8-orwHSiDvjC{M_d}l^uTt*#f!=d=NBsg{e zJ}gX@bl$H{uE{=VWtFN)szZM9e>ix&i4?ybc(?oNvw^vV;IU3~zlt%kx%<#>5Tmnn zZkNe_9hWY*$SgT+>_dx@={+ltgvH_kxu{vj`}Tsz8%SLv-sha8ol+V_1hMNkC9b4t z;+y^3?t`_x=L@vWODPSim{qkkhZ~SW7$b05ASdVcfM_{NaG3_NdU4FUafZDqrTYA; zqd7LH_=o%&FJihbh!*D`)G2Vn1Nk(KSql!s$8BHDXl&Y7GV1DMkyA!I#Vh@ zc;L*;;AY6BE}w{X#ahvfa|kKEZJyK2&Py#xG$Y@9aDFhb(7@~`vlMP?R(9JrD>_q$ z_AMJU5<;_#k?ERB?Q7c2m}u_}j2<*sj1I?MUS`Bka}PL2n084Me%)r4DZ)9tCMAPC z1pGZd3~yY|)FEeQ*ExPyx06mby?$>JQvnZSYmI1nSeN}<>>Jqokp#B>#Wx~mFPr+} zdZzW%&@J;L&l*B z5AJ-5gI_}|hsTSGi4|&3Gc@SK;>40F=i{sw!#NQe2w?<-o=%pTmWB_^u@qu36Xtq) q6bY<9=CQa7r8xJ6f;pnPiF*6-tDZsn{ndLCsULG+c}e^nApZswHpp-Q literal 0 HcmV?d00001 diff --git a/TestFramework/TestData/CERTS/IntermediateCA.pvk b/TestFramework/TestData/CERTS/IntermediateCA.pvk new file mode 100644 index 0000000000000000000000000000000000000000..83311fb7b737015ec92fb9c5c63d4a8942f005c5 GIT binary patch literal 1212 zcmV;t1Vj5C@wKo300002000010000G0001#1ONaeOlAi9k-n8@g&LSLg;LcA0ssI2 zBme-(?tEH8mT^$tn4gH=>MFV!m1$(h&q*EUA6r(eF!p~7j}Y(bU|tu6Kf&Y@N#2zzu*is|v>6W}@{D=e_Uo#7^(h-qy= z?71EFGtz^!+!j$;JYdWGy$D7f;C1?gy}a;YGnafDmfGdXX8hLSG%1|MCcVXdGZc#| zTOwy4-5|wd8sXT1?1$$Mjo%SZBiSY2k&%MY?Ejo%JZYy+CxW_tUjP4kZombTBJ19* z5(Eb*QEbNS{?e<44DiZXim-}BEUSyG#(#Ou?zPq9 zH=?FQ(E5sGFw~ZteZrx7{KOJ}^i`rUUgdi9Bwn;%jO}2p839w@$cio7Xh>M;%}bd=e;9H)Y0_+219(TXr3<)bb5(yTOEO7#adtY2y9Bb{rZVX; z6D#PPkP$|t8;F!9{x;&+FRU|8Ba@qLBo_c+Ik@K>tFTEO7FU*jheP7=YC~eS?sN3p z1qR1WIP*HB6e)rp#|c{5j7zw40=k(U$m@pd{6U~OGHAQaMvWBNLV7R{aKGM2^!;O= zIowfqGN6UvbU5D!5~CDXi*UG6E7y&y79NPbse6?2VSn-xg;iKQFo*VbpTS#E8|`X)tAVOvau{DM3ghWv|cg_luTq<(oA)bsLJhO z0*0$7cBj(Qo_Hou2ad%ffAfH&1{q#jS59o)jO`L69XaI0R+x!upv}{(xq0dwWY7`8 zgIV{>9{Sm@oAVwZ+bTgTSQr_m_x&yn&k2*iqbjX^c{0co_rv+QFOtXP>W{pe(Qb<- zo_H90cyRg9YdrEU4RA1%8g1_pI6h0z^Q`m)WUM*N_EgLOXT3&pgcXN191JQ zx^lsBTl`Vbh$W}{sTmAFT1WPXlxw|fBkuVBJyAb-Q4=(<)Me3tbRvh^wxp7B$C|c5Z`yZbpp8LdeHKrZHL`guCLJ2$ a>&Kv4BX9?ORA04A4ZcM-X&YmMO6v}Lxk`Be literal 0 HcmV?d00001 diff --git a/TestFramework/TestData/CERTS/README.md b/TestFramework/TestData/CERTS/README.md new file mode 100644 index 0000000..e9e2dca --- /dev/null +++ b/TestFramework/TestData/CERTS/README.md @@ -0,0 +1,6 @@ +# TestData \ CERTS + +These certificates are useful for setting up a simple PKI to be able to test certificate +authentication and A2A. + +The password to all of the certificates is just one letter: 'a' diff --git a/TestFramework/TestData/CERTS/RootCA.cer b/TestFramework/TestData/CERTS/RootCA.cer new file mode 100644 index 0000000000000000000000000000000000000000..b84789f7f93644e3199eb4d80ba6f0d8a065f4b9 GIT binary patch literal 939 zcmXqLVqR|0#MHllnTe5!NkB+st_yqKgf8#SFRn*R87~^}vTK{DLJEFr1IC5HS! zgMeISVYZaV@bP5mda^0>H-nW?w4g<6V^ z{@?tIcFM{}Zyl9?!=JZRoNJAFVCu0Q4YH!b$zHq8T(B_8>HKS*CZ?jK_eZa7Q}c>r zN5r%?-Tydy$7d<;hP(bh^*JYZJ7jDOakBj`8_hXYttYLiu*y7U<`=2MDvfLI3Ht*$Sy;$+F$KpJuB+U>&a{$SXmHVs!?yKL zK_}DVgp||f@0{ZUc5)W*}uy0TN(jDKyA$ z5U{zm?~Y0E!i|Sg?e&&xhL{PKHo-v^~0`XdKIRLpZtGa7Qb_&KSZ(iR+izM4+|uAX-RDKx>Pl{_)Ou% z-3ktCwbq{~6Yy+$zuZSCvo-P%_sWfe0mW-Rw|{4TbJ}E9*-s1oiLR#Nrr+Bicqr6d z+bp2>_#cBsQE>X*>RjoIu{UG*ALY8~IQ}(@e6900??aIEO4FldpJpBtdGKKKv$Ju9 ueOG=2&ShT{@g@Gqro$-(JWIBpxgut=GT_(4i*cp8%uBwu%=2R7djbG70aWh* literal 0 HcmV?d00001 diff --git a/TestFramework/TestData/CERTS/RootCA.pem b/TestFramework/TestData/CERTS/RootCA.pem new file mode 100644 index 0000000..a02608d --- /dev/null +++ b/TestFramework/TestData/CERTS/RootCA.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDpzCCAo+gAwIBAgIQEhSdRAdukIpLifRFxXUB0TANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0MQ0wCwYDVQQL +EwRUZXN0MQ8wDQYDVQQDEwZSb290Q0EwHhcNMTkwNTAyMjEyODU0WhcNMzkwNTAy +MjEyODUzWjBHMQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0 +MQ0wCwYDVQQLEwRUZXN0MQ8wDQYDVQQDEwZSb290Q0EwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC6rwscJPL9HBmrq46dw2w05ob5plrAf9bpbWX+GKnH +Fzw2NUtDOCZkLC/9b9E+IyPF2sUf7A9utRcKrDdRZca4gB0VE2NKuszQODJsif07 +ZhYkIi78Loayg6jGxBYqst/xm7jzGkuA3U/5LwmTi0BosVRCPfcdWwmVJoxmgnF6 +N1yZ9BrDJIGs3hNMUDaZUEtUAyFU6SaD0mRK/sUwp/GkGAsLhYMdaWH/V/JZPWwZ +8JVwJ92KJQNBokVWHCehe4xug0RBMb3eq/PwujCVlczShEGCvRl3UWX45lZUtxYg +QahFqkb1mToeoTDRSgg9hfxwiQKjYGTLN+5DX1ANf16tAgMBAAGjgY4wgYswDwYD +VR0TAQH/BAUwAwEB/zB4BgNVHQEEcTBvgBA82r7cNFOhscJlPy6nKVd1oUkwRzEL +MAkGA1UEBhMCVVMxGDAWBgNVBAoTD1NhZmVndWFyZERvdE5ldDENMAsGA1UECxME +VGVzdDEPMA0GA1UEAxMGUm9vdENBghASFJ1EB26QikuJ9EXFdQHRMA0GCSqGSIb3 +DQEBCwUAA4IBAQCBlWSXHcSlj0MRwxdAzykUEoScGpwkLcXPDx5AwQ/eaE/hkDJ4 +CLu9gRWmvZPsAdIsyR0vBd8dxjQzZKgsdujCpX7JAHu3z4tWthfxZ588L6vhisYu +eDVh8k/60xfc2I9UIX3aajGc8KAYuioYsUrSep1zzHGRuyBArSqvyHYQSYLvp0wS +aYVZwgupsRFQc6zzh/cD7Ms0mnb5OC+RRTUXNfeH4EggfNazEC7j/gA4clNn3Xtt +G9Fd2VwP4m1GLEH9NlnrLP1u8FIbqTXFdvKZxhTg4LPmzV5xjtT4UZ0HrFj0X8Sy +w2RwDKS3zNQWNKlQ+uHRXnUtA6T1hJ5KBg7k +-----END CERTIFICATE----- diff --git a/TestFramework/TestData/CERTS/RootCA.pfx b/TestFramework/TestData/CERTS/RootCA.pfx new file mode 100644 index 0000000000000000000000000000000000000000..2b35846e8fb3e4ab5d64e8aa92b4eb8c6808bcb2 GIT binary patch literal 2734 zcmZWqc{tQ<7yb=nFr;i5lPzn_P-7d**p(RjQi#;pvot7UH%uBMds2u{NeQo#W$eis zQkD`TMMNeB*&4mS_r1RFeXsBOuID<>bIyI==bY!Sb1)Qm85@KhLxFq4xKUJ7>gGNO zC!~l1cZ5>l_8@GHp}@HQhvL|VQaCm+6pmHUmSH^q$>N1W*or97Jq!i9gAs$l{|ke~ z`Jh~SE`I$&&r9;z*f`%nDbO)Mcr=}Ca3yQ<$L30BvrCjWz5 z8hoPc(`2Vz>wv>6%~&C4?CVTXgoTu|{ZL43)%q}B8Nw??a^v+Iy6?tQv`*@Q)y{2+I;oR3$k+1RLVaXCf zs(NY7dQy+N3EiC%pDjtjV_#a@B5L#bU6fv%7>H;Su6wWgjf!_m-tu5ee@g|WOk9!SMwE2%$@DB*kb zT_|k3YgQ&c_4t|=I!0EO(OVKnMUV_48Zv9tS~tRjN*|pLsuD^FnqJIdo$n3NfABJs z@fOeBOSnc?JWEs{OC(A~i>oZv{HC)O#M|7~nDLpGuzx8Ud1PuEBW2v0|gM6RdpCp8?^;gEX&LVu0XfNzoeP4={>D zqFu{QCUI}Q*1ONu;&M(E{7gwQjJ=O+L*KH&dsw0#^VzVPHw*Z0^-Ifyq7aq0Q4E&K zmd#daD23HjZWCFYqTaPy&FcJ&db`DN&p#G$`zb?Zi3F12g!CBNF-xGq~$#VcN2gjexD zJ5&|!8Ma__h=xMTM?N!oD#@5wI#?7p9AvA^_d;(&JSKTKowWN>+A;01`O`bDY11*z zi@==`POF%dtM)7M$riPV%!bLR8PDL@LO8QyngKw&-WOC>%;RL2kO{;K^vUgzcMu<0 zGk@(tU)xTES)<2_TugPN3A~Z_2-gX>2rSbtAkKA+N5*0CVPai^#NiV;`#123Q6H1< zuZ4X$W0rA!kIIy9pJJx5`4dFxv)N*%8Amz$@J-B7WkNI8rov3T{G3q;w`xjsSwl@o z+5DPf`5d`!1s6S(C{c%meoAx|U|4Up3S_$Ii8Xtl;wUC(hi;;vL_PE7H=homg$m8M zCgiA(7K0L0Q^SrUhnLk0C@ZhK8+EDf+RK=5#mZ#|o{=wxuXkmX2i_VL87it2WNF;` zPW!fHbZK&G+-L523~6N_f#|z3>|R(>Zdj07{Ek+y<#wjGI2FZ%OpBy#&Yw7p)G_Zy zhF+F+M$QM%ly($(&ntLt#0ZfK@89nC21}{^6Zid%TbP>zzF^`F1dx3VNfUp007K67&jUQfkG%6{19au2`j)7xCnRvApjA$ z44OA^1&{*l!TT@d65s)_67?n0U7`XPzO;ZKn?U%z@r4XgN!OzP6<#3Q5Eoe z8ek3<DaaTx5I}H>{(?Jp9q`J#N69+O0W6^JtuFqcdOOvTVtx`{dooznBYq@ zIZ%OYXfTY)=baxNnu-C8dGl98b1&Gjd(Exds)dHl(x}UEI_>zTOq<#tLOquX^V$X< z3A#tA59%NCzx4e&tB$;&8xealn^a-0NuL}0)^_C$Z&K*>7V>4AE3)4~ILnKv_g_l0zd zM;#DixWD*DD%WmJ2% zgSMg2l0RR8IOcMs(^XOa3gd*pl(?faPoi_|Gni#Ofie-fc}zj1?ej`PFQ?ktk#_AO z=pQx@t;4yZ^xZu^1;zC(Maw-#YZ z7`p}8j;el)E`3)!aQu7J59Eu_hcvFQv>g8GiED1BU`kHd=be>S&pGMKcDBcH=cONz zk+=3puHOrt#_>9YG->^?HyJo5%+ee~`h1zh>KNRBQ1-nWVY8}wIs$#q?b!0iJsfo} zZc2r8j!+xVn0;qu*m&@X^0$;W*%$6rW=KPyBSsh;MiRpb<3RJVvmv2EN1wK7!#jy5 paSQ`EEQRFNk7!859w zoR886PI>)hEXiWe%+9%Wl1@f;P+d0+myvTTjK?4Ib9&s$`UKKos8zHwb7q3ZwW9mu z)-zTtLhyy-4hvmyq1tD0Z$b5gT^S$8Bjmpv^y&W6Qf}*5N=c9nVahBvK*6={#CY&o zumAHs`mvsc-tNy=T_2|TcLPFq57`{nio9zEIq=FA6QOZyPm(;DQt1pHxvQ8VHvzVW z!U4ax1kcFk&|Y}dZiENQ9*E`>;nm3?3l=`U%oohYuBq@Ujr#Sh#JGFm>&;9U(kZB# zY5oVxmvNImO)i=JG|p>{$J%pRzyD&xsgTE_K`R^zI^(IUVJ?_LrT2lD55E-x?Rg`h zQP&e83!H+r6VQm15HegfBv2evxqjA)WGF`VLIvT?OHonHceD&mpIxja*lbT#u=0Bl zlZDqhLkIzqWn+AV_ntHp07|J*!0ms-hiz#t`kbn9T7}4 z9qRDgT6BCz9X4XGJv|6OZ09wVu*Zk^MzywfC#v8Y>*W_%rRsqH?^F~ZP_D~>?aFul zuOi8rZ3^4t-9+u+Nj9GE15EJbb)JRdq!jXbn9h^H$!SP8zP)KY^#YHfhCV1`Ba3e zoXNaN6Hg4xjnX1qks_AM?P~i+GP66(P|qJTr7}aUQw)lQMJ}HRt@H12qXQ$1AF_r7 z-fJn--p%7wy)Nx0lVgEC4Dls_{#LR>vQ+eMTP#d|9Vig9mB zXk7s5`SNkugxj{<09z#mcK7_;Rl1t}CiA|fGTdScjfxSC^Si6^+W_$1{*1;iZ(y?H z)g(lA>!vM!bP95uM?NRv8^5Z$XYR=B81kl%HgE=vCR5bFXf{qMNHtfT^;reai|H-JUfN*ys;Gq*VGYMx)cE{wvXHRgOkDr7(fxVOwd^(G zi-C6TcyYrnDMsh-?(Aj&l$?nxzXiXZ$VvzCA%H+wN5b8LO)adNb*|KzIZq^{MQCuN zl?F;RKSTwy$d?>w^_C*_cCF*o2uvgAFAN#6TP_VgBI6wAA#{#G(|J{1U&^5<^}CZjcPOFiS{kafzY0 zfhdT}EX?PbSCU$ko0^iDSd!}OXdoxfYiMa;YG7n!Xk=+%93{>Rq+nd5C<9M2Efh51 zhgirFTAW(soLW?3(8Q>O>>Wl{2IeM4eg>d87gG}>Bg1}{RtfVTQQmV?uH5}svYo41 z^^{|qOxN$r=M1$(48KfT$}{iSM$Z4bTMtLx%Ul%N_UG!N8vDDO)^U7$bM0g9TsODo z8A8&*@jt)KYJT-9ifPU2CHb++nVWXcT>91Mc;e6g@YcV~m&`(#lAdpU;dC(ZbyLZW zo`}8qX`Y+94rd*Dys#=xFV0h)Vb0?dA}-xe*R&`4$@FyaX@3wZ_r162^}^EVh+Hwt z!?t-PC!c0=87?jqn)zICd*YQwXBGvPj94l4{oFb?4C@t6`!n8J@#Eu*rk96T_;epF z^k4|c-SusQ3+t4!+6OuRs&$;^|Ni%6?@6_m8_Jwp+YB8qz4+`F={Td1iJ6gsadG1| zgT~DUJix${6=r1o&%$KDV4w-&sj?^;D6ny9voW$THkorVv#@ah*{rPWNGv9U3XoDp zmO_L41_5Q~Ckj@qH{W$_na227=YPUNPXl){qmv&PbKvyD7L=b~;_TQY@c1dGnRUR= zUiU7~#!va)+{jT2OsT-AWn^e?mHXLkwprtE+)1A6vgO)KH`VUH_tvKA;Px~*i<*FE zUf(+^V=d3j^6YyyXXf94T8=A`qEiec7L{K)ySyt%gH>I-H^A!<|GnU#XIx?u5>{u- z*3L70G(%)z<3Y32^MdEb&0Rj%S5{;4!nAe&^nW~C!Buk2x^%`~{Zm}oe-iR8PT%}? zo{R1335N?e*`1G`{9k=a?ejE5yM z8r{A~zFGD~y=TJv10N+)ShuMOh#c3bTq(nOU1{mWt)A_XM_uJ6OF1vSen~BvyRM~r g*KE#(aYvK#ECqZ@G9AsXb6iuhR7kmCqaNu90H3gB*8l(j literal 0 HcmV?d00001 diff --git a/TestFramework/TestData/CERTS/UserCert.pem b/TestFramework/TestData/CERTS/UserCert.pem new file mode 100644 index 0000000..3d8a6c7 --- /dev/null +++ b/TestFramework/TestData/CERTS/UserCert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2TCCAsGgAwIBAgIQcpeiEpdb0btAqETArYzEnTANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0MQ0wCwYDVQQL +EwRUZXN0MRcwFQYDVQQDEw5JbnRlcm1lZGlhdGVDQTAeFw0xOTA1MDIyMTI5MDNa +Fw0yOTA1MDIyMTI5MDJaMEkxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9TYWZlZ3Vh +cmREb3ROZXQxDTALBgNVBAsTBFRlc3QxETAPBgNVBAMTCFVzZXJDZXJ0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvwSFGDf4WkudZNTd/nS3CnslykGG +HIr7084xKhQx9JKlDJ7GsQn/LbXDWd5polWG/NWifD/dsq4I9uzW8W2dRkaDmBIb +U1/59pqD6upaAqyrpG9dI2myu5ml9TLHYfmPV4X9A9I2VAJi57XoQsFh64J02IxY +vW9mSbKKw2rC46F6Xi5eSScAnOPIFESL5ayHYU4cjIgOK/ASd03esuuhdVtYbRY5 +wz1udMnlaQoxo3ESmecRt2HUgUMEIARoXRonvwss2DF/IMtPAdqo+PHogunDqEyL +xXFIAFBtuvawRAWUdn3gbP57LEKf+/7kvckmhNgjCbWGMUHS6PNGWUGYcQIDAQAB +o4G2MIGzMAwGA1UdEwEB/wQCMAAwKQYDVR0lBCIwIAYKKwYBBAGCNwoDBAYIKwYB +BQUHAwQGCCsGAQUFBwMCMHgGA1UdAQRxMG+AECND5CA6BdnuirSWAeMs/2ChSTBH +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPU2FmZWd1YXJkRG90TmV0MQ0wCwYDVQQL +EwRUZXN0MQ8wDQYDVQQDEwZSb290Q0GCEOPlCTY7UPmNR4pJgfJvSwswDQYJKoZI +hvcNAQELBQADggEBAIeFHvmLNrMo/V7JDNcddyulsn2/3u08gsG3Zh44fFDmSveI +eV05zJpJjuacmf1QfQjUWRWUMBiid9TNp4pSKAUnK41QSsIP3lNS5goWGBg6zDat +njHimBShgcE2y55TnV6dp51NHSiToWau/i/45qgKdNY7dZi9L8oKa/xgbtGXs+2e +RD2rkMNxsj7PW5P/J5R9t2UsA7i8VIi2Q+Mse0FDNnx8OZK9cN+Jc6mmZQArAcMY +aIFG9BnspvQnjJDvwPEYZAW2JhAUxyh5qRwJ1yKlkbVJh1nFRR6TGkOl19ImYwt+ +hHu6mwmhXsVibjkQTHRpQTbXCNYiOSBk0DwnWUE= +-----END CERTIFICATE----- diff --git a/TestFramework/TestData/CERTS/UserCert.pfx b/TestFramework/TestData/CERTS/UserCert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..6d895016a2aab9ab00f4d299c602b697d84de20b GIT binary patch literal 2782 zcmZWqc{r4N8-8bcXNHk2lWh=pYlx2ib*2sskt852Z%N}x& zHBy{pYhsjLA#$>QZ|C~HbFT0D{&;@B`@Wy;zx%<_(c^H~Asii@ie#6IBt~v=!cefs zbaWI*M@K+%D2|R~`=1oz2S`V3;^>GqC{~ah|LHo6hQS}x!F?PZ+{TF@(f^g9;anhF zW2{202t2PI4oAHK>ENi$rulxi`KLk*lNC=Q z$z>QV)ZnFFFh{NcPNa3@dA$=W&GYYUMRtiEH~rw(n~O1tStPwSTVC;slKyft-g)C)H9tYHR8WDyVfeiRb3FHzC_9VDm0ea}l&u{7CYP>GaWBt%|~$P6uqS`3lBQFA^0Ddb}>A#uFde#|Z`TOSN-6N?+Z# zGwm{Rl56o=ew-y7$vGx#A;)%jWAJ!rbdTw{7*f|-B4VDqeAHp%;{NTVN?~WBt?!_Ej&>_e z;v4=fdn@U!+kl&#@y!`|^6wIb;+>}|6Aw+CVc-51q{vg-X$Cq(R+YE8~Gv=6pGY0LxdP@50 zz`MqwOX>T$2u?wdglx;x7w3{cvuDqyHFO66)tP%01yaJhu~sBrG9cqySE2v$SvXln z!P@=k%kO0b){O~MWxg&aEoB}HS4XysRkXf*f7acAqC!1^10{P#Vih$mf@`Uz-!_V>20jtwOR5;xIOjTa2|C9G#HRsy;3 zr+{=ClbKW7uF9R<=TgTl5E+dn9i+Ri-afgy^5e-njTEkTl@sf0YV9ri7DG1Dt#2oV zhYqt6+x}$+@Yw)si}JxWs;M`glWlnCMO#tH;r61#5!j43Q$qzdM7X1ZbMbdnmPps- z>&L=I`Ms0N_4e9_{6A4kJgkpe=2ljC(j>ZFOJT-Ix_)?Uib`9q@?;RB7CmOlyke=} zz}JWzu#8ILt=v|qxFUQ%!A*0K%iCSX-xzhvpLB0YBgJL$?A(bls%TV#GU2raeA^BvWR8VnFMQmfR`iQ1*43Vt1TN9EJh6szch`6Xm zUh4|sJyBj%fbK18rX~Yf^jpiv?Zeka5+7b>8(x`!ZOojLHrI&0E-yK1(CFb6lE4JZ z4zgSF~@$EFqk*1GLGr?CgZV;jpVMPXW!-UbflB&)uf9e<@0*`#4koE2qcI3OWI zTDq&mNz^eJA3lNO``a$rkK&Oa2m=5BcL~WZkA#6RI)N94w-U1iEC5fy74QQnz)dKA z0jPjDU<3!81>%0dRS0|h)g=yC{Z)4hVr0M#@WZ+NvlD}N6mx(I zZUNqa9pD2ssRO5>RjPn8a26l{YS17>h@Am&kgf>*Zjj~*p{swPYYo$~i06cV!gE0A;%4g;Xi{+|r&{|~^7{S=;qOHu3R0SKdmG6PLsbAkq5 z5v8d;E8!2k7o|^pEK}v@m2~0B#G~_5(%ZA=_+%Nx3|PB z%Ov47)rBX7Y$W78ri-4MWy7ih`wz#>*0iuSDtE3{PxeDS%KtT_2&mO@kU(MteUBJ zxe0{Wa7wYg#12s2aJBL%GEvLtP`>w8U#3g$W+x@c^n>~0%MZGZNDW#~LDo|nxOkA= z^8HF(q9aYr04(KrKL{#2QG!9(i26AD&GpZ4cqs>FSLj84bR$wWa_MZ4j{~gALu9%W zQy!{*b!uZ+dz$lxc5mkwNog@%te%41CDw-go5>Kc%H1bJhn0j~0%k8vuj?#G+z&ZW z(;2qlx~i9{dsqULl~5O%JLVp5D=rKW-@?t;uvJ@mtiHQ(^55>}H_N8e8^>uE9UU6# z#w=+W1W~P&P-`Q>a>nNfTg7cOzm8R_{&_@n=G2G5C{tP?EL8HAVqKjBb?3;Aui`5# z!9wWIvRqjnLtYOlo|1D`-q$`{R@_7+Re!!fsv@6~)+N~J^XH&c^&^FwUGDU2xL%M> zwEmrk%GWzR9HE?0+B>%iPR6dd3f0=>U;Ca^e0}goplC$~=)1uky((Ik9Z4f1oi;C_ zH9bmhQy(r{^vZF%b3ERb`%+O=+z^>stPC%b{hD+(yI z!%YJiwT5bz_y9P9N=WUXaQxpL+Fj_onE#Y;? zr!_5F#zhrlCdq6GaYDiSUzvGf(jJMw6(wYHl*FEO3OGi49W?dWb~u`M520$y)KeNx zG(0qKvMa0|o-;1BV~S!u3+l(MJ#=+aa>6d%lf&JtJVJ_Y|R{?V-_LTon z&|BZJ;&@hJQsC7++KFJR`vsSFX|+DUs_SwFZ*V#@K_s%~R{nV>lENA_k+0ssI2 zqyPZFo>8GH*2sKPd}bKA{30J*rxW@RjK-?^*cBAnxVbu=^11$=nhBCa3wHw!2Eq{> z|5d1FHhTN$7FfIZ;@zPp1O3oo2+4KkXP?2F`a(lIz0M3p;~}zFIg_VOG;d_~D4+uH zZMQKU9`&z9#Z^O~*Z}dYh3w3LYCB}e8WX_@ZBuC$S%iU9AHiGVf_ySHOs@NCLiok1&+W*PgW3j*8=W_&#_?HYkg^sWsyi?1DX z-v2H!>*X|(<{`N8L^RZrGC;id z9TzGARDe}uV64D|zW2qwbK4IXiZCsf9g?xzVNM5(@zd)tq+IE3(Z^{IYA<-IaMjr4 zPNRS5g`TW<({KSBs28mdrEV-Lq6GQZh+c@7z!3vp=)6!Gl+i&Fjwt|^q#ueD_?Vjt z(z(u_^-OIXKyD$h+GvW~3p^9}f|?eYqRe^)VS8pDxodKk=+47*Y|+DN?x%O(_qB(j zIIY7u_Z+-z;M@GrrOu~Mtmz|%Q{1R`i!xIm=to1cfU~w>x7pbSA*SYEq;Id5iz#-i5AD}=L|o}Kx& zg^tof8bBdw9m2yASp>+|#Nc2S#eYt2?X!GPZX*-C0ff~+m8GbDGEqnTw{Tv`=dSh@ zLUw4t6`aE$g~gft@@!xa=M_0Ds6RhV8)+(jFNn68Y#|OWASA(JMng9NRC3Vl4RPo< z`rM{-lP8#JMdjdCM0qoOtVDaV8wZK(Xe49fyof4CDea;AVm6tuF+II}j~MlhdR}5mnkFojWLll`;%^!~iO@KB|`Ku}D96PLxSR(Krq| z@yHsv>Th;{i7`cFi8hLRT=bgZm@)uRRXTJFIzYuHW_hQ|U(}k4R!L__$K!yCDw919 zZXRq{T#wT&{(7Z^?&>cI;oIkmNV0+q98i`Qgqq_2e>F|XRF;_l49V3@hWC8|EjWKi z0aXBO;8qldv3c^*nav=1qlViE_wb~6{=To!J2TofI9AJPcYv^hNh literal 0 HcmV?d00001 diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml new file mode 100644 index 0000000..95ef224 --- /dev/null +++ b/spotbugs-exclude.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index b10fe0d..55fa820 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -8,6 +8,7 @@ import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; import com.oneidentity.safeguard.safeguardjava.ISafeguardSessionsConnection; import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.SafeguardForPrivilegedSessions; import com.oneidentity.safeguard.safeguardjava.data.FullResponse; import com.oneidentity.safeguard.safeguardjava.data.Method; import com.oneidentity.safeguard.safeguardjava.data.Service; @@ -58,6 +59,12 @@ public static void main(String[] args) { } try { + if (opts.sps) { + handleSpsRequest(opts); + System.exit(0); + return; + } + ISafeguardConnection connection = createConnection(opts); if (opts.resourceOwner != null) { @@ -142,6 +149,38 @@ private static String handleStreamingRequest(ToolOptions opts, ISafeguardConnect } } + private static void handleSpsRequest(ToolOptions opts) throws Exception { + char[] password = null; + if (opts.readPassword) { + System.err.print("Password: "); + password = new Scanner(System.in).nextLine().toCharArray(); + } + + System.err.println("Connecting to SPS " + opts.appliance + " as " + opts.username); + ISafeguardSessionsConnection spsConnection = SafeguardForPrivilegedSessions.Connect( + opts.appliance, opts.username, password, opts.insecure); + + Method method = parseMethod(opts.method); + + if (opts.full) { + FullResponse response = spsConnection.invokeMethodFull(method, opts.relativeUrl, opts.body); + ObjectNode json = mapper.createObjectNode(); + json.put("StatusCode", response.getStatusCode()); + ArrayNode headersArray = json.putArray("Headers"); + for (Header h : response.getHeaders()) { + ObjectNode headerObj = mapper.createObjectNode(); + headerObj.put("Name", h.getName()); + headerObj.put("Value", h.getValue()); + headersArray.add(headerObj); + } + json.put("Body", response.getBody()); + System.out.println(mapper.writeValueAsString(json)); + } else { + String result = spsConnection.invokeMethod(method, opts.relativeUrl, opts.body); + System.out.println(result); + } + } + private static ISafeguardConnection createConnection(ToolOptions opts) throws Exception { char[] password = null; if (opts.readPassword) { diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java index 3693150..fb6dd08 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -95,6 +95,10 @@ public class ToolOptions { description = "File path for streaming upload (POST) or download (GET)") String file; + @Option(names = {"--sps"}, defaultValue = "false", + description = "Connect to Safeguard for Privileged Sessions (SPS) instead of SPP") + boolean sps; + @Option(names = {"--interactive"}, defaultValue = "false", description = "Run in interactive menu mode (legacy)") boolean interactive; From 89bf6626419412ab23814c89481b583cac245b4e Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sat, 28 Mar 2026 10:31:00 -0600 Subject: [PATCH 17/22] SDK samples --- README.md | 15 +++ Samples/A2ARetrievalExample/README.md | 56 +++++++++ Samples/A2ARetrievalExample/pom.xml | 56 +++++++++ .../samples/A2ARetrievalExample.java | 83 +++++++++++++ Samples/CertificateConnect/README.md | 42 +++++++ Samples/CertificateConnect/pom.xml | 56 +++++++++ .../safeguard/samples/CertificateConnect.java | 67 +++++++++++ Samples/EventListenerExample/README.md | 61 ++++++++++ Samples/EventListenerExample/pom.xml | 56 +++++++++ .../samples/EventListenerExample.java | 113 ++++++++++++++++++ Samples/PasswordConnect/README.md | 33 +++++ Samples/PasswordConnect/pom.xml | 56 +++++++++ .../safeguard/samples/PasswordConnect.java | 66 ++++++++++ Samples/README.md | 61 ++++++++++ 14 files changed, 821 insertions(+) create mode 100644 Samples/A2ARetrievalExample/README.md create mode 100644 Samples/A2ARetrievalExample/pom.xml create mode 100644 Samples/A2ARetrievalExample/src/main/java/com/oneidentity/safeguard/samples/A2ARetrievalExample.java create mode 100644 Samples/CertificateConnect/README.md create mode 100644 Samples/CertificateConnect/pom.xml create mode 100644 Samples/CertificateConnect/src/main/java/com/oneidentity/safeguard/samples/CertificateConnect.java create mode 100644 Samples/EventListenerExample/README.md create mode 100644 Samples/EventListenerExample/pom.xml create mode 100644 Samples/EventListenerExample/src/main/java/com/oneidentity/safeguard/samples/EventListenerExample.java create mode 100644 Samples/PasswordConnect/README.md create mode 100644 Samples/PasswordConnect/pom.xml create mode 100644 Samples/PasswordConnect/src/main/java/com/oneidentity/safeguard/samples/PasswordConnect.java create mode 100644 Samples/README.md diff --git a/README.md b/README.md index 789752b..8bff6b1 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,21 @@ connection.dispose(); Calling the simple 'Me' endpoint provides information about the currently logged on user. +## Samples + +The [Samples](Samples/) directory contains standalone example projects demonstrating +common SafeguardJava usage patterns: + +| Sample | Description | +|--------|-------------| +| [PasswordConnect](Samples/PasswordConnect/) | Connect using username and password, call the API | +| [CertificateConnect](Samples/CertificateConnect/) | Connect using a PKCS12 client certificate file | +| [A2ARetrievalExample](Samples/A2ARetrievalExample/) | Retrieve a credential via Application-to-Application (A2A) | +| [EventListenerExample](Samples/EventListenerExample/) | Subscribe to real-time Safeguard events via SignalR | + +Each sample is a self-contained Maven project that can be built and run independently. +See the README in each sample directory for prerequisites and usage instructions. + ## About the Safeguard API The Safeguard API is a REST-based Web API. Safeguard API endpoints are called diff --git a/Samples/A2ARetrievalExample/README.md b/Samples/A2ARetrievalExample/README.md new file mode 100644 index 0000000..f315d83 --- /dev/null +++ b/Samples/A2ARetrievalExample/README.md @@ -0,0 +1,56 @@ +# A2ARetrievalExample Sample + +Demonstrates using Safeguard Application-to-Application (A2A) to retrieve +credentials without requiring interactive login or access request workflow +approvals. This is the primary integration method for automated systems. + +## Prerequisites + +Before running this sample, you must configure Safeguard: + +1. **Create an Application user** — Register an application in Safeguard with + certificate authentication +2. **Configure A2A registrations** — Set up credential retrieval registrations + for the target asset accounts +3. **Note the API key** — Each retrieval registration is assigned an API key + that authorizes access to a specific credential +4. **Have the PKCS12/PFX certificate** — The A2A application's client certificate + +## Building + +```bash +mvn clean package +``` + +## Running + +```bash +java -jar target/a2a-retrieval-1.0-SNAPSHOT-jar-with-dependencies.jar +``` + +### Arguments + +| Argument | Description | +|----------|-------------| +| appliance | IP or hostname of Safeguard appliance | +| certificate-file | Path to PKCS12/PFX A2A client certificate | +| api-key | A2A API key for the credential retrieval registration | + +You will be prompted for the certificate password. + +### Example + +```bash +java -jar target/a2a-retrieval-1.0-SNAPSHOT-jar-with-dependencies.jar \ + safeguard.example.com /path/to/a2a-cert.pfx "A1B2C3D4-E5F6-7890-ABCD-EF1234567890" +``` + +## How It Works + +1. Creates an A2A context using the client certificate +2. Lists all retrievable accounts available to the certificate +3. Retrieves the password for the specified API key +4. Clears sensitive data from memory + +In a real application, the retrieved password would be used to connect to the +target system (database, server, etc.) rather than printed to the console. diff --git a/Samples/A2ARetrievalExample/pom.xml b/Samples/A2ARetrievalExample/pom.xml new file mode 100644 index 0000000..4c54534 --- /dev/null +++ b/Samples/A2ARetrievalExample/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.oneidentity.safeguard.samples + a2a-retrieval + 1.0-SNAPSHOT + jar + + SafeguardJava Sample - A2A Credential Retrieval + + + 1.8 + 1.8 + UTF-8 + + + + + com.oneidentity.safeguard + safeguardjava + 7.5.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + + com.oneidentity.safeguard.samples.A2ARetrievalExample + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/Samples/A2ARetrievalExample/src/main/java/com/oneidentity/safeguard/samples/A2ARetrievalExample.java b/Samples/A2ARetrievalExample/src/main/java/com/oneidentity/safeguard/samples/A2ARetrievalExample.java new file mode 100644 index 0000000..4ab6f13 --- /dev/null +++ b/Samples/A2ARetrievalExample/src/main/java/com/oneidentity/safeguard/samples/A2ARetrievalExample.java @@ -0,0 +1,83 @@ +package com.oneidentity.safeguard.samples; + +import com.oneidentity.safeguard.safeguardjava.ISafeguardA2AContext; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.data.A2ARetrievableAccount; + +import java.io.Console; +import java.util.List; + +/** + * Demonstrates using Safeguard Application-to-Application (A2A) to retrieve + * credentials without requiring an interactive login or access request workflow. + * + * A2A is the primary integration method for automated systems (scripts, services, + * CI/CD pipelines) that need to fetch passwords from Safeguard. It requires + * client certificate authentication and is protected by API keys and IP restrictions. + * + * Prerequisites: + * 1. Register an Application in Safeguard with certificate authentication + * 2. Configure A2A credential retrieval registrations for the target accounts + * 3. Note the API key(s) assigned to each retrieval registration + */ +public class A2ARetrievalExample { + + public static void main(String[] args) { + if (args.length < 3) { + System.out.println("Usage: A2ARetrievalExample "); + System.out.println(); + System.out.println(" appliance - IP address or hostname of Safeguard appliance"); + System.out.println(" certificate-file - Path to PKCS12/PFX A2A client certificate"); + System.out.println(" api-key - A2A API key for the credential retrieval registration"); + System.out.println(); + System.out.println("You will be prompted for the certificate password."); + return; + } + + String appliance = args[0]; + String certificateFile = args[1]; + char[] apiKey = args[2].toCharArray(); + + Console console = System.console(); + char[] certPassword; + if (console != null) { + certPassword = console.readPassword("Certificate password: "); + } else { + System.out.print("Certificate password: "); + certPassword = new java.util.Scanner(System.in).nextLine().toCharArray(); + } + + ISafeguardA2AContext a2aContext = null; + try { + // Create an A2A context using certificate authentication + a2aContext = Safeguard.A2A.getContext(appliance, certificateFile, certPassword, null, true, null); + + // List all retrievable accounts available to this certificate + System.out.println("Retrievable accounts:"); + List accounts = a2aContext.getRetrievableAccounts(); + for (A2ARetrievableAccount account : accounts) { + System.out.println(" Account: " + account.getAccountName() + + " (Asset: " + account.getAssetName() + + ", ApiKey: " + new String(account.getApiKey()) + ")"); + } + + // Retrieve the password using the API key + System.out.println("\nRetrieving password for API key: " + new String(apiKey)); + char[] password = a2aContext.retrievePassword(apiKey); + System.out.println("Password retrieved successfully (length: " + password.length + ")"); + + // IMPORTANT: Clear sensitive data from memory when done + java.util.Arrays.fill(password, '\0'); + + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } finally { + if (a2aContext != null) { + a2aContext.dispose(); + } + java.util.Arrays.fill(certPassword, '\0'); + java.util.Arrays.fill(apiKey, '\0'); + } + } +} diff --git a/Samples/CertificateConnect/README.md b/Samples/CertificateConnect/README.md new file mode 100644 index 0000000..5dba2a8 --- /dev/null +++ b/Samples/CertificateConnect/README.md @@ -0,0 +1,42 @@ +# CertificateConnect Sample + +Demonstrates connecting to Safeguard using client certificate authentication +with a PKCS12 (PFX) file. This is the recommended authentication method for +automated processes and services. + +## Prerequisites + +Before running this sample, you must configure Safeguard: + +1. **Upload the CA certificate chain** — Add the root CA and any intermediate CAs + to Safeguard's trusted certificates (`POST TrustedCertificates`) +2. **Create a certificate user** — Create a Safeguard user with the + `PrimaryAuthenticationProvider` set to Certificate (Id: -2) with the + client certificate's thumbprint as the Identity + +## Building + +```bash +mvn clean package +``` + +## Running + +```bash +java -jar target/certificate-connect-1.0-SNAPSHOT-jar-with-dependencies.jar +``` + +### Arguments + +| Argument | Description | +|----------|-------------| +| appliance | IP or hostname of Safeguard appliance | +| certificate-file | Path to PKCS12/PFX client certificate file | + +You will be prompted for the certificate password. + +### Example + +```bash +java -jar target/certificate-connect-1.0-SNAPSHOT-jar-with-dependencies.jar safeguard.example.com /path/to/client.pfx +``` diff --git a/Samples/CertificateConnect/pom.xml b/Samples/CertificateConnect/pom.xml new file mode 100644 index 0000000..73533f4 --- /dev/null +++ b/Samples/CertificateConnect/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.oneidentity.safeguard.samples + certificate-connect + 1.0-SNAPSHOT + jar + + SafeguardJava Sample - Certificate Connect + + + 1.8 + 1.8 + UTF-8 + + + + + com.oneidentity.safeguard + safeguardjava + 7.5.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + + com.oneidentity.safeguard.samples.CertificateConnect + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/Samples/CertificateConnect/src/main/java/com/oneidentity/safeguard/samples/CertificateConnect.java b/Samples/CertificateConnect/src/main/java/com/oneidentity/safeguard/samples/CertificateConnect.java new file mode 100644 index 0000000..1cd48f9 --- /dev/null +++ b/Samples/CertificateConnect/src/main/java/com/oneidentity/safeguard/samples/CertificateConnect.java @@ -0,0 +1,67 @@ +package com.oneidentity.safeguard.samples; + +import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.data.Method; +import com.oneidentity.safeguard.safeguardjava.data.Service; + +import java.io.Console; + +/** + * Demonstrates connecting to Safeguard using client certificate authentication + * with a PKCS12 (PFX) file. This is the recommended authentication method for + * automated processes and services. + * + * Prerequisites: + * 1. Upload the certificate's CA chain to Safeguard as trusted certificates + * 2. Create a certificate user in Safeguard mapped to the client certificate's thumbprint + * 3. Have the PKCS12/PFX file and its password available + */ +public class CertificateConnect { + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("Usage: CertificateConnect "); + System.out.println(); + System.out.println(" appliance - IP address or hostname of Safeguard appliance"); + System.out.println(" certificate-file - Path to PKCS12/PFX client certificate file"); + System.out.println(); + System.out.println("You will be prompted for the certificate password."); + return; + } + + String appliance = args[0]; + String certificateFile = args[1]; + + Console console = System.console(); + char[] certPassword; + if (console != null) { + certPassword = console.readPassword("Certificate password: "); + } else { + System.out.print("Certificate password: "); + certPassword = new java.util.Scanner(System.in).nextLine().toCharArray(); + } + + ISafeguardConnection connection = null; + try { + // Connect to Safeguard using client certificate authentication + // The certificate file must be in PKCS12 (PFX) format + connection = Safeguard.connect(appliance, certificateFile, certPassword, null, true, null); + + // Verify the connection by calling the "Me" endpoint + String me = connection.invokeMethod(Service.Core, Method.Get, "Me", + null, null, null, null); + System.out.println("Authenticated user info:"); + System.out.println(me); + + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } finally { + if (connection != null) { + connection.dispose(); + } + java.util.Arrays.fill(certPassword, '\0'); + } + } +} diff --git a/Samples/EventListenerExample/README.md b/Samples/EventListenerExample/README.md new file mode 100644 index 0000000..4691d17 --- /dev/null +++ b/Samples/EventListenerExample/README.md @@ -0,0 +1,61 @@ +# EventListenerExample Sample + +Demonstrates subscribing to real-time events from Safeguard using the SignalR +persistent event listener. Events are printed to the console as they arrive. +The listener automatically reconnects if the connection is interrupted. + +## Building + +```bash +mvn clean package +``` + +## Running + +```bash +java -jar target/event-listener-1.0-SNAPSHOT-jar-with-dependencies.jar [provider] [username] [events...] +``` + +### Arguments + +| Argument | Description | Default | +|----------|-------------|---------| +| appliance | IP or hostname of Safeguard appliance | (required) | +| provider | Identity provider name | `local` | +| username | Username to authenticate | `Admin` | +| events | Event names to subscribe to | `UserCreated UserDeleted` | + +You will be prompted for the password. Press Enter to stop listening. + +### Example + +Listen for access request events: + +```bash +java -jar target/event-listener-1.0-SNAPSHOT-jar-with-dependencies.jar \ + safeguard.example.com local Admin \ + AccessRequestCreated AccessRequestApproved AccessRequestDenied +``` + +### Common Events + +| Event | Description | +|-------|-------------| +| `AccessRequestCreated` | A new access request was submitted | +| `AccessRequestApproved` | An access request was approved | +| `AccessRequestDenied` | An access request was denied | +| `AccessRequestExpired` | An access request expired without action | +| `UserCreated` | A new user was created | +| `UserDeleted` | A user was deleted | +| `UserModified` | A user's properties were changed | +| `AssetCreated` | A new asset was added | +| `AssetDeleted` | An asset was removed | +| `AssetModified` | An asset's properties were changed | + +## How It Works + +1. Connects to Safeguard using password authentication +2. Creates a persistent event listener (auto-reconnects on disconnect) +3. Registers an event handler for each specified event name +4. Starts listening — events are printed to the console as they arrive +5. Press Enter to cleanly disconnect and exit diff --git a/Samples/EventListenerExample/pom.xml b/Samples/EventListenerExample/pom.xml new file mode 100644 index 0000000..bd34103 --- /dev/null +++ b/Samples/EventListenerExample/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.oneidentity.safeguard.samples + event-listener + 1.0-SNAPSHOT + jar + + SafeguardJava Sample - Event Listener + + + 1.8 + 1.8 + UTF-8 + + + + + com.oneidentity.safeguard + safeguardjava + 7.5.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + + com.oneidentity.safeguard.samples.EventListenerExample + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/Samples/EventListenerExample/src/main/java/com/oneidentity/safeguard/samples/EventListenerExample.java b/Samples/EventListenerExample/src/main/java/com/oneidentity/safeguard/samples/EventListenerExample.java new file mode 100644 index 0000000..6c5f4b4 --- /dev/null +++ b/Samples/EventListenerExample/src/main/java/com/oneidentity/safeguard/samples/EventListenerExample.java @@ -0,0 +1,113 @@ +package com.oneidentity.safeguard.samples; + +import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; +import com.oneidentity.safeguard.safeguardjava.ISafeguardEventListener; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.event.ISafeguardEventHandler; +import com.oneidentity.safeguard.safeguardjava.event.PersistentSafeguardEventListenerBase; + +import java.io.Console; + +/** + * Demonstrates subscribing to real-time events from Safeguard using the SignalR + * persistent event listener. The listener automatically reconnects if the connection + * is interrupted. + * + * Events are fired when state changes occur in Safeguard, such as: + * - Access request lifecycle events (new, approved, denied, expired, etc.) + * - User login/logout events + * - Asset discovery and account management events + * - Appliance health and configuration changes + */ +public class EventListenerExample { + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("Usage: EventListenerExample [provider] [username] [events...]"); + System.out.println(); + System.out.println(" appliance - IP address or hostname of Safeguard appliance"); + System.out.println(" provider - Identity provider name (default: local)"); + System.out.println(" username - Username to authenticate (default: Admin)"); + System.out.println(" events - Event names to subscribe to (default: UserCreated UserDeleted)"); + System.out.println(); + System.out.println("Common events:"); + System.out.println(" AccessRequestCreated, AccessRequestApproved, AccessRequestDenied"); + System.out.println(" UserCreated, UserDeleted, UserModified"); + System.out.println(" AssetCreated, AssetDeleted, AssetModified"); + System.out.println(); + System.out.println("Press Ctrl+C to stop listening."); + return; + } + + String appliance = args[0]; + String provider = args.length > 1 ? args[1] : "local"; + String username = args.length > 2 ? args[2] : "Admin"; + + // Collect event names from remaining args, or use defaults + String[] eventNames; + if (args.length > 3) { + eventNames = new String[args.length - 3]; + System.arraycopy(args, 3, eventNames, 0, eventNames.length); + } else { + eventNames = new String[]{"UserCreated", "UserDeleted"}; + } + + Console console = System.console(); + char[] password; + if (console != null) { + password = console.readPassword("Password: "); + } else { + System.out.print("Password: "); + password = new java.util.Scanner(System.in).nextLine().toCharArray(); + } + + // Create the event handler that will process incoming events + ISafeguardEventHandler handler = (eventName, eventBody) -> { + System.out.println("=== Event Received ==="); + System.out.println("Event: " + eventName); + System.out.println("Body: " + eventBody); + System.out.println("======================"); + }; + + ISafeguardConnection connection = null; + ISafeguardEventListener listener = null; + try { + // Connect to Safeguard + connection = Safeguard.connect(appliance, provider, username, password, null, true); + java.util.Arrays.fill(password, '\0'); + + // Create a persistent event listener that auto-reconnects + listener = Safeguard.Event.getPersistentEventListener( + connection, null, true); + + // Register the handler for each event + for (String eventName : eventNames) { + listener.registerEventHandler(eventName, handler); + System.out.println("Registered handler for: " + eventName); + } + + // Start listening for events + listener.start(); + System.out.println("\nListening for events... Press Enter to stop."); + + // Wait for user to press Enter + if (console != null) { + console.readLine(); + } else { + System.in.read(); + } + + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } finally { + if (listener != null) { + listener.stop(); + listener.dispose(); + } + if (connection != null) { + connection.dispose(); + } + } + } +} diff --git a/Samples/PasswordConnect/README.md b/Samples/PasswordConnect/README.md new file mode 100644 index 0000000..d2a8713 --- /dev/null +++ b/Samples/PasswordConnect/README.md @@ -0,0 +1,33 @@ +# PasswordConnect Sample + +Demonstrates the simplest way to connect to Safeguard: username and password +authentication. After connecting, it calls the `GET Me` endpoint to retrieve +information about the authenticated user. + +## Building + +```bash +mvn clean package +``` + +## Running + +```bash +java -jar target/password-connect-1.0-SNAPSHOT-jar-with-dependencies.jar [provider] [username] +``` + +### Arguments + +| Argument | Description | Default | +|----------|-------------|---------| +| appliance | IP or hostname of Safeguard appliance | (required) | +| provider | Identity provider name | `local` | +| username | Username to authenticate | `Admin` | + +You will be prompted for the password. + +### Example + +```bash +java -jar target/password-connect-1.0-SNAPSHOT-jar-with-dependencies.jar safeguard.example.com local Admin +``` diff --git a/Samples/PasswordConnect/pom.xml b/Samples/PasswordConnect/pom.xml new file mode 100644 index 0000000..b9a7639 --- /dev/null +++ b/Samples/PasswordConnect/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.oneidentity.safeguard.samples + password-connect + 1.0-SNAPSHOT + jar + + SafeguardJava Sample - Password Connect + + + 1.8 + 1.8 + UTF-8 + + + + + com.oneidentity.safeguard + safeguardjava + 7.5.0 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + + com.oneidentity.safeguard.samples.PasswordConnect + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/Samples/PasswordConnect/src/main/java/com/oneidentity/safeguard/samples/PasswordConnect.java b/Samples/PasswordConnect/src/main/java/com/oneidentity/safeguard/samples/PasswordConnect.java new file mode 100644 index 0000000..5da5f2a --- /dev/null +++ b/Samples/PasswordConnect/src/main/java/com/oneidentity/safeguard/samples/PasswordConnect.java @@ -0,0 +1,66 @@ +package com.oneidentity.safeguard.samples; + +import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.data.Method; +import com.oneidentity.safeguard.safeguardjava.data.Service; + +import java.io.Console; + +/** + * Demonstrates connecting to Safeguard using username and password authentication, + * then calling the API to retrieve information about the current user. + */ +public class PasswordConnect { + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("Usage: PasswordConnect [provider] [username]"); + System.out.println(); + System.out.println(" appliance - IP address or hostname of Safeguard appliance"); + System.out.println(" provider - Identity provider name (default: local)"); + System.out.println(" username - Username to authenticate (default: Admin)"); + return; + } + + String appliance = args[0]; + String provider = args.length > 1 ? args[1] : "local"; + String username = args.length > 2 ? args[2] : "Admin"; + + Console console = System.console(); + char[] password; + if (console != null) { + password = console.readPassword("Password: "); + } else { + System.out.print("Password: "); + password = new java.util.Scanner(System.in).nextLine().toCharArray(); + } + + ISafeguardConnection connection = null; + try { + // Connect to Safeguard using password authentication + // Set ignoreSsl=true for lab environments; use a HostnameVerifier in production + connection = Safeguard.connect(appliance, provider, username, password, null, true); + + // Call the "Me" endpoint to get info about the authenticated user + String me = connection.invokeMethod(Service.Core, Method.Get, "Me", + null, null, null, null); + System.out.println("Current user info:"); + System.out.println(me); + + // Get access token lifetime remaining + int lifetime = connection.getAccessTokenLifetimeRemaining(); + System.out.println("\nToken lifetime remaining: " + lifetime + " seconds"); + + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } finally { + if (connection != null) { + connection.dispose(); + } + // Clear password from memory + java.util.Arrays.fill(password, '\0'); + } + } +} diff --git a/Samples/README.md b/Samples/README.md new file mode 100644 index 0000000..67f4e5c --- /dev/null +++ b/Samples/README.md @@ -0,0 +1,61 @@ +# SafeguardJava Samples + +These sample projects demonstrate common usage patterns for the SafeguardJava SDK. +Each sample is a self-contained Maven project that can be built and run independently. + +## Prerequisites + +- Java JDK 8 or later +- Maven 3.0.5 or later +- Access to a Safeguard for Privileged Passwords appliance + +## Samples + +| Sample | Description | +|--------|-------------| +| [PasswordConnect](PasswordConnect/) | Connect using username and password, call the API | +| [CertificateConnect](CertificateConnect/) | Connect using a PKCS12 client certificate file | +| [A2ARetrievalExample](A2ARetrievalExample/) | Retrieve a credential via Application-to-Application (A2A) | +| [EventListenerExample](EventListenerExample/) | Subscribe to real-time Safeguard events via SignalR | + +## Building + +Each sample can be built from its own directory: + +```bash +cd PasswordConnect +mvn clean package +``` + +## Running + +Each sample is packaged as an executable JAR with dependencies: + +```bash +java -jar target/password-connect-1.0-SNAPSHOT-jar-with-dependencies.jar +``` + +See the README in each sample directory for specific usage instructions. + +## Using a Local SDK Build + +By default these samples reference SafeguardJava from Maven Central. To use a +local build of the SDK instead, install it to your local Maven repository first: + +```bash +# From the SafeguardJava root directory +mvn clean install -Dspotbugs.skip=true +``` + +Then update the `` in the sample's `pom.xml` to match your local build +(e.g. `8.2.0-SNAPSHOT`). + +## Security Note + +These samples use `ignoreSsl=true` for convenience. In production, you should +configure proper SSL/TLS certificate validation using a `HostnameVerifier` +callback or by adding the appliance certificate to your Java trust store. + +Client certificate authentication (as shown in `CertificateConnect`) is the +recommended approach for automated processes. Certificate enrollment via CSR +ensures that private keys never leave the machine. From 0e556178aedd8cd727269d48ecca23e7e8fe78db Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sat, 28 Mar 2026 22:01:45 -0600 Subject: [PATCH 18/22] Improved some tests and fixed URL encoding of query params --- .editorconfig | 8 ++ TestFramework/SafeguardTestFramework.psm1 | 4 +- .../Suites/Suite-AccessTokenAuth.ps1 | 59 ++++++++++++++ .../Suites/Suite-AnonymousAccess.ps1 | 12 ++- TestFramework/Suites/Suite-ApiInvocation.ps1 | 77 ++++++++++++++++++ TestFramework/Suites/Suite-PasswordAuth.ps1 | 24 +++++- TestFramework/Suites/Suite-PkceAuth.ps1 | 21 ++++- TestFramework/Suites/Suite-SpsIntegration.ps1 | 23 ++++-- TestFramework/Suites/Suite-Streaming.ps1 | 4 +- .../Suites/Suite-TokenManagement.ps1 | 43 +++++++++- .../TestData/CERTS/IntermediateCA.cer | Bin 947 -> 1460 bytes .../TestData/CERTS/IntermediateCA.pvk | Bin 1212 -> 2156 bytes TestFramework/TestData/CERTS/RootCA.cer | Bin 939 -> 1437 bytes TestFramework/TestData/CERTS/RootCA.pvk | Bin 1212 -> 2217 bytes TestFramework/TestData/CERTS/UserCert.cer | Bin 989 -> 1494 bytes TestFramework/TestData/CERTS/UserCert.pvk | Bin 1212 -> 2109 bytes .../safeguardjava/restclient/RestClient.java | 11 ++- .../safeguardclient/SafeguardJavaClient.java | 21 +++++ .../safeguardclient/ToolOptions.java | 8 ++ 19 files changed, 297 insertions(+), 18 deletions(-) create mode 100644 TestFramework/Suites/Suite-AccessTokenAuth.ps1 diff --git a/.editorconfig b/.editorconfig index 33fe931..09d5ec9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,3 +17,11 @@ trim_trailing_whitespace = false [*.xml] indent_size = 4 + +[*.{pfx,cer,pvk}] +charset = unset +end_of_line = unset +indent_style = unset +indent_size = unset +insert_final_newline = unset +trim_trailing_whitespace = unset diff --git a/TestFramework/SafeguardTestFramework.psm1 b/TestFramework/SafeguardTestFramework.psm1 index ae86292..f2e0762 100644 --- a/TestFramework/SafeguardTestFramework.psm1 +++ b/TestFramework/SafeguardTestFramework.psm1 @@ -437,7 +437,7 @@ function Invoke-SgJTokenCommand { [PSCustomObject]$Context, [Parameter(Mandatory)] - [ValidateSet("TokenLifetime", "GetToken")] + [ValidateSet("TokenLifetime", "GetToken", "RefreshToken", "Logout")] [string]$Command, [Parameter()] @@ -461,6 +461,8 @@ function Invoke-SgJTokenCommand { switch ($Command) { "TokenLifetime" { $toolArgs += " -T" } "GetToken" { $toolArgs += " -G" } + "RefreshToken" { $toolArgs += " --refresh-token" } + "Logout" { $toolArgs += " -L" } } return Invoke-SgJSafeguardTool -Arguments $toolArgs -StdinLine $effectivePass diff --git a/TestFramework/Suites/Suite-AccessTokenAuth.ps1 b/TestFramework/Suites/Suite-AccessTokenAuth.ps1 new file mode 100644 index 0000000..739f280 --- /dev/null +++ b/TestFramework/Suites/Suite-AccessTokenAuth.ps1 @@ -0,0 +1,59 @@ +@{ + Name = "Access Token Authentication" + Description = "Tests connecting to Safeguard using a pre-obtained access token via Safeguard.connect(address, token)" + Tags = @("token", "auth", "core") + + Setup = { + param($Context) + + # Obtain a valid access token via password auth + $tokenResult = Invoke-SgJTokenCommand -Context $Context -Command GetToken + $Context.SuiteData["AccessToken"] = $tokenResult.AccessToken + } + + Execute = { + param($Context) + + $token = $Context.SuiteData["AccessToken"] + + # --- Connect with access token and call API --- + Test-SgJAssert "Access token auth: GET Me returns identity" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -AccessToken $token + $null -ne $result.Name + } + + # --- Verify identity matches the bootstrap admin --- + Test-SgJAssert "Access token auth: identity matches original user" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -AccessToken $token + $result.Name -eq $Context.AdminUserName.ToLower() + } + + # --- Access token auth can list objects --- + Test-SgJAssert "Access token auth: can list Users" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Users" -AccessToken $token + $items = @($result) + $items.Count -ge 1 + } + + # --- Full response works with access token auth --- + Test-SgJAssert "Access token auth: Full response returns StatusCode 200" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -AccessToken $token -Full + $result.StatusCode -eq 200 + } + + # --- Invalid access token is rejected --- + Test-SgJAssertThrows "Invalid access token is rejected" { + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -AccessToken "not-a-valid-token" + } + } + + Cleanup = { + param($Context) + # No objects created - the access token will expire naturally. + } +} diff --git a/TestFramework/Suites/Suite-AnonymousAccess.ps1 b/TestFramework/Suites/Suite-AnonymousAccess.ps1 index 73d0b6a..5fdb163 100644 --- a/TestFramework/Suites/Suite-AnonymousAccess.ps1 +++ b/TestFramework/Suites/Suite-AnonymousAccess.ps1 @@ -17,10 +17,16 @@ $null -ne $result } - Test-SgJAssert "Anonymous response contains appliance state" { + Test-SgJAssert "Anonymous status response contains ApplianceCurrentState" { $result = Invoke-SgJSafeguardApi -Context $Context ` - -Service Notification -Method Get -RelativeUrl "Status" -Anonymous -ParseJson $false - $null -ne $result -and $result.Length -gt 0 + -Service Notification -Method Get -RelativeUrl "Status" -Anonymous + $null -ne $result.ApplianceCurrentState -and $result.ApplianceCurrentState.Length -gt 0 + } + + Test-SgJAssert "Anonymous status ApplianceCurrentState is Online" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Notification -Method Get -RelativeUrl "Status" -Anonymous + $result.ApplianceCurrentState -eq "Online" } } diff --git a/TestFramework/Suites/Suite-ApiInvocation.ps1 b/TestFramework/Suites/Suite-ApiInvocation.ps1 index 7c4b933..83cd664 100644 --- a/TestFramework/Suites/Suite-ApiInvocation.ps1 +++ b/TestFramework/Suites/Suite-ApiInvocation.ps1 @@ -14,6 +14,8 @@ Remove-SgJStaleTestObject -Context $Context -Collection "AssetAccounts" -Name "${prefix}_ApiAccount" Remove-SgJStaleTestObject -Context $Context -Collection "Assets" -Name "${prefix}_ApiAsset" Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiDeleteMe" + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiFullPost" + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiFullDel" Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name $adminUser # Create admin user for privileged operations @@ -69,6 +71,38 @@ $items.Count -ge 1 } + # --- GET with query filter --- + Test-SgJAssert "GET with filter returns matching objects only" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Users?filter=Name eq '$adminUser'" + $items = @($result) + $items.Count -eq 1 -and $items[0].Name -eq $adminUser + } + + # --- GET with contains filter --- + Test-SgJAssert "GET with contains filter finds test objects" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Users?filter=Name contains '${prefix}'" + $items = @($result) + $items.Count -ge 1 + } + + # --- GET with ordering --- + Test-SgJAssert "GET with orderby returns ordered results" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Users?orderby=Name" + $items = @($result) + if ($items.Count -lt 2) { $false; return } + $ordered = $true + for ($i = 1; $i -lt $items.Count; $i++) { + if ($items[$i].Name -lt $items[$i - 1].Name) { + $ordered = $false + break + } + } + $ordered + } + # --- DELETE removes an object --- Test-SgJAssert "DELETE removes an object" { $tempUser = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` @@ -132,6 +166,49 @@ $null -ne $body.Name } + # --- InvokeMethodFull: response contains Headers --- + Test-SgJAssert "Full response contains Headers" { + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Get ` + -RelativeUrl "Me" -Full + $null -ne $result.Headers -and $result.Headers.Count -gt 0 + } + + # --- InvokeMethodFull: POST returns 201 --- + Test-SgJAssert "Full response POST returns StatusCode 201" { + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiFullPost" + $result = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Full -Body @{ + PrimaryAuthenticationProvider = @{ Id = -1 } + Name = "${prefix}_ApiFullPost" + } + try { + $result.StatusCode -eq 201 + } + finally { + $body = $result.Body + if ($body -is [string]) { $body = $body | ConvertFrom-Json } + if ($body.Id) { + try { + Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "Users/$($body.Id)" -ParseJson $false + } catch {} + } + } + } + + # --- InvokeMethodFull: DELETE returns 204 --- + Test-SgJAssert "Full response DELETE returns StatusCode 204" { + Remove-SgJStaleTestObject -Context $Context -Collection "Users" -Name "${prefix}_ApiFullDel" + $tempUser = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Post ` + -RelativeUrl "Users" -Body @{ + PrimaryAuthenticationProvider = @{ Id = -1 } + Name = "${prefix}_ApiFullDel" + } + $deleteResult = Invoke-SgJSafeguardApi -Context $Context -Service Core -Method Delete ` + -RelativeUrl "Users/$($tempUser.Id)" -Full + $deleteResult.StatusCode -eq 204 + } + # --- GET against Notification service (anonymous) --- Test-SgJAssert "GET against Notification service (anonymous) works" { $result = Invoke-SgJSafeguardApi -Context $Context -Service Notification -Method Get ` diff --git a/TestFramework/Suites/Suite-PasswordAuth.ps1 b/TestFramework/Suites/Suite-PasswordAuth.ps1 index a286419..580414b 100644 --- a/TestFramework/Suites/Suite-PasswordAuth.ps1 +++ b/TestFramework/Suites/Suite-PasswordAuth.ps1 @@ -42,7 +42,7 @@ Test-SgJAssert "Bootstrap admin can connect and call Me endpoint" { $result = Invoke-SgJSafeguardApi -Context $Context ` -Service Core -Method Get -RelativeUrl "Me" - $null -ne $result -and $null -ne $result.Id + $null -ne $result -and $result.Name -eq $Context.AdminUserName.ToLower() } Test-SgJAssert "Test user can authenticate with password" { @@ -53,10 +53,32 @@ $result.Name -eq $Context.SuiteData["UserName"] } + Test-SgJAssert "Test user Id matches created user" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -Username $Context.SuiteData["UserName"] ` + -Password $Context.SuiteData["UserPassword"] + $result.Id -eq $Context.SuiteData["UserId"] + } + Test-SgJAssert "Token lifetime is positive after authentication" { $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime $result.TokenLifetimeRemaining -gt 0 } + + Test-SgJAssert "Wrong password is rejected" { + $rejected = $false + try { + Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -Username $Context.SuiteData["UserName"] ` + -Password "CompletelyWrongPassword!99" + } + catch { + $rejected = $true + } + $rejected + } } Cleanup = { diff --git a/TestFramework/Suites/Suite-PkceAuth.ps1 b/TestFramework/Suites/Suite-PkceAuth.ps1 index 972b0f6..57dfa74 100644 --- a/TestFramework/Suites/Suite-PkceAuth.ps1 +++ b/TestFramework/Suites/Suite-PkceAuth.ps1 @@ -17,11 +17,23 @@ $null -ne $result -and $result.Name -eq $Context.AdminUserName.ToLower() } + Test-SgJAssert "PKCE Me response contains valid user Id" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" -Pkce + # Bootstrap admin has Id=-2 (system user), so check non-null and non-zero + $null -ne $result.Id -and $result.Id -ne 0 + } + Test-SgJAssert "PKCE token lifetime is positive" { $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime -Pkce $result.TokenLifetimeRemaining -gt 0 } + Test-SgJAssert "PKCE token lifetime is within expected bounds (1-1440 minutes)" { + $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime -Pkce + $result.TokenLifetimeRemaining -ge 1 -and $result.TokenLifetimeRemaining -le 1440 + } + Test-SgJAssert "PKCE GetToken returns a non-empty access token" { $result = Invoke-SgJTokenCommand -Context $Context -Command GetToken -Pkce $null -ne $result.AccessToken -and $result.AccessToken.Length -gt 0 @@ -32,7 +44,7 @@ $meResult = Invoke-SgJSafeguardApi -Context $Context ` -Service Core -Method Get -RelativeUrl "Me" ` -AccessToken $tokenResult.AccessToken - $null -ne $meResult -and $null -ne $meResult.Id + $null -ne $meResult -and $meResult.Name -eq $Context.AdminUserName.ToLower() } Test-SgJAssert "PKCE can read appliance settings" { @@ -40,6 +52,13 @@ -Service Core -Method Get -RelativeUrl "Settings" -Pkce $result -is [array] -and $result.Count -gt 0 } + + # --- Negative test: wrong password --- + Test-SgJAssertThrows "PKCE with wrong password is rejected" { + Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" -Pkce ` + -Username $Context.AdminUserName -Password "CompletelyWrongPassword!99" + } } Cleanup = { diff --git a/TestFramework/Suites/Suite-SpsIntegration.ps1 b/TestFramework/Suites/Suite-SpsIntegration.ps1 index f15955c..ae1b846 100644 --- a/TestFramework/Suites/Suite-SpsIntegration.ps1 +++ b/TestFramework/Suites/Suite-SpsIntegration.ps1 @@ -14,22 +14,23 @@ param($Context) if ($Context.SuiteData["Skipped"]) { - Test-SgJSkip "SPS authentication" "SPS appliance not configured" - Test-SgJSkip "SPS firmware slots query" "SPS appliance not configured" + Test-SgJSkip "SPS authentication and email config query" "SPS appliance not configured" + Test-SgJSkip "SPS firmware slots query returns body key" "SPS appliance not configured" Test-SgJSkip "SPS full response has status 200" "SPS appliance not configured" + Test-SgJSkip "Invalid SPS endpoint returns error" "SPS appliance not configured" return } - Test-SgJAssert "SPS authentication" { + Test-SgJAssert "SPS authentication and email config query" { $result = Invoke-SgJSafeguardSessions -Context $Context ` -Method Get -RelativeUrl "configuration/management/email" $null -ne $result } - Test-SgJAssert "SPS firmware slots query" { + Test-SgJAssert "SPS firmware slots query returns body key" { $result = Invoke-SgJSafeguardSessions -Context $Context ` -Method Get -RelativeUrl "firmware/slots" - $null -ne $result + $null -ne $result -and $null -ne $result.body } Test-SgJAssert "SPS full response has status 200" { @@ -37,6 +38,18 @@ -Method Get -RelativeUrl "firmware/slots" -Full $result.StatusCode -eq 200 } + + Test-SgJAssert "Invalid SPS endpoint returns error" { + $rejected = $false + try { + Invoke-SgJSafeguardSessions -Context $Context ` + -Method Get -RelativeUrl "nonexistent/endpoint/that/should/fail" + } + catch { + $rejected = $true + } + $rejected + } } Cleanup = { diff --git a/TestFramework/Suites/Suite-Streaming.ps1 b/TestFramework/Suites/Suite-Streaming.ps1 index a2c15d8..6ff4f7c 100644 --- a/TestFramework/Suites/Suite-Streaming.ps1 +++ b/TestFramework/Suites/Suite-Streaming.ps1 @@ -61,8 +61,8 @@ -RelativeUrl "Backups/Upload" ` -File $downloadPath - Test-SgJAssert "Streaming upload: response received" { - $null -ne $uploadResult + Test-SgJAssert "Streaming upload: response has Id and Complete status" { + $null -ne $uploadResult.Id -and $uploadResult.Status -eq "Complete" } } finally { diff --git a/TestFramework/Suites/Suite-TokenManagement.ps1 b/TestFramework/Suites/Suite-TokenManagement.ps1 index 9b5029b..a43f2d4 100644 --- a/TestFramework/Suites/Suite-TokenManagement.ps1 +++ b/TestFramework/Suites/Suite-TokenManagement.ps1 @@ -1,6 +1,6 @@ @{ Name = "Token Management" - Description = "Tests access token retrieval and token lifetime operations" + Description = "Tests access token retrieval, refresh, lifetime bounds, and logout" Tags = @("auth", "token") Setup = { @@ -16,21 +16,56 @@ $null -ne $result.TokenLifetimeRemaining -and $result.TokenLifetimeRemaining -gt 0 } + Test-SgJAssert "Token lifetime is within expected bounds (1-1440 minutes)" { + $result = Invoke-SgJTokenCommand -Context $Context -Command TokenLifetime + $result.TokenLifetimeRemaining -ge 1 -and $result.TokenLifetimeRemaining -le 1440 + } + Test-SgJAssert "GetToken returns a non-empty access token" { $result = Invoke-SgJTokenCommand -Context $Context -Command GetToken $null -ne $result.AccessToken -and $result.AccessToken.Length -gt 0 } Test-SgJAssert "Access token can be used for subsequent API call" { - # Get a token $tokenResult = Invoke-SgJTokenCommand -Context $Context -Command GetToken $token = $tokenResult.AccessToken - # Use it to call Me $meResult = Invoke-SgJSafeguardApi -Context $Context ` -Service Core -Method Get -RelativeUrl "Me" ` -AccessToken $token - $null -ne $meResult -and $null -ne $meResult.Id + $null -ne $meResult -and $meResult.Name -eq $Context.AdminUserName.ToLower() + } + + Test-SgJAssert "RefreshAccessToken succeeds and lifetime is positive" { + $result = Invoke-SgJTokenCommand -Context $Context -Command RefreshToken + $result.TokenLifetimeRemaining -gt 0 + } + + Test-SgJAssert "Refreshed token lifetime is within expected bounds" { + $result = Invoke-SgJTokenCommand -Context $Context -Command RefreshToken + $result.TokenLifetimeRemaining -ge 1 -and $result.TokenLifetimeRemaining -le 1440 + } + + Test-SgJAssert "LogOut returns access token and confirms logged out" { + $result = Invoke-SgJTokenCommand -Context $Context -Command Logout + $result.LoggedOut -eq $true -and $result.AccessToken.Length -gt 0 + } + + Test-SgJAssert "Logged-out token is rejected by the API" { + $logoutResult = Invoke-SgJTokenCommand -Context $Context -Command Logout + $token = $logoutResult.AccessToken + + # Try to use the invalidated token - should be rejected + $rejected = $false + try { + $uri = "https://$($Context.Appliance)/service/core/v4/Me" + $headers = @{ Authorization = "Bearer $token" } + $null = Invoke-RestMethod -Uri $uri -Headers $headers -SkipCertificateCheck -ErrorAction Stop + } + catch { + $rejected = $true + } + $rejected } } diff --git a/TestFramework/TestData/CERTS/IntermediateCA.cer b/TestFramework/TestData/CERTS/IntermediateCA.cer index 10e20ac2cf962cd04b1739625b60b0c6a0703ed1..ee30f6b7fd8af8a17a410ba40f9a8db85f547334 100644 GIT binary patch literal 1460 zcmb_cOHWf#5bo`pmeNYCH=C*lSMYCvZ^fY>#TSwit zrv1=6DkA?_$Squ4D6fAmBd2HO#MO94MnPOdsyuip$)KU;NOn#33k!8fk~F|D^5XEJYSk!M)QwCK*lyi_g?b>_*sqf;ODGv{zYd&=`FLrT={Cq=0rZl~tJbkmUSJH;4jFjpGQiBqX`dkX)9$N(V|F@@s%^dlM zfqv5*5Ef#hfmx&j9-Lx4&FmCs2X||Yzh7Ze(bNr@S0_7Z#s|Edzmu3`(E^Gn&mVH* z?#}C)4u~aqL!uZsF)|V{pKOKlz=PG1T##x4F(A+hDe6@ zkw(FPf&n5h4F(A+hDe6@4FLfG1potr0S^E$f&mHwf&l>l&Q3?iTo9S4NSS-zp4~9( zzSBN(#$OlH5OSG>AuHBYFb@5!;R8(N!yf#vw+P&?s`A8LMVvugzWlBR(~@(rg`gr* z!HYc?Xd>0{0dft$^8}Y)gXDrZEvBxtMMUd=9kHgnpBo-eS5rg>+5M0O{Ir{`AI$h_ zHx<){paeh6;C5&40kCsYw^v!%<+g?3%@x|AK0u43^Fj{(@1pv>Mj4zOs3O=i)Ks$W zwpc>VU0Q=zfKWj`*bDOYu&Ns&xRe}WJa6rDr| zZjg#giS$Lqbpg>Z4F(A+hDe6@4FLfQ1potr0RaG;){>#qdy|~@md^)4;0`Ng69T_Y zIJB&OWMJvG={A~ANrn5?$%EUgCq0IE<-RUt8;)ODSx*QoIr}lmNF+}TsG;!lCBDM+ z1m*9r+TH^TdFM;mN)w(Wlm%ng=kS guQ~?So%a6eun)iYXT2KH%1zC$mM;4tw(H!aBk{*NNB{r; diff --git a/TestFramework/TestData/CERTS/IntermediateCA.pvk b/TestFramework/TestData/CERTS/IntermediateCA.pvk index 83311fb7b737015ec92fb9c5c63d4a8942f005c5..b76d5d0276cb9fa06e0af0096b29bfe911826ddc 100644 GIT binary patch literal 2156 zcmZ8iZBJWe7;Y(}bekJcEQL{~Z7DCUybHp*jIs?@U_d8O+K#$0V1pGLg+XDob@78{ zZc#t@5~F6s#AGgu@ry>IF)?mTjmCj*=)|waFa8Cui~GqLlU(<8U(bC`&vTypU7TT@hm6}XQ7H|LT^X^T-4Fj5>-$rMe*7($;tj-ys$^0oAbj9+ncAau(=LHH$75 z=eJpcP$}49iA)|I5iu)nM;83?r$F2}pE)UknZg@a`Np#uAiyS0m=n-kr!vPEW5=~E z)$CQNJ0`cC;cc+M0?#3s<4(prP(ELbKM1&gy#{CDw%bwr_>oTggPti)O*3+dF9eMZp^_FPtBG#u=S-REQToHmtperbS7aqBnXd4OVmk8&l*y#O9|<3pu6q?JDR zkhM?(;HpPJCT+>DR&tSPG;1*kmS6DC%b$zjn6g0hU5&tb2@W>F9}*3Y z!8+oc)Mi$pEWu)N#st&fT^_9HWaWT}eyGL4+3WGAIF1@v>WvpHOer72om5vt0UUfb zNFxiPiqgUES^KC+?NvX^U+sbket8RC4cIW`fn`uD$6@}_17P6m_l9$(zP~jFnJ1B9 zKKn`7{m(%5Y*0RH%|~b4d1=g9$H@TL9}NXO1Dgk!3{^+MBEg>38!QTIThtQ_rTFhf zHTb>Pp!rNd^$t{vy1Mp>Tq)^-PY-}N&g#V0erQ!4JtSaf%78v`hJ>YQz5trvs;c&G z@Ja3sa83oz22DnFgSS6;P>Bk=*PoJ$I=Ec^%h(!YKFB5)n1C8~yIy^zAP)u(Ee6c$ zv19k1o3kVM`^O+4zRF7Yo&_!N3&O=na_gJROfPy|wjv3yUJpAi7?b_{aCdQDjy6v0 L<4-zadv^W@Q~_a8 literal 1212 zcmV;t1Vj5C@wKo300002000010000G0001#1ONaeOlAi9k-n8@g&LSLg;LcA0ssI2 zBme-(?tEH8mT^$tn4gH=>MFV!m1$(h&q*EUA6r(eF!p~7j}Y(bU|tu6Kf&Y@N#2zzu*is|v>6W}@{D=e_Uo#7^(h-qy= z?71EFGtz^!+!j$;JYdWGy$D7f;C1?gy}a;YGnafDmfGdXX8hLSG%1|MCcVXdGZc#| zTOwy4-5|wd8sXT1?1$$Mjo%SZBiSY2k&%MY?Ejo%JZYy+CxW_tUjP4kZombTBJ19* z5(Eb*QEbNS{?e<44DiZXim-}BEUSyG#(#Ou?zPq9 zH=?FQ(E5sGFw~ZteZrx7{KOJ}^i`rUUgdi9Bwn;%jO}2p839w@$cio7Xh>M;%}bd=e;9H)Y0_+219(TXr3<)bb5(yTOEO7#adtY2y9Bb{rZVX; z6D#PPkP$|t8;F!9{x;&+FRU|8Ba@qLBo_c+Ik@K>tFTEO7FU*jheP7=YC~eS?sN3p z1qR1WIP*HB6e)rp#|c{5j7zw40=k(U$m@pd{6U~OGHAQaMvWBNLV7R{aKGM2^!;O= zIowfqGN6UvbU5D!5~CDXi*UG6E7y&y79NPbse6?2VSn-xg;iKQFo*VbpTS#E8|`X)tAVOvau{DM3ghWv|cg_luTq<(oA)bsLJhO z0*0$7cBj(Qo_Hou2ad%ffAfH&1{q#jS59o)jO`L69XaI0R+x!upv}{(xq0dwWY7`8 zgIV{>9{Sm@oAVwZ+bTgTSQr_m_x&yn&k2*iqbjX^c{0co_rv+QFOtXP>W{pe(Qb<- zo_H90cyRg9YdrEU4RA1%8g1_pI6h0z^Q`m)WUM*N_EgLOXT3&pgcXN191JQ zx^lsBTl`Vbh$W}{sTmAFT1WPXlxw|fBkuVBJyAb-Q4=(<)Me3tbRvh^wxp7B$C|c5Z`yZbpp8LdeHKrZHL`guCLJ2$ a>&Kv4BX9?ORA04A4ZcM-X&YmMO6v}Lxk`Be diff --git a/TestFramework/TestData/CERTS/RootCA.cer b/TestFramework/TestData/CERTS/RootCA.cer index b84789f7f93644e3199eb4d80ba6f0d8a065f4b9..4ce117632a3aadb8907a422a280fa832eebc173f 100644 GIT binary patch literal 1437 zcmb_bO-~b16rI;;3v{eg80Y{}Bfzk7~u9ea| znOLECBU?0_T%jk5W=&>yi-wBnFqAd>T%k~mMtH04G%oTW_xlZhI2c&gor8adgUhG4 z5Vp@c{yhw{NmLZ3N>mr-pvo{NK>?+uOPH1>5i7bGsMuw=BQeTn5gUcqheN^XXt+D$ z^&wFJuXJ{bN6_=2N)^`~kfGV^$2W+c2JPMyqCnWcDa_@ZyQ^aWp74#F|MNfJQ|}1RvVYghVN67 zS+_ael56g6q^8{{3o^hc;9k4AsR8dVDrgFfd4qc=sS5j-Nm!y3J3|7oa-01`him z0t5TOG#o7g&{d|cfoBCXro=44ZY`@6v>&o4VWd+Y`EGu zeIh{R0O)U|Erw|)V?jNLATZJP=*w%D`Qu38odj+8Fn8V%e x=na~NrUmLn5SlXgi_t7t1>OQzPhLSYTW6pZxM^?k$}m;xH}@=3Q}(kkj$i3O`L6%~ delta 698 zcmbQsy_#Lapow|8K@(H|0%j&gCME$Pk-0AHc@w(4JHNOdEoHoDz{|#|)#lOmeWKgW z`X)vtIG>l1k&%^wxrvdV!Jvtei>Zl`kzv<*ZW)zNe`O?BukM?BILG8!+s|cD2kNiA z%uW3#vGTaMjhU&pvxQoUj{e{Li+0M&M{gaKf5V@*Rh(;$d0^_X9SyRg!pUB{&RnoC z%IW-TohGKDr1wX!ZBz4#V@JfaHr3z%ID5xuDes26{y+6OCwDt!Yz%R-{Vp5LIaRGE zt*NleJZ9z>slzIbYwijA1enbX@D5>C40);6d@052-%*3*AD2jQbGJ6jW+wg*{}gGP zBl%%!f%@GpRc6OUu3<9j3#)tbnq3?X_ugCm`NJ-QsZ-BfYH@7ZD_I_x`r}zx$ab+< z1;-VxtK7cMw31tBaM6pyw)IazC)47Dl+)(#oZ|y{>*LlkF*7nSE^h2IXzVuNXJZbP z6=r1o&%$cJ45SPyKmv>`g$DTz0yeky-7yJXxbaY`z20)o@Y02z6Ma1En{bCeFub|J z;mydW5v&^eRjfKh^vHx-5R@ zMt_K6?X4`sIUg2C?9!6h=yj=TZt>6NBO%RbFKCi39H=4WT)Gz$B!{0N-Oz9!;J{Ey1Nj7$SlOqHykR=%22liPUaPKAa8kPZ0O~D(1|R#3%LS% z-?XgsPQo;SK#v4XOUJh0FSbJ#W$@tV4VbZ^Y$)o~%TPvLEEka3mN95C$d$YUc4wBM z5AH%V^qg<^Xh~&7bWM_#bgExm^eEqW3N*`r=)@}GbYK{IcmdMx=#vYgR}p5k0hi`p zHd9f)8Q3iHY`%tQetH9%(Dll29cz|O1fgfa2~d9=B4}_HXkK+(tA-1l0w-Ats*mx7 zg8GWr-QtW0555=QDnazOpK@9{as*bbDBG*_(XBLsdz76AuyfRggv+p3fW`ZFpnG5U zx0%b&{CnrS1>T+CZ6sWET_I$P8$1pg8Y-Xy!;?>7s=6a~Ysegiz4r7*>@*w6QzCbq z8|O2dpnen*1slODL6#fHsV%>nX3uW)bk51RX+YvOd#dTOB%)(%!i{x^uKxEg=d&@m z7)u#eyAzhB=0leUlFwm0{*1FxmZFfyU+@mKxnTQWT!-?w8UheY^@z=aD41Q} z@mU<4qsvv+$Kz<13ws|Ua?*_+{duYhV!@UM&&NZK4IDf&a^A*m6stn=fkQd7J=S!5Q`(@J4RP=I4OKxNsOc3kp)2gBfAzG;AH*g}486^8?QODqr5&qISI8w47fs&zbtw zfeWzv8o<73AN21JM>!dQ9sTF+-k=q6FwRN?x`6#{FCTt*%N$4GVQ%I|1N^>tTbZ;4 zAU1W|VYy(I{OnS0m!}}ix@&O387Eo&YOqSidCVd;kat0|j6r<~4|MMv$dS6({d*6; mg)0s|`^v_iRku&cmk5Kg5Kp6qt{qrTT~M~OoV#d&FaIA%x}3cL literal 1212 zcmV;t1Vj5C@wKo300002000010000G0001#1ONbW8g@fi;hSrY(-$bJF5d+Q0ssI2 zBme+=X@t(btN<&!oyGRFNk7!859w zoR886PI>)hEXiWe%+9%Wl1@f;P+d0+myvTTjK?4Ib9&s$`UKKos8zHwb7q3ZwW9mu z)-zTtLhyy-4hvmyq1tD0Z$b5gT^S$8Bjmpv^y&W6Qf}*5N=c9nVahBvK*6={#CY&o zumAHs`mvsc-tNy=T_2|TcLPFq57`{nio9zEIq=FA6QOZyPm(;DQt1pHxvQ8VHvzVW z!U4ax1kcFk&|Y}dZiENQ9*E`>;nm3?3l=`U%oohYuBq@Ujr#Sh#JGFm>&;9U(kZB# zY5oVxmvNImO)i=JG|p>{$J%pRzyD&xsgTE_K`R^zI^(IUVJ?_LrT2lD55E-x?Rg`h zQP&e83!H+r6VQm15HegfBv2evxqjA)WGF`VLIvT?OHonHceD&mpIxja*lbT#u=0Bl zlZDqhLkIzqWn+AV_ntHp07|J*!0ms-hiz#t`kbn9T7}4 z9qRDgT6BCz9X4XGJv|6OZ09wVu*Zk^MzywfC#v8Y>*W_%rRsqH?^F~ZP_D~>?aFul zuOi8rZ3^4t-9+u+Nj9GE15EJbb)JRdq!jXbn9h^H$!SP8zP)KY^#YHfhCV1`Ba3e zoXNaN6Hg4xjnX1qks_AM?P~i+GP66(P|qJTr7}aUQw)lQMJ}HRt@H12qXQ$1AF_r7 z-fJn--p%7wy)Nx0lVgEC4Dls_{#LR>vQ+eMTP#d|9Vig9mB zXk7s5`SNkugxj{<09z#mcK7_;Rl1t}CiA|fGTdScjfxSC^Si6^+W_$1{*1;iZ(y?H z)g(lA>!vM!bP95uM?NRv8^5Z$XYR=B81kl%HgE=vCR5bFXf{qMNHtfT^;reai|H-JUfN*ys;Gq*VGYMx)cE{wvXHRgOkDr7(fxVOwd^(G zi-C6TcyYrnDMsh-?(Aj&l$?nxzXiXZ$VvzCA%H+wN5b8LO)adNb*|KzIZq^{MQCuN zl?F;RKSTwy$d?>w^bg94O6N@Yow zU0F_wVdEPwkHb2-0)6{XIIOlNL_J({NRr)R5`&su*sSK5Vs*>0sOAx6l&EfJG?CKJ zEhe(b&a^S08=6DdX~phVM)UWJDTQ zxoX2uZ!3N0@7ph+bT9353XF*E9geXR^P2nJSclVTBOJoRTp(Zzv<-384ck1!bgzB; zfa7Z$+uMD&haOuab z1&(x6GaaaB2138hJzSU#6q6XIlz4Ry4-VhI|e*r@35X=Ao delta 725 zcmV;`0xJF13*84WFoFZwFoFWXpaTK{0s;_nm!cAvThY5fs6@c6jKrNV4F(A+hDe6@ z4FLfQ1pqKlk-s;Qg@k{C0U|IB1_>&LNQUBFc@ zi^XwB08nkZ_OMPw1(bGu;B5YTEJC0A{^Y&MCWP1{3AKhXLDJ~+Mp;3aaRLJY00E&LNQU0t)4OBm=vLZfx$M*o>QG( zou{2m9VnBbX0HA(_~xhzbk;j{n7uE`3TymeZqb*s?VdzEtB}KSvOdpSlm91_eYa&S z1Gv0Yh_*xHEPFviHhg?JlD%->iF2u@WdJJy!x(6RM)Vo%rt~L_knh0p7-R*uCJ+?I zD0!(I3D+W}k+n&OS;a*t9+MhFrPtCXV+($Sd%Bwmp7uz%{`vAq2-@j7frOLZ}JE7{Xd?2Pdj3MXe%5 zd8pJyRa>R2)I6t4)R%^bwxVg1Hj0`y4|(ZJ)xXelxT7ypA8MU@?zuDbec#NTJ7e2F z-`>&Tr&pE(kER3r9NmHJ963@CR|Km&JlU9@A|v((pluAsejqWP2p5X1D4m8l0?UEz zelD$fxsPr`bxEXgCX~mpm1;_ZFqeP=uyA_}4CGIV2mkRwb*cn28Sophd5MMlJUt?e z1Muarpi>&Wt;=&zwXhV}60F&30|Oiz@QCK(fgdj~c5}RiOUPXEFXW$+j)_wEGq8lC zZS*Dh->!h33xWhi-VPCKT2sx&i0FIoijTnYeMcKWVP+OEa9)OR!exiIz|=#B+Mxlf zdg1fU5`)!OLu2=^^&uRDxwX!VP-(w1-_<`|pN{nz!~Uw55iu)3f9r!Aj$YB$f_nV~ zWZ>M4G-!PH4!@esU45YmWYv1kdeb${UKqEWi3Jw27wxaWuSf{?!jgZz_8#1RhrSr9 z0G17lHnM~cz#d*@QU&HzY9Dg}96b!GQ~SeR$dz_*)-PY~@x>>WN_wMeedndu(8C=R zP&E?j-bZ_%oRrQ=IRE2M`KZIs*AJz2c%)W*h<|^rpN<+dItz;VY5~`{}u;UmTlD3s++m_e2$>ehd zKpE`rTcRT~-0VCj=je8i7BAHA*rf*?DL2{6svu?uJstW9wK0cS#vQQ7AF~o`V2jKF z5a{1-Nx=8VLB*;X&sUv$`pwn)p*k4@o^@#wLT%%tSDl4C3<93CP&J4j0xBC;);>XJg7Tn8mudwSpi zzE=a+$I5Z;Q@nzgKk)Y(x1xKW4O9%fCFWwsV#D0WAj}+R9;=KXPp7J}C~KEr0$AY5 z@j@_%?7qYDsrvrww7oCvEZXjzrekpTZw{IHzrjP;T?+ynM!s3cgT<){j=8sZ84dS? zI!)YAz#?v$DF&Jc=D_W+IBzw9L+1;0tsA;n|JC7bX$(Dh8)9;Jo^yD5`E`EFWs2G& zQ|dt`m$T4d=XwNu1~8WRzy%TrC)-9Lcqs!qt{XTZ?vlQ#GdZLW=pu+G)iucJ{LFJ! zrX~iN0o#E+d6Sdiby7JGI^$x8Emz-N&VO7$CAtEVPcuC#FHnNb7S3-vKe{=cYmKwQ HGP3>!vH&O1 literal 1212 zcmV;t1Vj5C@wKo300001000010000G0001#1ONcnZho{lOA{*R8R?@b5>_k+0ssI2 zqyPZFo>8GH*2sKPd}bKA{30J*rxW@RjK-?^*cBAnxVbu=^11$=nhBCa3wHw!2Eq{> z|5d1FHhTN$7FfIZ;@zPp1O3oo2+4KkXP?2F`a(lIz0M3p;~}zFIg_VOG;d_~D4+uH zZMQKU9`&z9#Z^O~*Z}dYh3w3LYCB}e8WX_@ZBuC$S%iU9AHiGVf_ySHOs@NCLiok1&+W*PgW3j*8=W_&#_?HYkg^sWsyi?1DX z-v2H!>*X|(<{`N8L^RZrGC;id z9TzGARDe}uV64D|zW2qwbK4IXiZCsf9g?xzVNM5(@zd)tq+IE3(Z^{IYA<-IaMjr4 zPNRS5g`TW<({KSBs28mdrEV-Lq6GQZh+c@7z!3vp=)6!Gl+i&Fjwt|^q#ueD_?Vjt z(z(u_^-OIXKyD$h+GvW~3p^9}f|?eYqRe^)VS8pDxodKk=+47*Y|+DN?x%O(_qB(j zIIY7u_Z+-z;M@GrrOu~Mtmz|%Q{1R`i!xIm=to1cfU~w>x7pbSA*SYEq;Id5iz#-i5AD}=L|o}Kx& zg^tof8bBdw9m2yASp>+|#Nc2S#eYt2?X!GPZX*-C0ff~+m8GbDGEqnTw{Tv`=dSh@ zLUw4t6`aE$g~gft@@!xa=M_0Ds6RhV8)+(jFNn68Y#|OWASA(JMng9NRC3Vl4RPo< z`rM{-lP8#JMdjdCM0qoOtVDaV8wZK(Xe49fyof4CDea;AVm6tuF+II}j~MlhdR}5mnkFojWLll`;%^!~iO@KB|`Ku}D96PLxSR(Krq| z@yHsv>Th;{i7`cFi8hLRT=bgZm@)uRRXTJFIzYuHW_hQ|U(}k4R!L__$K!yCDw919 zZXRq{T#wT&{(7Z^?&>cI;oIkmNV0+q98i`Qgqq_2e>F|XRF;_l49V3@hWC8|EjWKi z0aXBO;8qldv3c^*nav=1qlViE_wb~6{=To!J2TofI9AJPcYv^hNh diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index 0c56b4b..348df39 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -135,7 +135,16 @@ private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ign private URI getBaseURI(String segments) { try { - return new URI(serverUrl+"/"+segments); + String fullUrl = serverUrl + "/" + segments; + int queryIdx = fullUrl.indexOf('?'); + if (queryIdx >= 0) { + URI base = new URI(fullUrl.substring(0, queryIdx)); + String rawQuery = fullUrl.substring(queryIdx + 1); + return new URI(base.getScheme(), base.getUserInfo(), + base.getHost(), base.getPort(), base.getPath(), + rawQuery, null); + } + return new URI(fullUrl); } catch (URISyntaxException ex) { logger.error("Invalid URI", ex); } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index 55fa820..0481514 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -91,6 +91,27 @@ public static void main(String[] args) { return; } + if (opts.refreshToken) { + connection.refreshAccessToken(); + int remaining = connection.getAccessTokenLifetimeRemaining(); + ObjectNode json = mapper.createObjectNode(); + json.put("TokenLifetimeRemaining", remaining); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + if (opts.logout) { + char[] token = connection.getAccessToken(); + ObjectNode json = mapper.createObjectNode(); + json.put("AccessToken", new String(token)); + connection.logOut(); + json.put("LoggedOut", true); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + Service service = parseService(opts.service); Method method = parseMethod(opts.method); Map headers = parseKeyValuePairs(opts.headers); diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java index fb6dd08..9049d3d 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -79,6 +79,14 @@ public class ToolOptions { description = "Output the current access token as JSON and exit") boolean getToken; + @Option(names = {"-L", "--logout"}, defaultValue = "false", + description = "Log out the connection (invalidate token) and output result as JSON") + boolean logout; + + @Option(names = {"--refresh-token"}, defaultValue = "false", + description = "Refresh the access token and output new token lifetime as JSON") + boolean refreshToken; + @Option(names = {"--pkce"}, defaultValue = "false", description = "Use PKCE (Proof Key for Code Exchange) authentication instead of password grant") boolean pkce; From 40954423cb8ce7c3533b8c1a382005a7d4feb5e7 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sat, 28 Mar 2026 22:17:55 -0600 Subject: [PATCH 19/22] Added an AGENTS.md for more effective use of AI in this repo --- AGENTS.md | 579 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 579 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c0d070d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,579 @@ +# AGENTS.md -- SafeguardJava + +Java SDK for the One Identity Safeguard for Privileged Passwords REST API. Published on +[Maven Central](https://central.sonatype.com/artifact/com.oneidentity.safeguard/safeguardjava) +as `com.oneidentity.safeguard:safeguardjava`. + +Targets Java 8 source/target compatibility. Root package: +`com.oneidentity.safeguard.safeguardjava`. Key dependencies: Apache HttpClient 5, +Jackson Databind, Microsoft SignalR Java client, SLF4J logging, Gson (via SignalR). + +## Project structure + +``` +SafeguardJava/ +|-- src/main/java/com/oneidentity/safeguard/safeguardjava/ +| |-- Safeguard.java # Entry point: connect(), A2A, Events, Persist, SPS +| |-- ISafeguardConnection.java # Primary connection interface +| |-- SafeguardConnection.java # Base connection implementation +| |-- PersistentSafeguardConnection.java # Auto-refreshing token decorator +| |-- ISafeguardA2AContext.java # A2A context interface +| |-- SafeguardA2AContext.java # A2A context implementation +| |-- SafeguardForPrivilegedSessions.java # SPS entry point +| |-- ISafeguardSessionsConnection.java # SPS connection interface +| |-- SafeguardSessionsConnection.java # SPS connection implementation +| |-- authentication/ # IAuthenticationMechanism strategy pattern +| | |-- IAuthenticationMechanism.java # Auth interface contract +| | |-- AuthenticatorBase.java # Shared auth logic (rSTS token exchange) +| | |-- PasswordAuthenticator.java # Username/password via ROG or PKCE +| | |-- CertificateAuthenticator.java # Client certificate (keystore/file/thumbprint) +| | |-- AccessTokenAuthenticator.java # Pre-existing access token +| | |-- AnonymousAuthenticator.java # Unauthenticated connection +| | `-- ManagementServiceAuthenticator.java # Management service auth +| |-- event/ # SignalR-based event system +| | |-- ISafeguardEventListener.java # Event listener interface +| | |-- SafeguardEventListener.java # Standard listener +| | |-- PersistentSafeguardEventListener.java # Auto-reconnecting listener +| | |-- PersistentSafeguardA2AEventListener.java # A2A persistent listener +| | `-- EventHandlerRegistry.java # Thread-safe handler dispatch +| |-- restclient/ # RestClient wraps Apache HttpClient 5 +| |-- data/ # DTOs and enums (Service, Method, KeyFormat, etc.) +| `-- exceptions/ # SafeguardForJavaException, ArgumentException, etc. +| +|-- tests/safeguardjavaclient/ # CLI test tool (interactive, not automated) +| `-- src/main/java/.../ +| |-- SafeguardJavaClient.java # Main entry point for test tool +| `-- ToolOptions.java # CLI argument definitions +| +|-- TestFramework/ # PowerShell integration test framework +| |-- Invoke-SafeguardTests.ps1 # Test runner entry point +| |-- SafeguardTestFramework.psm1 # Framework module (assertions, helpers, API wrappers) +| |-- Suites/Suite-*.ps1 # Test suite files (auto-discovered) +| `-- TestData/CERTS/ # Test certificate data (PEM, PFX, CER files) +| +|-- Samples/ # Example projects (each with own pom.xml) +| |-- PasswordConnect/ # Password-based connection example +| |-- CertificateConnect/ # Certificate-based connection example +| |-- A2ARetrievalExample/ # A2A credential retrieval example +| `-- EventListenerExample/ # Event listener example +| +|-- pom.xml # Maven build descriptor +|-- azure-pipelines.yml # CI/CD pipeline definition +|-- .editorconfig # Code style enforcement (LF line endings) +|-- spotbugs-exclude.xml # SpotBugs exclusions +|-- settings/settings.xml # Maven settings for release publishing +`-- .github/copilot-instructions.md # Copilot custom instructions +``` + +## Setup and build commands + +**Prerequisites:** JDK 8+ and Maven 3.0.5+. Maven does not need to be on PATH. + +```bash +# Standard build (compile + editorconfig check + spotbugs + package) +mvn package + +# Quick build (skip static analysis for faster iteration) +mvn package -Dspotbugs.skip=true + +# Build with a specific version +mvn package -Drevision=8.2.0 + +# Clean build +mvn clean package + +# Editorconfig check only +mvn editorconfig:check + +# Build for release (includes source jars, javadoc, GPG signing) +mvn deploy -P release --settings settings/settings.xml +``` + +**PowerShell note:** When running Maven from PowerShell, `-D` flags get parsed by +PowerShell's parameter parser. You must quote them: + +```powershell +# WRONG — PowerShell interprets -D as a parameter +mvn package -Dspotbugs.skip=true + +# CORRECT — quoted to prevent PowerShell parsing +mvn package "-Dspotbugs.skip=true" +& "C:\path\to\mvn.cmd" clean package "-Dspotbugs.skip=true" +``` + +The build must complete with **0 errors**. The project enforces: +- **EditorConfig** — LF line endings, UTF-8, 4-space indentation (via `editorconfig-maven-plugin`) +- **SpotBugs** — static analysis for bug patterns (via `spotbugs-maven-plugin`) + +If you introduce a warning or violation, fix it before considering the change complete. + +## Line endings (CRITICAL on Windows) + +The `.editorconfig` enforces `end_of_line = lf` for all text files, and the +`editorconfig-maven-plugin` checks this during every build. On Windows, many tools +(including editors and file creation APIs) default to CRLF line endings. + +**Every file you create or modify must have LF line endings.** If you are an AI agent +creating files on Windows, post-process every file after creation or modification: + +```powershell +$content = [System.IO.File]::ReadAllText($path) +$fixed = $content.Replace("`r`n", "`n") +[System.IO.File]::WriteAllText($path, $fixed, [System.Text.UTF8Encoding]::new($false)) +``` + +The `.editorconfig` excludes `*.{pfx,cer,pvk}` from line ending checks because these +are binary/DER-encoded certificate files. + +The `.gitattributes` file controls Git line ending behavior. The global `core.autocrlf` +setting may conflict — the `.gitattributes` overrides take precedence for tracked files. + +## Linting and static analysis + +Two checks are integrated into the Maven build: + +| Tool | Plugin | Purpose | +|---|---|---| +| EditorConfig | `editorconfig-maven-plugin` | Line endings, encoding, indentation | +| SpotBugs | `spotbugs-maven-plugin` | Static analysis for common bug patterns | + +Both run during `mvn package`. To skip SpotBugs for faster iteration: + +```bash +mvn package -Dspotbugs.skip=true +``` + +SpotBugs exclusions are defined in `spotbugs-exclude.xml`. When adding new exclusions, +prefer fixing the code over suppressing the warning. + +## Testing against a live appliance + +This SDK interacts with a live Safeguard appliance API. **There are no mock/unit tests.** +The `tests/` directory contains a CLI test tool and the `TestFramework/` directory contains +a PowerShell integration test framework. Running tests against a live appliance is the only +way to validate changes. + +### Asking the user for appliance access + +**If you are making non-trivial code changes, ask the user whether they have access to a +live Safeguard appliance for testing.** If they do, ask for: + +1. **Appliance address** (IP or hostname of a Safeguard for Privileged Passwords appliance) +2. **Admin password** (for the built-in `admin` account — the bootstrap administrator) +3. *(Optional)* **SPS appliance address** (for Safeguard for Privileged Sessions tests) +4. *(Optional)* **SPS credentials** (username and password for the SPS appliance) + +This is not required for documentation or minor fixes, but it is **strongly encouraged** +for any change that touches authentication, API calls, connection logic, event handling, +or streaming. + +### Connecting to the appliance (PKCE vs Resource Owner Grant) + +**Resource Owner Grant (ROG) is disabled by default** on Safeguard appliances. The SDK's +`PasswordAuthenticator` uses ROG under the hood, which will fail with a 400 error when ROG +is disabled. + +The test framework handles this automatically — it checks whether ROG is enabled via a +preflight PKCE connection and enables it if needed. It also restores the original setting +when tests complete. You should not need to manually enable ROG. + +If you are testing manually and receive a 400 error like +`"OAuth2 resource owner password credentials grant type is not allowed"`, you can either: +- Use PKCE authentication (`--pkce` flag in the test tool) +- Enable ROG via the appliance Settings API + +### Running the PowerShell test suite + +```powershell +# Build first (always build before testing) +mvn clean package "-Dspotbugs.skip=true" + +# Run all suites +cd TestFramework +pwsh -File Invoke-SafeguardTests.ps1 ` + -Appliance

-AdminPassword + +# Run with SPS tests +pwsh -File Invoke-SafeguardTests.ps1 ` + -Appliance
-AdminPassword ` + -SpsAppliance -SpsPassword + +# Run a specific suite by name +pwsh -File Invoke-SafeguardTests.ps1 ` + -Appliance
-AdminPassword ` + -Suite PasswordAuth + +# List available suites +pwsh -File Invoke-SafeguardTests.ps1 -ListSuites +``` + +**Important:** The test runner requires **PowerShell 7** (`pwsh`). It: +- Builds the SDK and test tool automatically before running tests +- Validates the appliance is reachable (preflight HTTPS check) +- Checks and enables Resource Owner Grant if needed (via PKCE bootstrap) +- Discovers and runs suite files from `TestFramework/Suites/` +- Cleans up test objects and restores appliance settings when done +- Reports pass/fail/skip with structured output + +### Current test suites + +| Suite | Tests | What it covers | +|---|---|---| +| AccessTokenAuth | 5 | Pre-obtained access token authentication | +| AnonymousAccess | 3 | Unauthenticated Notification service access | +| ApiInvocation | 12 | GET/POST/PUT/DELETE, filters, ordering, full responses | +| CertificateAuth | 4 | Certificate-based authentication via PFX file | +| PasswordAuth | 5 | Password authentication, negative tests | +| PkceAuth | 8 | PKCE authentication, token operations, negative tests | +| SpsIntegration | 4 | SPS connectivity (requires SPS appliance) | +| Streaming | 5 | Streaming upload/download via backup endpoints | +| TokenManagement | 8 | Token lifecycle: get, refresh, bounds, logout | + +### Running the CLI test tool directly + +The test tool is a standalone Java CLI in `tests/safeguardjavaclient/`: + +```powershell +# Build the test tool +cd tests/safeguardjavaclient +mvn package "-Dspotbugs.skip=true" -q + +# Find the built jar +$jar = Get-ChildItem target/*.jar | Select-Object -First 1 + +# Password auth — GET Me endpoint +echo '' | java -jar $jar -a -u admin -p -x -s Core -m Get -U Me + +# PKCE auth — GET Me endpoint +echo '' | java -jar $jar -a -u admin -p -x -s Core -m Get -U Me --pkce + +# Anonymous — GET Notification Status +java -jar $jar -a -x -s Notification -m Get -U Status -A + +# Access token auth (provide token directly) +java -jar $jar -a -x -s Core -m Get -U Me -T + +# Full response (includes StatusCode, Headers, Body) +echo '' | java -jar $jar -a -u admin -p -x -s Core -m Get -U Me -F + +# POST with body +echo '' | java -jar $jar -a -u admin -p -x -s Core -m Post -U Users -B '{"Name":"test"}' + +# Token operations +echo '' | java -jar $jar -a -u admin -p -x --get-token +echo '' | java -jar $jar -a -u admin -p -x --token-lifetime +echo '' | java -jar $jar -a -u admin -p -x --refresh-token +echo '' | java -jar $jar -a -u admin -p -x --logout + +# SPS connection +echo '' | java -jar $jar --sps -u admin -p -x -s Core -m Get -U "/api/configuration/management/email" + +# Streaming download +echo '' | java -jar $jar -a -u admin -p -x --download-stream Appliance/Backups/ + +# Streaming upload +echo '' | java -jar $jar -a -u admin -p -x --upload-stream Appliance/Backups/Upload --upload-file backup.sgb +``` + +### Module-to-suite mapping + +When you change a specific SDK module, run the relevant suite(s) rather than the full set: + +| SDK module | Relevant test suite(s) | +|---|---| +| `Safeguard.java` | PasswordAuth, CertificateAuth, AccessTokenAuth, AnonymousAccess | +| `SafeguardConnection.java` | ApiInvocation, TokenManagement | +| `PersistentSafeguardConnection.java` | TokenManagement | +| `authentication/PasswordAuthenticator.java` | PasswordAuth, PkceAuth | +| `authentication/CertificateAuthenticator.java` | CertificateAuth | +| `authentication/AccessTokenAuthenticator.java` | AccessTokenAuth | +| `authentication/AnonymousAuthenticator.java` | AnonymousAccess | +| `restclient/RestClient.java` | ApiInvocation, Streaming | +| `SafeguardForPrivilegedSessions.java` | SpsIntegration (requires SPS appliance) | +| `*Streaming*` classes | Streaming | +| `event/` classes | (no automated suite yet — test manually) | +| `SafeguardA2AContext.java` | (no automated suite yet — requires A2A app setup) | + +### Fixing test failures + +When a test fails, **investigate and fix the source code first** — do not change the test +to make it pass without asking the user. The test suite exists to catch regressions. + +Only modify a test if: +- The test itself has a genuine bug (wrong assertion logic, stale assumptions) +- The user explicitly approves changing the test +- A new feature intentionally changes behavior and the test needs updating + +Always ask the user before weakening or removing an assertion. + +## Exploring the Safeguard API + +The appliance exposes Swagger UI for each service at: +- `https:///service/core/swagger` — Core service (assets, users, policies, requests) +- `https:///service/appliance/swagger` — Appliance service (networking, diagnostics, backups) +- `https:///service/notification/swagger` — Notification service (status, events) +- `https:///service/event/swagger` — Event service (SignalR streaming) + +Use Swagger to discover endpoints, required fields, query parameters, and response schemas. +The default API version is **v4** (since SDK 7.0). Pass `apiVersion` parameter to use v3. + +## Architecture + +### Entry point (`Safeguard.java`) + +The static `Safeguard` class is the SDK's public entry point. All SDK usage starts through +static factory methods: + +- **`Safeguard.connect(...)`** — Creates `ISafeguardConnection` instances. Multiple + overloads support password, certificate (keystore/file/thumbprint/byte array), and + access token authentication. +- **`Safeguard.A2A.getContext(...)`** — Creates `ISafeguardA2AContext` for + application-to-application credential retrieval. Only supports certificate authentication. +- **`Safeguard.A2A.Events.getPersistentA2AEventListener(...)`** — Creates auto-reconnecting + A2A event listeners. +- **`Safeguard.Persist(connection)`** — Wraps any connection in a + `PersistentSafeguardConnection` that auto-refreshes tokens. +- **`SafeguardForPrivilegedSessions.Connect(...)`** — Creates + `ISafeguardSessionsConnection` for Safeguard for Privileged Sessions (SPS). + +### Safeguard API services + +The SDK targets five backend services, represented by the `Service` enum: + +| Service | Endpoint pattern | Auth required | +|---|---|---| +| `Core` | `/service/core/v{version}` | Yes | +| `Appliance` | `/service/appliance/v{version}` | Yes | +| `Notification` | `/service/notification/v{version}` | No | +| `A2A` | `/service/a2a/v{version}` | Certificate | +| `Management` | `/service/management/v{version}` | Yes | + +### Authentication strategy pattern (`authentication/`) + +All authenticators implement `IAuthenticationMechanism`. When adding a new authentication +method: +1. Implement `IAuthenticationMechanism` in the `authentication/` package +2. Add `Safeguard.connect()` overload(s) in `Safeguard.java` +3. Follow the pattern of existing authenticators (extend `AuthenticatorBase`) + +### Connection classes + +- **`SafeguardConnection`** — Base `ISafeguardConnection` implementation. Makes HTTP calls + via `invokeMethod()` / `invokeMethodFull()`. +- **`PersistentSafeguardConnection`** — Decorator that checks + `getAccessTokenLifetimeRemaining() <= 0` before each call and auto-refreshes tokens. + +### rSTS authentication flow + +All authenticators obtain tokens via the embedded Safeguard RSTS (Resource Security Token +Service) at `https://{host}/RSTS/oauth2/token`. + +- **Password authentication** uses the `password` grant type (Resource Owner Grant). **ROG + is disabled by default** on modern Safeguard appliances. If ROG is disabled, password + auth will fail with a 400 error. It must be explicitly enabled via appliance settings + or the test framework's preflight check. +- **PKCE authentication** (`PasswordAuthenticator` with `usePkce=true`) drives the rSTS + login controller at `/RSTS/UserLogin/LoginController` programmatically, exchanging an + authorization code for a token. **PKCE is always available** regardless of ROG settings + and is the preferred method for interactive/programmatic login. +- **Certificate authentication** uses the `client_credentials` grant type with a client + certificate. +- **Access token authentication** accepts a pre-obtained token but cannot refresh it. + +The test framework handles ROG automatically — it enables ROG before tests run and +restores the original setting when tests complete. Both ROG and PKCE are valid for tests; +use whichever is appropriate for the feature being tested. + +### Event listeners (`event/`) + +- **`SafeguardEventListener`** — Standard SignalR listener. Does NOT survive prolonged outages. +- **`PersistentSafeguardEventListener`** — Auto-reconnecting persistent listener. +- **`PersistentSafeguardA2AEventListener`** — Persistent A2A-specific variant. +- **`EventHandlerRegistry`** — Thread-safe handler dispatch. Each event type gets its own + handler thread; handlers for the same event execute sequentially, handlers for different + events execute concurrently. +- Use `getPersistentEventListener()` for production deployments. +- Event handling code **must use Gson** `JsonElement`/`JsonObject` types (transitive from + the SignalR Java client's `GsonHubProtocol`). + +### A2A (`SafeguardA2AContext`) + +Certificate-only authentication for automated credential retrieval. Key types: +`ISafeguardA2AContext`, `A2ARegistration`, `BrokeredAccessRequest`. + +### SPS integration (`SafeguardForPrivilegedSessions`) + +Integration with Safeguard for Privileged Sessions. `ISafeguardSessionsConnection` / +`SafeguardSessionsConnection`. Connects using basic auth (username/password) over HTTPS. + +## Code conventions + +### Sensitive data as `char[]` + +Passwords, access tokens, and API keys are stored as `char[]` rather than `String` to +allow explicit clearing from memory. Follow this pattern in all new code that handles +credentials. + +### Interface-first design + +Every public type has a corresponding `I`-prefixed interface (`ISafeguardConnection`, +`ISafeguardA2AContext`, `ISafeguardEventListener`, `IAuthenticationMechanism`). Code +against interfaces, not implementations. + +### Dispose pattern + +Connections, A2A contexts, and event listeners implement a `dispose()` method that must +be called to release resources. Every public method on these classes guards against +use-after-dispose: + +```java +if (disposed) { + throw new ObjectDisposedException("ClassName"); +} +``` + +Follow this pattern in any new connection or context class. + +### Error handling + +- Parameter validation throws `ArgumentException` +- HTTP failures throw `SafeguardForJavaException` with status code and response body +- Null HTTP responses throw `SafeguardForJavaException("Unable to connect to ...")` +- Disposed object access throws `ObjectDisposedException` + +### SSL/TLS + +- `ignoreSsl=true` uses `NoopHostnameVerifier` (development only) +- Custom `HostnameVerifier` callback for fine-grained validation +- Default: standard Java certificate validation +- Certificate contexts (`CertificateContext`) support JKS keystores, PFX files, byte + arrays, and Windows certificate store (by thumbprint) +- **Never recommend `ignoreSsl` for production** without explicit warning + +### Logging + +Uses **SLF4J** as the logging facade. The SLF4J dependency provides the facade for both +the SDK and the SignalR library. Users supply their own SLF4J binding (e.g., Logback, +Log4j2, JUL bridge) at runtime. + +### Naming conventions + +- Java standard naming: camelCase for fields/methods, PascalCase for classes +- Interfaces use `I` prefix: `ISafeguardConnection`, `IAuthenticationMechanism` +- Constants: UPPER_SNAKE_CASE +- Package: `com.oneidentity.safeguard.safeguardjava` + +### Java version + +The SDK targets **Java 8** source/target compatibility. Do not use language features +from Java 9+ (var, modules, records, sealed classes, etc.). All dependencies must be +compatible with Java 8. + +## CI/CD + +The project uses **Azure Pipelines** (`azure-pipelines.yml`). Key behavior: + +- Release branches (`master`, `release-*`) deploy to Sonatype/Maven Central with GPG signing +- Non-release branches only build and package +- The CI pipeline uses the `release` Maven profile for signing and publishing +- Version is set via `-Drevision=X.Y.Z` during CI builds + +## Writing a new test suite + +### Suite file structure + +Create `TestFramework/Suites/Suite-YourFeature.ps1` returning a hashtable: + +```powershell +@{ + Name = "Your Feature" + Description = "Tests for your feature" + Tags = @("yourfeature") + + Setup = { + param($Context) + # Create test objects, store in $Context.SuiteData + # Register cleanup actions with Register-SgJTestCleanup + } + + Execute = { + param($Context) + + # Success test + Test-SgJAssert "Can do the thing" { + $result = Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" + $null -ne $result -and $result.Name -eq "expected" + } + + # Error test + Test-SgJAssertThrows "Rejects bad input" { + Invoke-SgJSafeguardApi -Context $Context ` + -Service Core -Method Get -RelativeUrl "Me" ` + -Username "baduser" -Password "wrong" + } + } + + Cleanup = { + param($Context) + # Registered cleanup handles everything. + } +} +``` + +### Key framework functions + +| Function | Purpose | +|---|---| +| `Invoke-SgJSafeguardApi` | Call Safeguard API via the test tool. Supports `-Service`, `-Method`, `-RelativeUrl`, `-Body`, `-Full`, `-Anonymous`, `-Pkce`, `-Username`, `-Password`, `-AccessToken` | +| `Invoke-SgJTokenCommand` | Token operations: `GetToken`, `TokenLifetime`, `RefreshToken`, `Logout` | +| `Test-SgJAssert` | Assert that a script block returns `$true` | +| `Test-SgJAssertThrows` | Assert that a script block throws an error | +| `Register-SgJTestCleanup` | Register a cleanup action (runs in LIFO order after suite) | +| `Remove-SgJStaleTestObject` | Pre-cleanup helper to remove leftover test objects | +| `Remove-SgJSafeguardTestObject` | Delete a specific object by relative URL | + +### Test naming prefix + +All test objects should use `$Context.TestPrefix` (default: `SgJTest`) to avoid collisions: + +```powershell +$userName = "$($Context.TestPrefix)_MyTestUser" +``` + +### Test validation guidelines + +Write tests with **strong validation criteria**: +- Check specific field values, not just "not null" +- Validate HTTP status codes (200, 201, 204) on full responses +- Include negative tests (wrong password, invalid token, bad endpoints) +- Verify bounds on numeric values (e.g., token lifetime 1-1440 minutes) +- Match user identities to confirm correct authentication + +### Adding test tool commands + +If a test requires functionality not exposed by the CLI test tool: + +1. Add the CLI option in `tests/safeguardjavaclient/.../ToolOptions.java` +2. Add the handling logic in `tests/safeguardjavaclient/.../SafeguardJavaClient.java` +3. Add framework support in `TestFramework/SafeguardTestFramework.psm1` if needed +4. Rebuild the test tool before running tests + +## Samples + +The `Samples/` directory contains standalone example projects, each with its own `pom.xml`: + +| Sample | Description | +|---|---| +| `PasswordConnect` | Password-based authentication and API call | +| `CertificateConnect` | Certificate-based authentication via PFX/JKS | +| `A2ARetrievalExample` | Application-to-application credential retrieval | +| `EventListenerExample` | Persistent event listener with SignalR | + +Each sample is self-contained and can be built independently: + +```bash +cd Samples/PasswordConnect +mvn package +java -jar target/PasswordConnect-1.0.jar +``` From 9c9a34836806cf9c92e2958935aa1a4d31482e3b Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sat, 28 Mar 2026 23:46:26 -0600 Subject: [PATCH 20/22] Remove intellij stuff that I accidentally added --- .gitignore | 6 ++++++ .idea/.gitignore | 12 ------------ .idea/compiler.xml | 13 ------------- .idea/encodings.xml | 7 ------- .idea/jarRepositories.xml | 20 -------------------- .idea/misc.xml | 11 ----------- .idea/vcs.xml | 6 ------ 7 files changed, 6 insertions(+), 69 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index c1be709..31bb8ba 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,9 @@ hs_err_pid* /jenkins-plugin/safeguard4jenkins/target/ /safeguard4jenkins/target/ /tests/safeguardjavaclient/target/ + +# JetBrains IDEs +.idea/ +*.iml +*.iws +*.ipr diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 660d40e..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Misc -copilot.* diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 387675c..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e85157a..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 712ab9d..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index b73630f..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 007f9b30759f6bec5366c64008ceab64fe19710d Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sun, 29 Mar 2026 00:04:11 -0600 Subject: [PATCH 21/22] Update these tasks to current versions --- azure-pipelines.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 78581ee..b1f3cd8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,7 @@ steps: script: 'env | sort' displayName: 'Display environment variables' -- task: AzureKeyVault@1 +- task: AzureKeyVault@2 inputs: azureSubscription: 'OneIdentity.RD.SBox.Safeguard-ServiceConnection' keyVaultName: 'SafeguardBuildSecrets' @@ -35,7 +35,7 @@ steps: displayName: 'Get Sonatype Central Portal credentials from Azure Key Vault' condition: and(succeeded(), eq(variables.isReleaseBranch, true)) -- task: AzureKeyVault@1 +- task: AzureKeyVault@2 inputs: azureSubscription: 'Azure.Infrastructure.CodeSigning' KeyVaultName: 'CodeSigningCertificates' @@ -80,7 +80,7 @@ steps: displayName: 'Import the signing key' condition: and(succeeded(), eq(variables.isReleaseBranch, true)) -- task: Maven@3 +- task: Maven@4 inputs: mavenPomFile: 'pom.xml' mavenOptions: '-Xmx3072m' @@ -94,7 +94,7 @@ steps: displayName: 'Build and package SafeguardJava $(version)' condition: and(succeeded(), eq(variables.isReleaseBranch, false)) -- task: Maven@3 +- task: Maven@4 inputs: mavenPomFile: 'pom.xml' mavenOptions: '-Xmx3072m' From 3c66a22b0d1b3dcd37a0bd3e914cef802dd33210 Mon Sep 17 00:00:00 2001 From: petrsnd Date: Sun, 29 Mar 2026 00:09:03 -0600 Subject: [PATCH 22/22] Remove copilot instructions, rely on agents.md only --- .github/copilot-instructions.md | 103 -------------------------------- .gitignore | 3 + 2 files changed, 3 insertions(+), 103 deletions(-) delete mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index ca2b910..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,103 +0,0 @@ -# Copilot Instructions for SafeguardJava - -## Build Commands - -```bash -# Build (requires JDK 8+ and Maven 3.0.5+) -mvn package - -# Build with a specific version -mvn package -Drevision=7.5.0 - -# Build for release (includes source jars, javadoc, GPG signing) -mvn deploy -P release --settings settings/settings.xml - -# Clean build -mvn clean package -``` - -There are no unit tests in the main project. The `tests/safeguardjavaclient/` directory contains an interactive CLI test harness (not automated tests), which requires a live Safeguard appliance to run. - -## Architecture - -SafeguardJava is a Java SDK for the One Identity Safeguard for Privileged Passwords REST API. It targets Java 8 source/target compatibility and is published to Maven Central as `com.oneidentity.safeguard:safeguardjava`. - -### Entry Points - -All SDK usage starts through static factory methods: - -- **`Safeguard.connect(...)`** — Creates `ISafeguardConnection` instances. Multiple overloads support password, certificate (keystore/file/thumbprint/byte array), and access token authentication. -- **`Safeguard.A2A.getContext(...)`** — Creates `ISafeguardA2AContext` for application-to-application credential retrieval. Only supports certificate authentication. -- **`Safeguard.A2A.Events.getPersistentA2AEventListener(...)`** — Creates auto-reconnecting A2A event listeners. -- **`Safeguard.Persist(connection)`** — Wraps any connection in a `PersistentSafeguardConnection` that auto-refreshes tokens. -- **`SafeguardForPrivilegedSessions.Connect(...)`** — Creates `ISafeguardSessionsConnection` for Safeguard for Privileged Sessions (SPS). - -### Package Structure (`com.oneidentity.safeguard.safeguardjava`) - -- **Root package** — Public API: `Safeguard`, `ISafeguardConnection`/`SafeguardConnection`, `ISafeguardA2AContext`/`SafeguardA2AContext`, streaming classes, and SPS integration. -- **`authentication/`** — `IAuthenticationMechanism` interface with implementations: `PasswordAuthenticator`, `CertificateAuthenticator`, `AccessTokenAuthenticator`, `AnonymousAuthenticator`, `ManagementServiceAuthenticator`. All extend `AuthenticatorBase`. -- **`event/`** — SignalR-based event system: `ISafeguardEventListener`/`SafeguardEventListener`, persistent (auto-reconnecting) variants, `EventHandlerRegistry` for thread-safe handler management. -- **`restclient/`** — `RestClient` wraps Apache HttpClient 4.5 for all HTTP operations. OkHttp 4.11 is present as a transitive dependency of the Microsoft SignalR client. -- **`data/`** — DTOs and enums (`Service`, `Method`, `KeyFormat`, `SafeguardEventListenerState`, `BrokeredAccessRequestType`). -- **`exceptions/`** — `SafeguardForJavaException` (general), `ArgumentException` (validation), `ObjectDisposedException` (resource lifecycle), `SafeguardEventListenerDisconnectedException`. - -### Safeguard API Services - -The SDK targets five backend services, represented by the `Service` enum: - -| Service | Endpoint Pattern | Auth Required | -|---|---|---| -| `Core` | `/service/core/v{version}` | Yes | -| `Appliance` | `/service/appliance/v{version}` | Yes | -| `Notification` | `/service/notification/v{version}` | No | -| `A2A` | `/service/a2a/v{version}` | Certificate | -| `Management` | `/service/management/v{version}` | Yes | - -The default API version is **v4** (since SDK 7.0). Pass `apiVersion` parameter to use v3. - -## Key Conventions - -### Interface-First Design - -Every public type has a corresponding `I`-prefixed interface (`ISafeguardConnection`, `ISafeguardA2AContext`, `ISafeguardEventListener`, `IAuthenticationMechanism`). Code against interfaces, not implementations. - -### Dispose Pattern - -Connections, A2A contexts, and event listeners implement a `dispose()` method that must be called to release resources. Every public method on these classes guards against use-after-dispose: - -```java -if (disposed) { - throw new ObjectDisposedException("ClassName"); -} -``` - -### Authentication Flow - -All authenticators obtain tokens via the embedded Safeguard RSTS (Resource Security Token Service) at `https://{host}/RSTS/oauth2/token`. Password auth uses the `password` grant type; certificate auth uses `client_credentials`. The `AccessTokenAuthenticator` accepts a pre-obtained token but cannot refresh it. - -### SSL/TLS Handling - -Three modes: `ignoreSsl=true` (uses `NoopHostnameVerifier`), custom `HostnameVerifier` callback, or default Java validation. Certificate contexts (`CertificateContext`) support JKS keystores, PFX files, byte arrays, and Windows certificate store (by thumbprint). - -### Error Handling - -- Parameter validation throws `ArgumentException` -- HTTP failures throw `SafeguardForJavaException` with status code and response body -- Null HTTP responses throw `SafeguardForJavaException("Unable to connect to ...")` -- Disposed object access throws `ObjectDisposedException` - -### Logging - -Uses `java.util.logging` (JUL). The SLF4J dependency provides the facade for the SignalR library. Debug-level HTTP wire logging can be enabled via Apache Commons Logging system properties (see `tests/safeguardjavaclient` for examples). - -### Sensitive Data as `char[]` - -Passwords, access tokens, and API keys are stored as `char[]` rather than `String` to allow explicit clearing from memory. - -### Event System Threading - -Each event type gets its own handler thread. Handlers for the same event execute sequentially; handlers for different events execute concurrently. The `EventHandlerRegistry` manages thread-safe concurrent dispatch. - -### CI/CD - -The project uses Azure Pipelines (`azure-pipelines.yml`). Release branches (`master`, `release-*`) deploy to Sonatype/Maven Central with GPG signing. Non-release branches only build and package. diff --git a/.gitignore b/.gitignore index 31bb8ba..361bc04 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ hs_err_pid* *.iml *.iws *.ipr + +# Copilot instructions (keep local only) +.github/copilot-instructions.md