diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..09d5ec9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,27 @@
+# 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
+
+[*.{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/.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/.gitignore b/.gitignore
index 676bcd4..361bc04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,4 +24,13 @@ hs_err_pid*
/target/
/jenkins-plugin/safeguard4jenkins/target/
/safeguard4jenkins/target/
-/tests/safeguardjavaclient/target/
\ No newline at end of file
+/tests/safeguardjavaclient/target/
+
+# JetBrains IDEs
+.idea/
+*.iml
+*.iws
+*.ipr
+
+# Copilot instructions (keep local only)
+.github/copilot-instructions.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
+```
diff --git a/README.md b/README.md
index 698709c..8bff6b1 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
[](https://github.com/OneIdentity/SafeguardJava/blob/master/LICENSE)
-[](https://maven-badges.herokuapp.com/maven-central/com.oneidentity.safeguard/safeguardjava)
-[](https://oss.sonatype.org/content/repositories/releases/com/oneidentity/safeguard/safeguardjava/)
-[](https://oss.sonatype.org/content/repositories/snapshots/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.
@@ -93,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
@@ -197,14 +210,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.safeguardsafeguardjava
- 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/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.
diff --git a/TestFramework/Invoke-SafeguardTests.ps1 b/TestFramework/Invoke-SafeguardTests.ps1
new file mode 100644
index 0000000..2a5ae6a
--- /dev/null
+++ b/TestFramework/Invoke-SafeguardTests.ps1
@@ -0,0 +1,334 @@
+#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 Pkce
+ 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".
+
+.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()]
+ [switch]$Pkce,
+
+ [Parameter()]
+ [string]$SpsAppliance,
+
+ [Parameter()]
+ [string]$SpsUserName = "admin",
+
+ [Parameter()]
+ [string]$SpsPassword,
+
+ [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
+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
+
+# --- 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
+ }
+}
+
+# --- 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
+Clear-SgJStaleTestEnvironment -Context $context
+
+# --- Run suites ---
+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
+
+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..f2e0762
--- /dev/null
+++ b/TestFramework/SafeguardTestFramework.psm1
@@ -0,0 +1,1128 @@
+#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]$SpsAppliance,
+
+ [Parameter()]
+ [string]$SpsUserName,
+
+ [Parameter()]
+ [string]$SpsPassword,
+
+ [Parameter()]
+ [string]$TestPrefix = "SgJTest",
+
+ [Parameter()]
+ [string]$MavenCmd,
+
+ [Parameter()]
+ [switch]$Pkce
+ )
+
+ $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
+
+ # Paths
+ RepoRoot = $repoRoot
+ TestRoot = $PSScriptRoot
+ ToolDir = (Join-Path $repoRoot "tests" "safeguardjavaclient")
+ MavenCmd = $MavenCmd
+ Pkce = [bool]$Pkce
+
+ # 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()]
+ [string]$CertificateFile,
+
+ [Parameter()]
+ [string]$CertificatePassword,
+
+ [Parameter()]
+ [switch]$Pkce,
+
+ [Parameter()]
+ [switch]$Full,
+
+ [Parameter()]
+ [string]$File,
+
+ [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`""
+ }
+ 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 }
+ $provider = "local"
+ $toolArgs += " -u $effectiveUser -i $provider -p"
+ if ($Pkce) { $toolArgs += " --pkce" }
+ $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 ($File) { $toolArgs += " -F `"$File`"" }
+
+ 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", "RefreshToken", "Logout")]
+ [string]$Command,
+
+ [Parameter()]
+ [string]$Username,
+
+ [Parameter()]
+ [string]$Password,
+
+ [Parameter()]
+ [switch]$Pkce
+ )
+
+ 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"
+ if ($Pkce) { $toolArgs += " --pkce" }
+
+ switch ($Command) {
+ "TokenLifetime" { $toolArgs += " -T" }
+ "GetToken" { $toolArgs += " -G" }
+ "RefreshToken" { $toolArgs += " --refresh-token" }
+ "Logout" { $toolArgs += " -L" }
+ }
+
+ 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
+# ============================================================================
+
+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 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
+ 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'
+ 'Invoke-SgJSafeguardSessions'
+ 'Test-SgJSpsConfigured'
+ 'Test-SgJCertsConfigured'
+
+ # Object management
+ 'Remove-SgJStaleTestObject'
+ 'Remove-SgJSafeguardTestObject'
+ 'Remove-SgJStaleTestCert'
+ '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-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
new file mode 100644
index 0000000..5fdb163
--- /dev/null
+++ b/TestFramework/Suites/Suite-AnonymousAccess.ps1
@@ -0,0 +1,37 @@
+@{
+ 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 status response contains ApplianceCurrentState" {
+ $result = Invoke-SgJSafeguardApi -Context $Context `
+ -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"
+ }
+ }
+
+ 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..83cd664
--- /dev/null
+++ b/TestFramework/Suites/Suite-ApiInvocation.ps1
@@ -0,0 +1,224 @@
+@{
+ 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 "${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
+ $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
+ }
+
+ # --- 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 `
+ -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
+ }
+
+ # --- 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 `
+ -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-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-PasswordAuth.ps1 b/TestFramework/Suites/Suite-PasswordAuth.ps1
new file mode 100644
index 0000000..580414b
--- /dev/null
+++ b/TestFramework/Suites/Suite-PasswordAuth.ps1
@@ -0,0 +1,88 @@
+@{
+ 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 $result.Name -eq $Context.AdminUserName.ToLower()
+ }
+
+ 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 "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 = {
+ param($Context)
+ # Registered cleanup handles user deletion.
+ }
+}
diff --git a/TestFramework/Suites/Suite-PkceAuth.ps1 b/TestFramework/Suites/Suite-PkceAuth.ps1
new file mode 100644
index 0000000..57dfa74
--- /dev/null
+++ b/TestFramework/Suites/Suite-PkceAuth.ps1
@@ -0,0 +1,68 @@
+@{
+ 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 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
+ }
+
+ 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 $meResult.Name -eq $Context.AdminUserName.ToLower()
+ }
+
+ 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
+ }
+
+ # --- 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 = {
+ param($Context)
+ # No cleanup needed.
+ }
+}
diff --git a/TestFramework/Suites/Suite-SpsIntegration.ps1 b/TestFramework/Suites/Suite-SpsIntegration.ps1
new file mode 100644
index 0000000..ae1b846
--- /dev/null
+++ b/TestFramework/Suites/Suite-SpsIntegration.ps1
@@ -0,0 +1,59 @@
+@{
+ 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 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 and email config query" {
+ $result = Invoke-SgJSafeguardSessions -Context $Context `
+ -Method Get -RelativeUrl "configuration/management/email"
+ $null -ne $result
+ }
+
+ Test-SgJAssert "SPS firmware slots query returns body key" {
+ $result = Invoke-SgJSafeguardSessions -Context $Context `
+ -Method Get -RelativeUrl "firmware/slots"
+ $null -ne $result -and $null -ne $result.body
+ }
+
+ Test-SgJAssert "SPS full response has status 200" {
+ $result = Invoke-SgJSafeguardSessions -Context $Context `
+ -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 = {
+ param($Context)
+ # No objects created.
+ }
+}
diff --git a/TestFramework/Suites/Suite-Streaming.ps1 b/TestFramework/Suites/Suite-Streaming.ps1
new file mode 100644
index 0000000..6ff4f7c
--- /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 has Id and Complete status" {
+ $null -ne $uploadResult.Id -and $uploadResult.Status -eq "Complete"
+ }
+
+ } 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/TestFramework/Suites/Suite-TokenManagement.ps1 b/TestFramework/Suites/Suite-TokenManagement.ps1
new file mode 100644
index 0000000..a43f2d4
--- /dev/null
+++ b/TestFramework/Suites/Suite-TokenManagement.ps1
@@ -0,0 +1,76 @@
+@{
+ Name = "Token Management"
+ Description = "Tests access token retrieval, refresh, lifetime bounds, and logout"
+ 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 "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" {
+ $tokenResult = Invoke-SgJTokenCommand -Context $Context -Command GetToken
+ $token = $tokenResult.AccessToken
+
+ $meResult = Invoke-SgJSafeguardApi -Context $Context `
+ -Service Core -Method Get -RelativeUrl "Me" `
+ -AccessToken $token
+ $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
+ }
+ }
+
+ Cleanup = {
+ param($Context)
+ # Nothing to clean up.
+ }
+}
diff --git a/TestFramework/TestData/CERTS/IntermediateCA.cer b/TestFramework/TestData/CERTS/IntermediateCA.cer
new file mode 100644
index 0000000..ee30f6b
Binary files /dev/null and b/TestFramework/TestData/CERTS/IntermediateCA.cer differ
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 0000000..bd7b629
Binary files /dev/null and b/TestFramework/TestData/CERTS/IntermediateCA.pfx differ
diff --git a/TestFramework/TestData/CERTS/IntermediateCA.pvk b/TestFramework/TestData/CERTS/IntermediateCA.pvk
new file mode 100644
index 0000000..b76d5d0
Binary files /dev/null and b/TestFramework/TestData/CERTS/IntermediateCA.pvk differ
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 0000000..4ce1176
Binary files /dev/null and b/TestFramework/TestData/CERTS/RootCA.cer differ
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 0000000..2b35846
Binary files /dev/null and b/TestFramework/TestData/CERTS/RootCA.pfx differ
diff --git a/TestFramework/TestData/CERTS/RootCA.pvk b/TestFramework/TestData/CERTS/RootCA.pvk
new file mode 100644
index 0000000..5c48393
Binary files /dev/null and b/TestFramework/TestData/CERTS/RootCA.pvk differ
diff --git a/TestFramework/TestData/CERTS/UserCert.cer b/TestFramework/TestData/CERTS/UserCert.cer
new file mode 100644
index 0000000..cb3037b
Binary files /dev/null and b/TestFramework/TestData/CERTS/UserCert.cer differ
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 0000000..6d89501
Binary files /dev/null and b/TestFramework/TestData/CERTS/UserCert.pfx differ
diff --git a/TestFramework/TestData/CERTS/UserCert.pvk b/TestFramework/TestData/CERTS/UserCert.pvk
new file mode 100644
index 0000000..c60d561
Binary files /dev/null and b/TestFramework/TestData/CERTS/UserCert.pvk differ
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index d394495..b1f3cd8 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'
@@ -27,15 +27,15 @@ steps:
script: 'env | sort'
displayName: 'Display environment variables'
-- task: AzureKeyVault@1
+- task: AzureKeyVault@2
inputs:
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
+- 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'
@@ -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
@@ -164,4 +182,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/pom.xml b/pom.xml
index 523cf99..4e7385b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
UTF-8
- 7.5.0-SNAPSHOT
+ 8.2.0-SNAPSHOT./signingcert.pfx1secret
@@ -42,17 +42,17 @@
com.squareup.okhttp3okhttp
- 4.11.0
+ 4.12.0com.microsoft.signalrsignalr
- 7.0.11
+ 8.0.12
- org.apache.httpcomponents
- httpclient
- 4.5.14
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.4.2commons-codec
@@ -63,12 +63,7 @@
commons-codeccommons-codec
- 1.15
-
-
- org.apache.httpcomponents
- httpmime
- 4.5.14
+ 1.17.1jakarta.xml.bind
@@ -84,18 +79,19 @@
com.fasterxml.jackson.corejackson-databind
- 2.15.2
+ 2.18.3jarorg.slf4jslf4j-api
- 2.0.9
+ 2.0.17
+
com.google.code.gsongson
- 2.10.1
+ 2.11.0
@@ -107,7 +103,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.0
+ 3.13.0true1.8
@@ -134,15 +130,49 @@
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.13
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.7.0true
- ossrh
- https://oss.sonatype.org/
- false
+ central
+ false
+ validated
+
+
+
+ org.ec4j.maven
+ editorconfig-maven-plugin
+ 0.2.0
+
+
+ check
+ verify
+
+ check
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.6.6
+
+ Max
+ Medium
+ spotbugs-exclude.xml
+ true
+
+
+ check
+ verify
+
+ check
+
+
+
@@ -231,16 +261,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 2d47769..c27cb66 100644
--- a/settings/settings.xml
+++ b/settings/settings.xml
@@ -2,19 +2,24 @@
- ossrh
+ central${sonatypeusername}${sonatypepassword}
+
+ github
+ ${githubusername}
+ ${githubtoken}
+ ${gpgkeyname}${signingkeystorepassword}
-
+
- ossrh
+ centraltrue
@@ -24,5 +29,5 @@
-
-
\ No newline at end of file
+
+
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/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..6fc35f0
--- /dev/null
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/AgentBasedLoginUtils.java
@@ -0,0 +1,168 @@
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 = LoggerFactory.getLogger(AgentBasedLoginUtils.class);
+
+ /** 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 static final SecureRandom RANDOM = new SecureRandom();
+
+ 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];
+ RANDOM.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];
+ RANDOM.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/CertificateUtilities.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java
index 4668088..14254ee 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/CertificateUtilities.java
@@ -18,14 +18,14 @@ 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 {
-
+
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..bea0018 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/PersistentSafeguardConnection.java
@@ -11,9 +11,8 @@
import java.util.Map;
class PersistentSafeguardConnection implements ISafeguardConnection {
-
+
private final ISafeguardConnection _connection;
- private boolean disposed;
public PersistentSafeguardConnection(ISafeguardConnection connection) {
_connection = connection;
@@ -30,7 +29,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 +71,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..d9faced 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;
@@ -38,7 +39,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 +67,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 +82,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 +98,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 +129,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) {
@@ -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.
@@ -149,14 +244,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 +263,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 +277,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 +304,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 +330,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 +344,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 +365,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 +379,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 +410,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 +436,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 +457,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 +467,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 +483,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 +495,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 +514,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 +529,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 +557,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 +583,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 +598,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 +619,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 +634,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 +666,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 +693,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 +714,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 +725,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 +742,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 +776,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 +791,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 +822,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 +832,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;
@@ -749,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)));
}
/**
@@ -762,7 +857,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 +867,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 +887,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
@@ -812,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)));
}
/**
@@ -825,7 +920,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 +947,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
@@ -872,9 +967,9 @@ 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)));
}
-
+
/**
* Get a persistent event listener using a client certificate stored in memory.
*
@@ -884,7 +979,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 +996,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 +1007,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.
@@ -931,9 +1026,9 @@ 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)));
}
-
+
/**
* Get a persistent event listener using a client certificate from the
* certificate keystore for authentication.
@@ -944,7 +1039,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 +1067,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
@@ -992,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)));
}
/**
@@ -1006,7 +1101,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 +1120,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 +1128,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 +1146,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.");
@@ -1062,11 +1157,11 @@ 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)));
}
/**
- * 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 +1169,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 +1182,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 +1197,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 +1206,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 +1224,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.");
@@ -1140,11 +1235,11 @@ 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)));
}
/**
- * 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 +1248,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 +1261,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 +1285,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
@@ -1210,9 +1305,9 @@ 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)));
}
-
+
/**
* Get a persistent event listener using a client certificate stored in memory.
*
@@ -1223,7 +1318,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 +1336,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 +1348,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.
@@ -1273,9 +1368,9 @@ 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)));
}
-
+
/**
* Get a persistent event listener using a client certificate from the
* certificate keystore for authentication.
@@ -1287,13 +1382,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 +1416,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 +1443,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 +1457,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 +1465,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 +1489,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 +1502,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 +1512,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 +1534,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 +1561,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 +1582,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 +1599,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 +1608,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 +1620,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 +1631,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 +1648,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.
@@ -1573,14 +1668,14 @@ 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);
}
/**
- * 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 +1690,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 +1710,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 +1725,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 +1734,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;
@@ -1652,13 +1747,13 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe
return new PersistentSafeguardA2AEventListener(
new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version,
- ignoreSsl, null), apiKey, handler);
+ sslIgnore, 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 +1766,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 +1775,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 +1785,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 +1801,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 +1810,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;
@@ -1728,13 +1823,13 @@ public static ISafeguardEventListener getPersistentA2AEventListener(char[] apiKe
return new PersistentSafeguardA2AEventListener(
new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version,
- ignoreSsl, null), apiKey, handler);
+ sslIgnore, 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 +1841,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 +1850,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 +1860,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 +1879,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.
@@ -1804,14 +1899,14 @@ 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);
}
/**
- * 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 +1921,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 +1941,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 +1956,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 +1965,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;
@@ -1883,17 +1978,17 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List
return new PersistentSafeguardA2AEventListener(
new SafeguardA2AContext(networkAddress, certificatePath, certificatePassword, version,
- ignoreSsl, null), apiKeys, handler);
+ sslIgnore, 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 +1999,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 +2009,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 +2026,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 +2051,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 +2061,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 +2083,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, sslIgnore,
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 +2106,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 +2115,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 +2125,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 +2141,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 +2150,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;
@@ -2068,13 +2163,13 @@ public static ISafeguardEventListener getPersistentA2AEventListener(List
return new PersistentSafeguardA2AEventListener(
new SafeguardA2AContext(networkAddress, certificateData, certificatePassword, version,
- ignoreSsl, null), apiKeys, handler);
+ sslIgnore, 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 +2181,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 +2190,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..36093c2 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;
@@ -21,19 +20,21 @@
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;
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 {
+ private static final Logger logger = LoggerFactory.getLogger(SafeguardA2AContext.class);
+
private boolean disposed;
private final String networkAddress;
@@ -45,10 +46,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 +65,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 +86,7 @@ public SafeguardA2AContext(String networkAddress, byte[] certificateData, char[]
@Override
public List getRetrievableAccounts() throws ObjectDisposedException, SafeguardForJavaException {
-
+
if (disposed) {
throw new ObjectDisposedException("SafeguardA2AContext");
}
@@ -96,37 +97,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()))
- 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);
-
+
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()))
- 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);
-
+
for (A2ARetrievableAccountInternal retrieval : retrievals)
{
A2ARetrievableAccount account = new A2ARetrievableAccount();
@@ -144,7 +145,7 @@ public List getRetrievableAccounts() throws ObjectDispo
account.setDomainName(retrieval.getDomainName());
account.setAccountType(retrieval.getAccountType());
account.setAccountDescription(retrieval.getAccountDescription());
-
+
list.add(account);
}
}
@@ -153,11 +154,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,16 +174,16 @@ 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())) {
+ 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.");
+ logger.info("Successfully retrieved A2A password.");
return password;
}
@@ -191,11 +192,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,20 +206,20 @@ 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()));
}
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.");
+
+ logger.info("Successfully set A2A password.");
}
@Override
@@ -229,7 +230,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,23 +246,23 @@ 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())) {
+ 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();
-
- Logger.getLogger(SafeguardA2AContext.class.getName()).log(Level.INFO, "Successfully retrieved A2A private key.");
+ char[] privateKey = parseJsonString(reply).toCharArray();
+
+ logger.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,18 +272,18 @@ 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);
+
+ String body = serializeToJson(sshKey);
Map headers = new HashMap<>();
headers.put(HttpHeaders.AUTHORIZATION, String.format("A2A %s", new String(apiKey)));
@@ -291,27 +292,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())) {
+ 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.");
+
+ logger.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.");
@@ -330,13 +331,13 @@ 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);
-
+
for (ApiKeySecretInternal apiKeySecretInternal : apiKeySecretsInternal) {
ApiKeySecret apiKeySecret = new ApiKeySecret();
apiKeySecret.setId(apiKeySecretInternal.getId());
@@ -345,17 +346,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");
}
@@ -368,14 +369,14 @@ 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;
}
-
+
@Override
public ISafeguardEventListener getA2AEventListener(List apiKeys, ISafeguardEventHandler handler)
throws ObjectDisposedException, ArgumentException {
-
+
if (disposed) {
throw new ObjectDisposedException("SafeguardA2AContext");
}
@@ -388,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;
}
@@ -404,7 +405,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 +418,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 +438,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,14 +453,14 @@ 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())) {
+ 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.");
+ logger.info("Successfully created A2A access request.");
return reply;
}
@@ -478,51 +479,67 @@ 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);
} catch (IOException ex) {
- Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex);
+ logger.error("Exception occurred", ex);
}
return null;
}
-
+
private List parseA2ARetrievableAccountResponse(String response) {
-
+
ObjectMapper mapper = new ObjectMapper();
-
+
try {
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;
}
private List parseApiKeySecretResponse(String response) {
-
+
ObjectMapper mapper = new ObjectMapper();
-
+
try {
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;
}
+
+ 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 ea2856c..fbf0253 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.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 {
+ private static final Logger logger = LoggerFactory.getLogger(SafeguardConnection.class);
+
private boolean disposed;
private final IAuthenticationMechanism authenticationMechanism;
@@ -34,7 +36,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 +51,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);
}
@@ -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
@@ -96,17 +98,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 +123,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())) {
+
+ 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);
-
+
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 +154,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,21 +169,21 @@ 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.");
+
+ logger.trace("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(
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;
}
@@ -189,7 +191,7 @@ public SafeguardEventListener getEventListener() throws ObjectDisposedException,
@Override
public ISafeguardEventListener getPersistentEventListener()
throws ObjectDisposedException, SafeguardForJavaException {
-
+
if (disposed)
throw new ObjectDisposedException("SafeguardConnection");
@@ -204,43 +206,43 @@ 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 {
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());
- 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("Reponse status code: {}", fullResponse.getStatusCode());
+ 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.getLogger(SafeguardConnection.class.getName()).log(Level.FINEST, " Body size: {0}", msg);
+ logger.trace(" Body size: {}", msg);
}
protected RestClient getClientForService(Service service) throws SafeguardForJavaException {
@@ -258,30 +260,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 +303,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/SafeguardSessionsConnection.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java
index 1f23eef..a74d952 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SafeguardSessionsConnection.java
@@ -9,19 +9,20 @@
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 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.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.
*/
class SafeguardSessionsConnection implements ISafeguardSessionsConnection {
+ private static final Logger logger = LoggerFactory.getLogger(SafeguardSessionsConnection.class);
+
private boolean disposed;
private RestClient client;
@@ -33,9 +34,7 @@ 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.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);
@@ -46,9 +45,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");
@@ -56,7 +55,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 +90,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;
@@ -118,14 +117,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));
+ logger.trace(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 7bacf49..1faadf0 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/SpsStreamingRequest.java
@@ -11,10 +11,13 @@
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;
+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;
@@ -46,20 +49,20 @@ 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);
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 {
@@ -82,23 +85,23 @@ 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);
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");
}
@@ -116,23 +119,23 @@ 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);
}
-
+
@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;
@@ -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/StreamResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java
index 4575e95..3b30ad4 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/StreamResponse.java
@@ -3,9 +3,9 @@
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
*/
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..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.http.client.methods.CloseableHttpResponse;
+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;
@@ -31,7 +34,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 +44,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);
@@ -55,12 +58,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);
@@ -70,7 +73,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 +83,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);
@@ -91,10 +94,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;
@@ -111,11 +114,11 @@ 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.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 874a263..749d93a 100644
--- a/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java
+++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/Utils.java
@@ -2,21 +2,22 @@
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.apache.http.HttpEntity;
-import org.apache.http.ParseException;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.util.EntityUtils;
+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;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
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