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 @@ [![GitHub](https://img.shields.io/github/license/OneIdentity/SafeguardJava.svg)](https://github.com/OneIdentity/SafeguardJava/blob/master/LICENSE) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.oneidentity.safeguard/safeguardjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.oneidentity.safeguard/safeguardjava) -[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/r/https/oss.sonatype.org/com.oneidentity.safeguard/safeguardjava.svg)](https://oss.sonatype.org/content/repositories/releases/com/oneidentity/safeguard/safeguardjava/) -[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.oneidentity.safeguard/safeguardjava.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/oneidentity/safeguard/safeguardjava/) +[![Maven Central](https://img.shields.io/maven-central/v/com.oneidentity.safeguard/safeguardjava)](https://central.sonatype.com/artifact/com.oneidentity.safeguard/safeguardjava) # SafeguardJava @@ -16,7 +14,7 @@ One Identity open source projects are supported through [One Identity GitHub iss ## Default API Update -SafeguardDotNet will use v4 API by default starting with version 7.0. It is +SafeguardJava will use v4 API by default starting with version 7.0. It is possible to continue using the v3 API by passing in the apiVersion parameter when creating a connection or A2A context. @@ -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.safeguard safeguardjava - 7.3.0 + 7.5.0 ``` + +### Building SafeguardJava + +Building SafeguardJava requires Java JDK 8 or greater and Maven 3.0.5 or greater. + +```bash +mvn clean package +``` + +### Publishing + +SafeguardJava is published to Maven Central via the +[Sonatype Central Portal](https://central.sonatype.com) and to GitHub Packages. +Release builds are triggered automatically by the Azure Pipeline when changes are +pushed to `master` or `release-*` branches. + +Publishing credentials and signing keys are stored in Azure Key Vault and injected +at build time. To configure publishing for a new environment: + +1. Generate a user token at [central.sonatype.com](https://central.sonatype.com) + and store it as `SonatypeUserToken` / `SonatypeRepositoryPassword` in your + Azure Key Vault +2. Store a GitHub PAT with `write:packages` scope as `GitHubPackagesToken` +3. Import the GPG signing key and code signing certificate into the Key Vault diff --git a/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.pfx 1 secret @@ -42,17 +42,17 @@ com.squareup.okhttp3 okhttp - 4.11.0 + 4.12.0 com.microsoft.signalr signalr - 7.0.11 + 8.0.12 - org.apache.httpcomponents - httpclient - 4.5.14 + org.apache.httpcomponents.client5 + httpclient5 + 5.4.2 commons-codec @@ -63,12 +63,7 @@ commons-codec commons-codec - 1.15 - - - org.apache.httpcomponents - httpmime - 4.5.14 + 1.17.1 jakarta.xml.bind @@ -84,18 +79,19 @@ com.fasterxml.jackson.core jackson-databind - 2.15.2 + 2.18.3 jar org.slf4j slf4j-api - 2.0.9 + 2.0.17 + com.google.code.gson gson - 2.10.1 + 2.11.0 @@ -107,7 +103,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.13.0 true 1.8 @@ -134,15 +130,49 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - false + central + false + validated + + + + 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 + central true @@ -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>() { }); } catch (IOException ex) { - Logger.getLogger(PasswordAuthenticator.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return map; @@ -52,7 +53,7 @@ public static String getResponse(CloseableHttpResponse response) { if (entity != null) { try { return EntityUtils.toString(response.getEntity()); - + } catch (IOException | ParseException ex) {} } return ""; @@ -69,7 +70,7 @@ public static boolean isSuccessful(int status) { return false; } } - + public static String getOsName() { if (OS == null) { OS = System.getProperty("os.name"); @@ -80,10 +81,10 @@ public static String getOsName() { public static boolean isWindows() { return getOsName().startsWith("Windows"); } - + public static boolean isSunMSCAPILoaded() { Provider provider = Security.getProvider("SunMSCAPI"); return provider != null; } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java index e478549..54d9233 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AccessTokenAuthenticator.java @@ -6,15 +6,13 @@ public class AccessTokenAuthenticator extends AuthenticatorBase { - private boolean disposed; - public AccessTokenAuthenticator(String networkAddress, char[] accessToken, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); if (accessToken == null) throw new ArgumentException("The accessToken parameter can not be null"); - + this.accessToken = accessToken.clone(); } @@ -22,7 +20,7 @@ public AccessTokenAuthenticator(String networkAddress, char[] accessToken, public String getId() { return "AccessToken"; } - + @Override protected char[] getRstsTokenInternal() throws SafeguardForJavaException { @@ -33,21 +31,11 @@ protected char[] getRstsTokenInternal() throws SafeguardForJavaException public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Access token authenticators are not cloneable"); } - + @Override public void dispose() { super.dispose(); - disposed = true; } - - @Override - protected void finalize() throws Throwable { - try { - } finally { - disposed = true; - super.finalize(); - } - } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java index d77ea39..c5b7457 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AnonymousAuthenticator.java @@ -6,19 +6,17 @@ import java.util.HashMap; import java.util.Map; import javax.net.ssl.HostnameVerifier; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class AnonymousAuthenticator extends AuthenticatorBase { - private boolean disposed; - public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); - + String notificationUrl = String.format("https://%s/service/notification/v%d", networkAddress, apiVersion); RestClient notificationClient = new RestClient(notificationUrl, ignoreSsl, validationCallback); - + Map headers = new HashMap<>(); headers.put(HttpHeaders.ACCEPT, "application/json"); headers.put(HttpHeaders.CONTENT_TYPE, "application/json"); @@ -27,12 +25,12 @@ public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ign if (response == null) { throw new SafeguardForJavaException(String.format("Unable to anonymously connect to web service %s", notificationClient.getBaseURL())); } - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Unable to anonymously connect to {networkAddress}, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } } @@ -40,12 +38,12 @@ public AnonymousAuthenticator(String networkAddress, int apiVersion, boolean ign public String getId() { return "Anonymous"; } - + @Override public boolean isAnonymous() { return true; } - + @Override protected char[] getRstsTokenInternal() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous connection cannot be used to get an API access token, Error: Unsupported operation"); @@ -55,25 +53,15 @@ protected char[] getRstsTokenInternal() throws SafeguardForJavaException { public boolean hasAccessToken() { return false; } - + @Override public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous authenticators are not cloneable"); } - + @Override public void dispose() { super.dispose(); - disposed = true; - } - - @Override - protected void finalize() throws Throwable { - try { - } finally { - disposed = true; - super.finalize(); - } } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java index 1a473c0..b96005b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/AuthenticatorBase.java @@ -14,14 +14,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; -import org.apache.http.HttpHeaders; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; abstract class AuthenticatorBase implements IAuthenticationMechanism { + private static final Logger logger = LoggerFactory.getLogger(AuthenticatorBase.class); + private boolean disposed; private final String networkAddress; @@ -115,7 +117,7 @@ public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, Saf if (response == null) { throw new SafeguardForJavaException(String.format("Unable to connect to web service %s", coreClient.getBaseURL())); } - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { return 0; } @@ -151,9 +153,9 @@ public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJav } String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error exchanging RSTS token from " + this.getId() + "authenticator for Safeguard API access token, Error: " - + String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); + + String.format("%d %s", response.getCode(), reply)); } Map map = Utils.parseResponse(reply); @@ -161,27 +163,27 @@ public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJav accessToken = map.get("UserToken").toCharArray(); } } - + public String resolveProviderToScope(String provider) throws SafeguardForJavaException { try { CloseableHttpResponse response; Map headers = new HashMap<>(); - + headers.clear(); headers.put(HttpHeaders.ACCEPT, "application/json"); - + response = coreClient.execGET("AuthenticationProviders", null, headers, null); - + if (response == null) throw new SafeguardForJavaException("Unable to connect to RSTS to find identity provider scopes"); - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getCode())) throw new SafeguardForJavaException("Error requesting identity provider scopes from RSTS, Error: " + - String.format("%d %s", response.getStatusLine().getStatusCode(), reply)); - + String.format("%d %s", response.getCode(), reply)); + List knownScopes = parseLoginResponse(reply); // 3 step check for determining if the user provided scope is valid: @@ -205,7 +207,7 @@ public String resolveProviderToScope(String provider) throws SafeguardForJavaExc }); throw new SafeguardForJavaException(String.format("Unable to find scope matching '%s' in [%s]", provider, s.toString())); } - + return scope; } catch (SafeguardForJavaException ex) { @@ -239,7 +241,7 @@ protected void finalize() throws Throwable { super.finalize(); } } - + private class Provider { private String RstsProviderId; private String Name; @@ -251,28 +253,28 @@ public Provider(String RstsProviderId, String Name, String RstsProviderScope) { this.RstsProviderScope = RstsProviderScope; } } - + private List parseLoginResponse(String response) { - + List providers = new ArrayList<>(); ObjectMapper mapper = new ObjectMapper(); - + try { JsonNode jsonNodeProviders = mapper.readTree(response); Iterator iter = jsonNodeProviders.elements(); - + while(iter.hasNext()){ JsonNode providerNode=iter.next(); Provider p = new Provider(getJsonValue(providerNode, "RstsProviderId"), getJsonValue(providerNode, "Name"), getJsonValue(providerNode, "RstsProviderScope")); providers.add(p); - } + } } catch (IOException ex) { - Logger.getLogger(Utils.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return providers; } - + private String getMatchingScope(String provider, List providers) { for (Provider s : providers) { if (s.Name.equalsIgnoreCase(provider) || s.RstsProviderId.equalsIgnoreCase(provider)) @@ -280,7 +282,7 @@ private String getMatchingScope(String provider, List providers) { } return null; } - + private String getJsonValue(JsonNode node, String propName) { if (node.get(propName) != null) { return node.get(propName).asText(); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java index 17d03fc..e79251c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/CertificateAuthenticator.java @@ -7,24 +7,24 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import java.util.Map; import javax.net.ssl.HostnameVerifier; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; public class CertificateAuthenticator extends AuthenticatorBase { private boolean disposed; private final CertificateContext clientCertificate; - + private String provider; - public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, + public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(certificateAlias, keystorePath, null, keystorePassword); } - - public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, + + public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); @@ -33,34 +33,34 @@ public CertificateAuthenticator(String networkAddress, String certificateThumbpr public CertificateAuthenticator(String networkAddress, String certificatePath, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(null, certificatePath, null, certificatePassword); } public CertificateAuthenticator(String networkAddress, byte[] certificateData, char[] certificatePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); clientCertificate = new CertificateContext(certificateAlias, null, certificateData, certificatePassword); } - - private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, + + private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.clientCertificate = clientCertificate.cloneObject(); } - - public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, + + public CertificateAuthenticator(String networkAddress, String keystorePath, char[] keystorePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(certificateAlias, keystorePath, null, keystorePassword); } - - public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, + + public CertificateAuthenticator(String networkAddress, String certificateThumbprint, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) throws SafeguardForJavaException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); @@ -70,7 +70,7 @@ public CertificateAuthenticator(String networkAddress, String certificateThumbpr public CertificateAuthenticator(String networkAddress, String certificatePath, char[] certificatePassword, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(null, certificatePath, null, certificatePassword); @@ -78,25 +78,25 @@ public CertificateAuthenticator(String networkAddress, String certificatePath, c public CertificateAuthenticator(String networkAddress, byte[] certificateData, char[] certificatePassword, String certificateAlias, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; clientCertificate = new CertificateContext(certificateAlias, null, certificateData, certificatePassword); } - - private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, + + private CertificateAuthenticator(String networkAddress, CertificateContext clientCertificate, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback, String provider) { - + super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; this.clientCertificate = clientCertificate.cloneObject(); } - + @Override public String getId() { return "Certificate"; } - + @Override protected char[] getRstsTokenInternal() throws ObjectDisposedException, SafeguardForJavaException { @@ -110,40 +110,37 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar CloseableHttpResponse response = null; OauthBody body = new OauthBody("client_credentials", providerScope); - + response = rstsClient.execPOST("oauth2/token", null, null, null, body, clientCertificate); - + if (response == null) throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); - + String content = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) { - String msg = Utils.isNullOrEmpty(clientCertificate.getCertificateAlias()) ? - String.format("file=%s", clientCertificate.getCertificatePath()) : String.format("alias=%s", clientCertificate.getCertificateAlias()); - + if (!Utils.isSuccessful(response.getCode())) { throw new SafeguardForJavaException("Error using client_credentials grant_type with " + clientCertificate.toString() + - String.format(", Error: %d %s", response.getStatusLine().getStatusCode(), content)); + String.format(", Error: %d %s", response.getCode(), content)); } - + Map map = Utils.parseResponse(content); - + if (!map.containsKey("access_token")) { throw new SafeguardForJavaException(String.format("Error retrieving the access token for certificate: %s", clientCertificate.getCertificatePath())); } - + return map.get("access_token").toCharArray(); } @Override public Object cloneObject() throws SafeguardForJavaException { - CertificateAuthenticator auth = new CertificateAuthenticator(this.getNetworkAddress(), clientCertificate, + CertificateAuthenticator auth = new CertificateAuthenticator(this.getNetworkAddress(), clientCertificate, this.getApiVersion(), this.isIgnoreSsl(), this.getValidationCallback()); if (this.accessToken != null) { auth.accessToken = this.accessToken.clone(); } return auth; } - + @Override public void dispose() { @@ -151,7 +148,7 @@ public void dispose() clientCertificate.dispose(); disposed = true; } - + @Override protected void finalize() throws Throwable { try { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java index 446849d..d6beaf5 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/ManagementServiceAuthenticator.java @@ -7,7 +7,7 @@ public class ManagementServiceAuthenticator implements IAuthenticationMechanism { private boolean disposed; - + private final String networkAddress; private final int apiVersion; private final boolean ignoreSsl; @@ -25,12 +25,12 @@ public ManagementServiceAuthenticator(IAuthenticationMechanism parentAuthenticat public String getId() { return "Management"; } - + @Override public String getNetworkAddress() { return networkAddress; } - + @Override public int getApiVersion() { return apiVersion; @@ -50,12 +50,12 @@ public HostnameVerifier getValidationCallback() { public boolean isAnonymous() { return true; } - + @Override public boolean hasAccessToken() { return false; } - + @Override public void clearAccessToken() { // There is no access token for anonymous auth @@ -63,29 +63,38 @@ public void clearAccessToken() { @Override public char[] getAccessToken() throws ObjectDisposedException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } return null; } @Override public int getAccessTokenLifetimeRemaining() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } return 0; } - + @Override public void refreshAccessToken() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("ManagementServiceAuthenticator"); + } throw new SafeguardForJavaException("Anonymous connection cannot be used to get an API access token, Error: Unsupported operation"); } - + @Override public String resolveProviderToScope(String provider) throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous connection does not require a provider, Error: Unsupported operation"); } - + @Override public Object cloneObject() throws SafeguardForJavaException { throw new SafeguardForJavaException("Anonymous authenticators are not cloneable"); } - + @Override public void dispose() { disposed = true; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java index 76d04db..6f78cb6 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PasswordAuthenticator.java @@ -7,13 +7,15 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; import java.util.Arrays; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -public class PasswordAuthenticator extends AuthenticatorBase +public class PasswordAuthenticator extends AuthenticatorBase { + private static final Logger logger = LoggerFactory.getLogger(PasswordAuthenticator.class); + private boolean disposed; private final String provider; @@ -22,15 +24,15 @@ public class PasswordAuthenticator extends AuthenticatorBase private final char[] password; public PasswordAuthenticator(String networkAddress, String provider, String username, - char[] password, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) + char[] password, int apiVersion, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { super(networkAddress, apiVersion, ignoreSsl, validationCallback); this.provider = provider; - + if (Utils.isNullOrEmpty(this.provider) || this.provider.equalsIgnoreCase("local")) providerScope = "rsts:sts:primaryproviderid:local"; - + this.username = username; if (password == null) throw new ArgumentException("The password parameter can not be null"); @@ -55,17 +57,17 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar if (response == null) throw new SafeguardForJavaException(String.format("Unable to connect to RSTS service %s", rstsClient.getBaseURL())); - + String reply = Utils.getResponse(response); - if (!Utils.isSuccessful(response.getStatusLine().getStatusCode())) + if (!Utils.isSuccessful(response.getCode())) throw new SafeguardForJavaException(String.format("Error using password grant_type with scope %s, Error: ", providerScope) + - String.format("%s %s", response.getStatusLine().getStatusCode(), reply)); + String.format("%s %s", response.getCode(), reply)); Map map = Utils.parseResponse(reply); if (!map.containsKey("access_token")) throw new SafeguardForJavaException(String.format("Error retrieving the access key for scope: %s", providerScope)); - + return map.get("access_token").toCharArray(); } @@ -73,16 +75,16 @@ protected char[] getRstsTokenInternal() throws ObjectDisposedException, Safeguar public Object cloneObject() throws SafeguardForJavaException { try { - PasswordAuthenticator auth = new PasswordAuthenticator(getNetworkAddress(), provider, username, password, + PasswordAuthenticator auth = new PasswordAuthenticator(getNetworkAddress(), provider, username, password, getApiVersion(), isIgnoreSsl(), getValidationCallback()); auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); return auth; } catch (ArgumentException ex) { - Logger.getLogger(PasswordAuthenticator.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } return null; } - + @Override public void dispose() { @@ -91,7 +93,7 @@ public void dispose() Arrays.fill(password, '0'); disposed = true; } - + @Override protected void finalize() throws Throwable { try { @@ -102,5 +104,5 @@ protected void finalize() throws Throwable { super.finalize(); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java new file mode 100644 index 0000000..38d8741 --- /dev/null +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/authentication/PkceAuthenticator.java @@ -0,0 +1,455 @@ +package com.oneidentity.safeguard.safeguardjava.authentication; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.oneidentity.safeguard.safeguardjava.AgentBasedLoginUtils; +import com.oneidentity.safeguard.safeguardjava.Utils; +import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; +import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; +import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; +import com.oneidentity.safeguard.safeguardjava.restclient.RestClient; +import java.io.IOException; +import java.net.URLEncoder; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * Authenticator that uses PKCE (Proof Key for Code Exchange) OAuth2 flow + * to authenticate against Safeguard without requiring the password grant type. + *

+ * This authenticator programmatically simulates the browser-based OAuth2/PKCE + * flow by directly interacting with the rSTS login controller endpoints. + */ +public class PkceAuthenticator extends AuthenticatorBase { + + private static final Logger logger = LoggerFactory.getLogger(PkceAuthenticator.class); + + // rSTS login controller step numbers (from rSTS Login.js) + private static final String STEP_INIT = "1"; + private static final String STEP_PRIMARY_AUTH = "3"; + private static final String STEP_SECONDARY_INIT = "7"; + private static final String STEP_SECONDARY_AUTH = "5"; + private static final String STEP_GENERATE_CLAIMS = "6"; + + private boolean disposed; + + private final String provider; + private String resolvedProviderId; + private final String username; + private final char[] password; + private final char[] secondaryPassword; + + /** + * Creates a PKCE authenticator for primary authentication only. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name for authentication. + * @param password User password for authentication. + * @param apiVersion Target API version. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @throws ArgumentException If required arguments are null. + */ + public PkceAuthenticator(String networkAddress, String provider, String username, + char[] password, int apiVersion, boolean ignoreSsl, + HostnameVerifier validationCallback) throws ArgumentException { + this(networkAddress, provider, username, password, null, apiVersion, ignoreSsl, validationCallback); + } + + /** + * Creates a PKCE authenticator with support for multi-factor authentication. + * + * @param networkAddress Network address of Safeguard appliance. + * @param provider Safeguard authentication provider name (e.g. local). + * @param username User name for authentication. + * @param password User password for authentication. + * @param secondaryPassword One-time password for MFA (null if not required). + * @param apiVersion Target API version. + * @param ignoreSsl Whether to ignore SSL certificate validation. + * @param validationCallback Optional hostname verifier callback. + * @throws ArgumentException If required arguments are null. + */ + public PkceAuthenticator(String networkAddress, String provider, String username, + char[] password, char[] secondaryPassword, int apiVersion, boolean ignoreSsl, + HostnameVerifier validationCallback) throws ArgumentException { + super(networkAddress, apiVersion, ignoreSsl, validationCallback); + this.provider = provider; + this.username = username; + if (password == null) { + throw new ArgumentException("The password parameter can not be null"); + } + this.password = password.clone(); + this.secondaryPassword = secondaryPassword != null ? secondaryPassword.clone() : null; + } + + @Override + public String getId() { + return "PKCE"; + } + + @Override + protected char[] getRstsTokenInternal() throws ObjectDisposedException, SafeguardForJavaException { + if (disposed) { + throw new ObjectDisposedException("PkceAuthenticator"); + } + + // Resolve the provider to an rSTS provider ID if needed + if (resolvedProviderId == null) { + resolvedProviderId = resolveIdentityProvider(provider); + } + + String csrfToken = AgentBasedLoginUtils.generateCsrfToken(); + String codeVerifier = AgentBasedLoginUtils.oAuthCodeVerifier(); + String codeChallenge = AgentBasedLoginUtils.oAuthCodeChallenge(codeVerifier); + String redirectUri = AgentBasedLoginUtils.REDIRECT_URI; + + CloseableHttpClient httpClient = createPkceHttpClient(getNetworkAddress(), csrfToken); + + try { + String pkceUrl = String.format( + "https://%s/RSTS/UserLogin/LoginController?response_type=code&code_challenge_method=S256&code_challenge=%s&redirect_uri=%s&loginRequestStep=", + getNetworkAddress(), codeChallenge, redirectUri); + + String primaryFormData = String.format( + "directoryComboBox=%s&usernameTextbox=%s&passwordTextbox=%s&csrfTokenTextbox=%s", + URLEncoder.encode(resolvedProviderId, "UTF-8"), + URLEncoder.encode(username, "UTF-8"), + URLEncoder.encode(new String(password), "UTF-8"), + URLEncoder.encode(csrfToken, "UTF-8")); + + // Step 1: Initialize + logger.debug("Calling RSTS for provider initialization"); + rstsFormPost(httpClient, pkceUrl + STEP_INIT, primaryFormData); + + // Step 3: Primary authentication + logger.debug("Calling RSTS for primary authentication"); + String primaryBody = rstsFormPost(httpClient, pkceUrl + STEP_PRIMARY_AUTH, primaryFormData); + + // Handle secondary authentication (MFA) if required + handleSecondaryAuthentication(httpClient, pkceUrl, primaryFormData, primaryBody); + + // Step 6: Generate claims + logger.debug("Calling RSTS for generate claims"); + String claimsBody = rstsFormPost(httpClient, pkceUrl + STEP_GENERATE_CLAIMS, primaryFormData); + + // Extract authorization code from claims response + String authorizationCode = extractAuthorizationCode(claimsBody); + + // Exchange authorization code for RSTS access token + logger.debug("Redeeming RSTS authorization code"); + char[] rstsAccessToken = AgentBasedLoginUtils.postAuthorizationCodeFlow( + getNetworkAddress(), authorizationCode, codeVerifier, redirectUri, + isIgnoreSsl(), getValidationCallback()); + + return rstsAccessToken; + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("PKCE authentication failed", e); + } finally { + try { + httpClient.close(); + } catch (IOException e) { + logger.warn("Failed to close PKCE HTTP client", e); + } + } + } + + private void handleSecondaryAuthentication(CloseableHttpClient httpClient, String pkceUrl, + String primaryFormData, String primaryAuthBody) throws SafeguardForJavaException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode primaryResponse; + try { + primaryResponse = mapper.readTree(primaryAuthBody); + } catch (Exception e) { + return; // Non-JSON response means no secondary auth info + } + + if (primaryResponse == null) { + return; + } + + JsonNode secondaryProviderNode = primaryResponse.get("SecondaryProviderID"); + if (secondaryProviderNode == null || secondaryProviderNode.isNull() + || Utils.isNullOrEmpty(secondaryProviderNode.asText())) { + return; // No MFA required + } + + String secondaryProviderId = secondaryProviderNode.asText(); + logger.debug("Secondary authentication required, provider: {}", secondaryProviderId); + + if (secondaryPassword == null) { + throw new SafeguardForJavaException( + String.format("Multi-factor authentication is required (provider: %s) " + + "but no secondary password was provided.", secondaryProviderId)); + } + + try { + // Step 7: Secondary provider initialization + logger.debug("Calling RSTS for secondary provider initialization"); + String initBody = rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_INIT, primaryFormData); + + // Parse MFA state from secondary init response + String mfaState = ""; + try { + JsonNode initResponse = mapper.readTree(initBody); + if (initResponse != null && initResponse.has("State")) { + mfaState = initResponse.get("State").asText(""); + } + } catch (IOException e) { + // Proceed without state + } + + // Step 5: Submit secondary password (OTP) + String mfaFormData = primaryFormData + + "&secondaryLoginTextbox=" + URLEncoder.encode(new String(secondaryPassword), "UTF-8") + + "&secondaryAuthenticationStateTextbox=" + URLEncoder.encode(mfaState, "UTF-8"); + + logger.debug("Calling RSTS for secondary authentication"); + rstsFormPost(httpClient, pkceUrl + STEP_SECONDARY_AUTH, mfaFormData); + + logger.debug("Secondary authentication completed successfully"); + } catch (SafeguardForJavaException e) { + throw e; + } catch (IOException e) { + throw new SafeguardForJavaException("Secondary authentication failed", e); + } + } + + private String extractAuthorizationCode(String response) throws SafeguardForJavaException { + ObjectMapper mapper = new ObjectMapper(); + try { + JsonNode jsonObject = mapper.readTree(response); + JsonNode relyingPartyUrlNode = jsonObject.get("RelyingPartyUrl"); + + if (relyingPartyUrlNode == null || relyingPartyUrlNode.isNull() + || Utils.isNullOrEmpty(relyingPartyUrlNode.asText())) { + throw new SafeguardForJavaException( + "rSTS response did not contain a RelyingPartyUrl. " + + "The authentication process may be incomplete."); + } + + String relyingPartyUrl = relyingPartyUrlNode.asText(); + + // Parse the authorization code from the query string. + // The URL may be a URN (e.g., urn:InstalledApplication?code=xxx) + // or a standard HTTP URL, so we parse the query part manually. + int queryStart = relyingPartyUrl.indexOf('?'); + if (queryStart >= 0) { + String query = relyingPartyUrl.substring(queryStart + 1); + for (String param : query.split("&")) { + String[] pair = param.split("=", 2); + if (pair.length == 2 && "code".equals(pair[0])) { + return java.net.URLDecoder.decode(pair[1], "UTF-8"); + } + } + } + + throw new SafeguardForJavaException("rSTS response did not contain an authorization code"); + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("Failed to parse authorization code from rSTS response", e); + } + } + + private String resolveIdentityProvider(String provider) throws SafeguardForJavaException { + // If provider is null/empty or "local", use the local provider ID + if (Utils.isNullOrEmpty(provider) || provider.equalsIgnoreCase("local")) { + return "local"; + } + + // Use the existing resolveProviderToScope to find the matching provider, + // but we need the RstsProviderId, not the scope. For PKCE we resolve it + // by querying AuthenticationProviders and matching. + try { + String coreUrl = String.format("https://%s/service/core/v%d", getNetworkAddress(), getApiVersion()); + RestClient client = new RestClient(coreUrl, isIgnoreSsl(), getValidationCallback()); + + java.util.Map headers = new java.util.HashMap<>(); + headers.put(HttpHeaders.ACCEPT, "application/json"); + + CloseableHttpResponse response = client.execGET("AuthenticationProviders", null, headers, null); + + if (response == null) { + throw new SafeguardForJavaException("Unable to connect to Safeguard to resolve identity provider"); + } + + String reply = Utils.getResponse(response); + if (!Utils.isSuccessful(response.getCode())) { + throw new SafeguardForJavaException( + "Error requesting authentication providers, Error: " + + String.format("%d %s", response.getCode(), reply)); + } + + ObjectMapper mapper = new ObjectMapper(); + JsonNode providers = mapper.readTree(reply); + + if (providers != null && providers.isArray()) { + for (JsonNode p : providers) { + String rstsProviderId = p.has("RstsProviderId") ? p.get("RstsProviderId").asText() : ""; + String name = p.has("Name") ? p.get("Name").asText() : ""; + + if (rstsProviderId.equalsIgnoreCase(provider) || name.equalsIgnoreCase(provider)) { + return rstsProviderId; + } + } + // Broad match + for (JsonNode p : providers) { + String rstsProviderId = p.has("RstsProviderId") ? p.get("RstsProviderId").asText() : ""; + if (rstsProviderId.toLowerCase().contains(provider.toLowerCase())) { + return rstsProviderId; + } + } + } + + throw new SafeguardForJavaException( + String.format("Unable to find identity provider matching '%s'", provider)); + } catch (SafeguardForJavaException e) { + throw e; + } catch (Exception e) { + throw new SafeguardForJavaException("Unable to resolve identity provider", e); + } + } + + /** + * Posts form data to an rSTS login controller URL and returns the response body. + */ + private String rstsFormPost(CloseableHttpClient httpClient, String url, String formData) + throws SafeguardForJavaException { + try { + HttpPost post = new HttpPost(url); + post.setHeader(HttpHeaders.ACCEPT, "application/json"); + post.setEntity(new StringEntity(formData, ContentType.APPLICATION_FORM_URLENCODED)); + + CloseableHttpResponse response = httpClient.execute(post); + String body = ""; + if (response.getEntity() != null) { + body = EntityUtils.toString(response.getEntity()); + } + + int statusCode = response.getCode(); + // rSTS returns 203 for some secondary auth states — treat as non-error + if (statusCode >= 400) { + String errorMessage = !Utils.isNullOrEmpty(body) ? body.trim() : String.valueOf(statusCode); + throw new SafeguardForJavaException( + String.format("rSTS authentication error: %s (HTTP %d)", errorMessage, statusCode)); + } + + return body; + } catch (SafeguardForJavaException e) { + throw e; + } catch (ParseException | IOException e) { + throw new SafeguardForJavaException("Failed to communicate with rSTS login controller", e); + } + } + + private CloseableHttpClient createPkceHttpClient(String appliance, String csrfToken) { + try { + SSLConnectionSocketFactory sslsf; + if (isIgnoreSsl()) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] certs, String authType) { } + public void checkServerTrusted(X509Certificate[] certs, String authType) { } + }}, new java.security.SecureRandom()); + sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + } else if (getValidationCallback() != null) { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + public void checkClientTrusted(X509Certificate[] certs, String authType) { } + public void checkServerTrusted(X509Certificate[] certs, String authType) { } + }}, new java.security.SecureRandom()); + sslsf = new SSLConnectionSocketFactory(sslContext, getValidationCallback()); + } else { + sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); + } + + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("https", sslsf) + .build(); + BasicHttpClientConnectionManager connectionManager = + new BasicHttpClientConnectionManager(socketFactoryRegistry); + + // Set up cookie store with CSRF token + BasicCookieStore cookieStore = new BasicCookieStore(); + BasicClientCookie csrfCookie = new BasicClientCookie("CsrfToken", csrfToken); + csrfCookie.setDomain(appliance); + csrfCookie.setPath("/RSTS"); + cookieStore.addCookie(csrfCookie); + + return HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultCookieStore(cookieStore) + .build(); + } catch (Exception e) { + throw new RuntimeException("Failed to create PKCE HTTP client", e); + } + } + + @Override + public Object cloneObject() throws SafeguardForJavaException { + try { + PkceAuthenticator auth = new PkceAuthenticator(getNetworkAddress(), provider, username, + password, secondaryPassword, getApiVersion(), isIgnoreSsl(), getValidationCallback()); + auth.accessToken = this.accessToken == null ? null : this.accessToken.clone(); + return auth; + } catch (ArgumentException ex) { + logger.error("Exception occurred", ex); + } + return null; + } + + @Override + public void dispose() { + super.dispose(); + if (password != null) { + Arrays.fill(password, '0'); + } + if (secondaryPassword != null) { + Arrays.fill(secondaryPassword, '0'); + } + disposed = true; + } + + @Override + protected void finalize() throws Throwable { + try { + if (password != null) { + Arrays.fill(password, '0'); + } + if (secondaryPassword != null) { + Arrays.fill(secondaryPassword, '0'); + } + } finally { + disposed = true; + super.finalize(); + } + } +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java index 81c572e..6c43b45 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARegistration.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class A2ARegistration { - + @JsonProperty("Id") private Integer id; @JsonProperty("AppName") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java index 9a03cba..a62ad96 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccount.java @@ -10,7 +10,6 @@ */ @JsonIgnoreProperties public class A2ARetrievableAccount implements IA2ARetrievableAccount { - private boolean disposed; private String applicationName; private String description; @@ -99,7 +98,7 @@ public String getAssetNetworkAddress() { public void setAssetNetworkAddress(String assetNetworkAddress) { this.assetNetworkAddress = assetNetworkAddress; } - + @Override public int getAccountId() { return accountId; @@ -135,7 +134,7 @@ public String getAccountType() { public void setAccountType(String accountType) { this.accountType = accountType; } - + @Override public String getAssetDescription() { return assetDescription; @@ -159,10 +158,9 @@ public void dispose() if (apiKey != null) { Arrays.fill(apiKey, '0'); } - disposed = true; apiKey = null; } - + @Override protected void finalize() throws Throwable { try { @@ -170,7 +168,6 @@ protected void finalize() throws Throwable { Arrays.fill(apiKey, '0'); } } finally { - disposed = true; super.finalize(); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java index 375e66d..0728b72 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/A2ARetrievableAccountInternal.java @@ -62,7 +62,7 @@ public int getAssetId() { public void setSystemId(int systemId) { this.assetId = systemId; } - + public void setAssetId(int assetId) { this.assetId = assetId; } @@ -74,7 +74,7 @@ public String getAssetName() { public void setSystemName(String systemName) { this.assetName = systemName; } - + public void setAssetName(String assetName) { this.assetName = assetName; } @@ -118,7 +118,7 @@ public String getAssetDescription() { public void setSystemDescription(String systemDescription) { this.assetDescription = systemDescription; } - + public void setAssetDescription(String assetDescription) { this.assetDescription = assetDescription; } @@ -130,7 +130,7 @@ public String getAccountDescription() { public void setAccountDescription(String accountDescription) { this.accountDescription = accountDescription; } - + public String getAssetNetworkAddress() { return assetNetworkAddress; } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java index 43ddf4a..7f67859 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/AccessTokenBody.java @@ -3,7 +3,7 @@ import com.oneidentity.safeguard.safeguardjava.Utils; public class AccessTokenBody implements JsonObject { - + private final char[] stsAccessToken; public AccessTokenBody(char[] stsAccessToken ) { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java index fb0383d..3619579 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecret.java @@ -10,7 +10,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class ApiKeySecret extends ApiKeySecretBase implements IApiKeySecret { - + @JsonProperty("ClientSecret") private char[] clientSecret; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java index 7303500..a9b4389 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretBase.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) class ApiKeySecretBase { - + @JsonProperty("Id") private Integer id; @JsonProperty("Name") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java index f2de700..e0fbf57 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/ApiKeySecretInternal.java @@ -9,7 +9,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class ApiKeySecretInternal extends ApiKeySecretBase { - + @JsonProperty("ClientSecret") private String clientSecret; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java index 223ec68..fa598a7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequest.java @@ -10,7 +10,7 @@ public class BrokeredAccessRequest implements JsonObject, IBrokeredAccessRequest { private int version; - + private BrokeredAccessRequestType AccessType; // converted by AccessRequestTypeConverter private String ForUserName; private String ForUserIdentityProvider; // renamed from ForProvider @@ -35,7 +35,7 @@ public class BrokeredAccessRequest implements JsonObject, IBrokeredAccessRequest public void setVersion(int apiVersion) { version = apiVersion; } - + /** * Get the type of access request to create. * @return BrokeredAccessRequestType @@ -353,7 +353,7 @@ public Long getRequestedDurationMinutes() { public void setRequestedDurationMinutes(Long RequestedDurationMinutes) { this.RequestedDurationMinutes = RequestedDurationMinutes; } - + @Override public String toJson() { return new StringBuffer("{") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java index 27109ee..3eceb73 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/BrokeredAccessRequestType.java @@ -17,17 +17,17 @@ public enum BrokeredAccessRequestType * Access request is for a remote desktop session. */ Rdp ("RemoteDesktop"); - + private final String name; - + private BrokeredAccessRequestType(String s) { name = s; } - + public boolean equalsName (String otherName) { return name.equals(otherName); } - + @Override public String toString() { return this.name; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java index fa59beb..a5d028d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/FullResponse.java @@ -2,14 +2,14 @@ import java.util.Arrays; import java.util.List; -import org.apache.http.Header; +import org.apache.hc.core5.http.Header; /** * A simple class for returning extended information from a Safeguard API method call. */ public class FullResponse { - + private int statusCode; private Header[] headers; private String body; @@ -43,5 +43,5 @@ public String getBody() { public void setBody(String body) { this.body = body; } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java index 94bedbc..7e69134 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JoinRequest.java @@ -4,18 +4,20 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JoinRequest implements JsonObject { + private static final Logger logger = LoggerFactory.getLogger(JoinRequest.class); + private String spp; private char[] spp_api_token; private String spp_cert_chain; public JoinRequest() { } - + public String getSpp() { return spp; } @@ -39,16 +41,16 @@ public String getSpp_cert_chain() { public void setSpp_cert_chain(String spp_cert_chain) { this.spp_cert_chain = spp_cert_chain; } - + @Override public String toJson() throws SafeguardForJavaException { ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); try { return ow.writeValueAsString(this); } catch (JsonProcessingException ex) { - Logger.getLogger(JoinRequest.class.getName()).log(Level.FINEST, null, ex); + logger.trace("Exception occurred", ex); throw new SafeguardForJavaException("Failed to convert request to json", ex); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java index cda59df..11a1bd0 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/JsonBody.java @@ -1,13 +1,13 @@ package com.oneidentity.safeguard.safeguardjava.data; public class JsonBody implements JsonObject { - + private final String body; - + public JsonBody(String body) { this.body = body; } - + @Override public String toJson() { return body; diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java index 26c11f7..db5f0a3 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/KeyFormat.java @@ -17,4 +17,4 @@ public enum KeyFormat * PuttY format for use with PuTTY tools */ Putty -} \ No newline at end of file +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java index 8afaf16..d00ce77 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/OauthBody.java @@ -3,7 +3,7 @@ import com.oneidentity.safeguard.safeguardjava.Utils; public class OauthBody implements JsonObject { - + private String grantType; private String username; private char[] password; @@ -17,7 +17,7 @@ public OauthBody(String grantType, String username, char[] password, String scop this.scope = scope; this.isPassword = true; } - + public OauthBody(String grantType, String scope ) { this.grantType = grantType; this.username = null; @@ -26,7 +26,7 @@ public OauthBody(String grantType, String scope ) { this.isPassword = false; } - + public String getGrantType() { return grantType; } @@ -58,7 +58,7 @@ public String getScope() { public void setScope(String scope) { this.scope = scope; } - + @Override public String toJson() { if (isPassword) { @@ -75,5 +75,5 @@ public String toJson() { .append(Utils.toJsonString("scope", this.scope, true)) .append("}").toString(); } - } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java index a0317d2..484ccd7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SafeguardEventListenerState.java @@ -2,7 +2,7 @@ /** * Connection state of the Safeguard event listener. - */ + */ public enum SafeguardEventListenerState { /** diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java index 54e5424..431ef76 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/Service.java @@ -8,26 +8,26 @@ public enum Service { * The core service contains all general cluster-wide Safeguard operations. */ Core, - + /** * The appliance service contains appliance-specific Safeguard operations. */ Appliance, - + /** * The notification service contains unauthenticated Safeguard operations. */ Notification, - + /** * The a2a service contains application integration Safeguard operations. It is called via the Safeguard.A2A class. */ A2A, - + /** * The Management service contains unauthenticated endpoints for disaster-recovery and support operations. On hardware * it is bound to the MGMT network interface. For on-prem VM it is unavailable except through the Kiosk app. On cloud * VM it is listening on port 9337 and should be firewalled appropriately to restrict access. */ - Management + Management } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java index c855094..b532d3b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/SshKey.java @@ -8,7 +8,7 @@ */ @JsonIgnoreProperties(ignoreUnknown = true) public class SshKey { - + @JsonProperty("Passphrase") private String passphrase; @JsonProperty("PrivateKey") diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java index 6d909a1..97c3f28 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/data/TransferProgress.java @@ -19,7 +19,7 @@ public long getBytesTotal() { public void setBytesTotal(long BytesTotal) { this.BytesTotal = BytesTotal; } - + public int getPercentComplete() { return BytesTotal == 0 ? 0 : (int)((double)BytesTransferred / BytesTotal * 100); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java index efef237..d0ee33d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRegistry.java @@ -1,26 +1,24 @@ package com.oneidentity.safeguard.safeguardjava.event; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class EventHandlerRegistry { private static final Map> delegateRegistry = new HashMap<>(); - private final Logger logger = Logger.getLogger(getClass().getName()); - + private static final Logger logger = LoggerFactory.getLogger(EventHandlerRegistry.class); + private void handleEvent(String eventName, JsonElement eventBody) { if (!delegateRegistry.containsKey(eventName)) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.FINEST, - String.format("No handlers registered for event %s", eventName)); + logger.trace("No handlers registered for event {}", eventName); return; } @@ -29,13 +27,11 @@ private void handleEvent(String eventName, JsonElement eventBody) List handlers = delegateRegistry.get(eventName); for (ISafeguardEventHandler handler : handlers) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.INFO, - String.format("Calling handler for event %s", eventName)); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Event %s has body %s", eventName, eventBody)); + logger.info("Calling handler for event {}", eventName); + logger.warn("Event {} has body {}", eventName, eventBody); final EventHandlerRunnable handlerRunnable = new EventHandlerRunnable(handler, eventName, eventBody.toString()); final EventHandlerThread eventHandlerThread = new EventHandlerThread(handlerRunnable) { - + }; eventHandlerThread.start(); } @@ -53,7 +49,7 @@ private Map parseEvents(JsonElement eventObject) { try { Integer.parseInt(name); name = ((JsonObject)body).get("EventName").getAsString(); - } catch (Exception e) { + } catch (Exception e) { } } events.put(name, body); @@ -61,8 +57,7 @@ private Map parseEvents(JsonElement eventObject) { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Unable to parse event object %s", eventObject.toString())); + logger.warn("Unable to parse event object {}", eventObject.toString()); return null; } } @@ -75,8 +70,7 @@ public void handleEvent(JsonElement eventObject) for (Map.Entry eventInfo : events.entrySet()) { if (eventInfo.getKey() == null) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Found null event with body %s", eventInfo.getValue())); + logger.warn("Found null event with body {}", eventInfo.getValue()); continue; } handleEvent(eventInfo.getKey(), eventInfo.getValue()); @@ -88,9 +82,8 @@ public void registerEventHandler(String eventName, ISafeguardEventHandler handle if (!delegateRegistry.containsKey(eventName)) { delegateRegistry.put(eventName, new ArrayList<>()); } - + delegateRegistry.get(eventName).add(handler); - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - String.format("Registered a handler for event %s", eventName)); + logger.warn("Registered a handler for event {}", eventName); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java index 01644d8..5cca70d 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerRunnable.java @@ -1,14 +1,16 @@ package com.oneidentity.safeguard.safeguardjava.event; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class EventHandlerRunnable implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(EventHandlerRunnable.class); + private final ISafeguardEventHandler handler; private final String eventName; private final String eventBody; - + EventHandlerRunnable(ISafeguardEventHandler handler, String eventName, String eventBody) { this.handler = handler; this.eventName = eventName; @@ -23,8 +25,7 @@ public void run() { } catch (Exception ex) { - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, - "An error occured while calling onEventReceived"); + logger.warn("An error occured while calling onEventReceived"); } } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java index 0e7e522..6cb0ba7 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/EventHandlerThread.java @@ -1,9 +1,9 @@ package com.oneidentity.safeguard.safeguardjava.event; abstract class EventHandlerThread extends Thread { - + public EventHandlerThread(Runnable eventHandlerRunnable) { super(eventHandlerRunnable); } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java index c0a0153..79f3eb4 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventHandler.java @@ -3,11 +3,11 @@ /** * A callback that will be called when a given event occurs in Safeguard. The callback will * receive the event name and JSON data representing the event. - */ + */ public interface ISafeguardEventHandler { /** * Handles an incoming event - * + * * @param eventName Event name. * @param eventBody Event body. */ diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java index 9f64766..f81171c 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListener.java @@ -4,7 +4,7 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardEventListenerDisconnectedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -/** +/** * This is an event listener interface that will allow you to be notified each time something * changes on Safeguard. The events that you are notified for depend on the role and event * registrations of the authenticated user. Safeguard event listeners use SignalR to make @@ -15,11 +15,11 @@ public interface ISafeguardEventListener /** * Register an event handler to be called each time the specified event occurs. Multiple * handlers may be registered for each event. - * + * * @param eventName Name of the event. * @param handler Callback method. * @throws ObjectDisposedException Object has already been disposed - */ + */ void registerEventHandler(String eventName, ISafeguardEventHandler handler) throws ObjectDisposedException; /** @@ -27,9 +27,9 @@ public interface ISafeguardEventListener * state changes of the event listener. * * @param eventListenerStateCallback Callback method. - */ + */ void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback eventListenerStateCallback); - + /** * Start listening for Safeguard events in a background thread. * @throws ObjectDisposedException Object has already been disposed @@ -40,7 +40,7 @@ public interface ISafeguardEventListener /** * Stop listening for Safeguard events in a background thread. - * + * * @throws ObjectDisposedException Object has already been disposed * @throws SafeguardForJavaException General Safeguard for Java exception */ @@ -48,15 +48,15 @@ public interface ISafeguardEventListener /** * Indicates whether the SignalR connection has completed start up. - * + * * @return boolean flag */ boolean isStarted(); /** * Disposes of the connection. - * - */ + * + */ void dispose(); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java index e95d39b..8907626 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/ISafeguardEventListenerStateCallback.java @@ -4,11 +4,11 @@ /** * A callback that will be called whenever the event listener connection state Changes. - */ + */ public interface ISafeguardEventListenerStateCallback { /** * Handles an incoming event listener connection change. - * + * * @param eventListenerState New connection state of the event listener. */ void onEventListenerStateChange(SafeguardEventListenerState eventListenerState); diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java index a9be20b..5f95cbb 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardA2AEventListener.java @@ -7,8 +7,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class DefaultSafeguardEventHandler implements ISafeguardEventHandler { @@ -19,6 +19,8 @@ public void onEventReceived(String eventName, String eventBody) { public class PersistentSafeguardA2AEventListener extends PersistentSafeguardEventListenerBase { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardA2AEventListener.class); + private boolean disposed; private final ISafeguardA2AContext a2AContext; @@ -36,10 +38,10 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, char registerEventHandler("AssetAccountPasswordUpdated", handler); registerEventHandler("AssetAccountSshKeyUpdated", handler); registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); + logger.trace("Persistent A2A event listener successfully created."); } - public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List apiKeys, ISafeguardEventHandler handler) + public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List apiKeys, ISafeguardEventHandler handler) throws ArgumentException, ObjectDisposedException { this.a2AContext = a2AContext; if (apiKeys == null) { @@ -56,9 +58,9 @@ public PersistentSafeguardA2AEventListener(ISafeguardA2AContext a2AContext, List registerEventHandler("AssetAccountPasswordUpdated", handler); registerEventHandler("AssetAccountSshKeyUpdated", handler); registerEventHandler("AccountApiKeySecretUpdated", handler); - Logger.getLogger(PersistentSafeguardA2AEventListener.class.getName()).log(Level.FINEST, "Persistent A2A event listener successfully created."); + logger.trace("Persistent A2A event listener successfully created."); } - + @Override public SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, ArgumentException { @@ -81,7 +83,7 @@ public void dispose() a2AContext.dispose(); disposed = true; } - + @Override protected void finalize() throws Throwable { try { @@ -97,5 +99,5 @@ protected void finalize() throws Throwable { super.finalize(); } } - + } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java index ca094b6..3dec584 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListener.java @@ -4,28 +4,30 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PersistentSafeguardEventListener extends PersistentSafeguardEventListenerBase { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardEventListener.class); + private boolean disposed; private final ISafeguardConnection connection; public PersistentSafeguardEventListener(ISafeguardConnection connection) { this.connection = connection; - Logger.getLogger(PersistentSafeguardEventListener.class.getName()).log(Level.FINEST, "Persistent event listener successfully created."); + logger.trace("Persistent event listener successfully created."); } @Override public SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, SafeguardForJavaException, ArgumentException { - + if (disposed) { throw new ObjectDisposedException("SafeguardEventListener"); } - + if (connection.getAccessTokenLifetimeRemaining() == 0) { connection.refreshAccessToken(); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java index e2cd6e8..9ee9ef2 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/PersistentSafeguardEventListenerBase.java @@ -3,11 +3,13 @@ import com.oneidentity.safeguard.safeguardjava.exceptions.ArgumentException; import com.oneidentity.safeguard.safeguardjava.exceptions.ObjectDisposedException; import com.oneidentity.safeguard.safeguardjava.exceptions.SafeguardForJavaException; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class PersistentSafeguardEventListenerBase implements ISafeguardEventListener { + private static final Logger logger = LoggerFactory.getLogger(PersistentSafeguardEventListenerBase.class); + private boolean disposed; private SafeguardEventListener eventListener; @@ -34,7 +36,7 @@ public void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback e { this.eventListenerStateCallback = eventListenerStateCallback; } - + protected abstract SafeguardEventListener reconnectEventListener() throws ObjectDisposedException, SafeguardForJavaException, ArgumentException; class PersistentReconnectAndStartHandler implements IDisconnectHandler { @@ -59,8 +61,7 @@ public void run() { if (eventListener != null) { eventListener.dispose(); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.FINEST, - "Attempting to connect and start internal event listener."); + logger.trace("Attempting to connect and start internal event listener."); eventListener = reconnectEventListener(); eventListener.setEventHandlerRegistry(eventHandlerRegistry); eventListener.SetEventListenerStateCallback(eventListenerStateCallback); @@ -68,10 +69,8 @@ public void run() { eventListener.setDisconnectHandler(new PersistentReconnectAndStartHandler()); break; } catch (ObjectDisposedException | SafeguardForJavaException | ArgumentException ex) { - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.WARNING, - "Internal event listener connection error (see debug for more information), sleeping for 5 seconds..."); - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.FINEST, - "Internal event listener connection error."); + logger.warn("Internal event listener connection error (see debug for more information), sleeping for 5 seconds..."); + logger.trace("Internal event listener connection error."); try { Thread.sleep(5000); } catch (InterruptedException ex1) { @@ -81,15 +80,15 @@ public void run() { } } }; - - + + try { this.reconnectThread.start(); this.reconnectThread.join(); } catch (InterruptedException ex1) { isCancellationRequested = true; } - + this.reconnectThread = null; } @@ -98,7 +97,7 @@ public void start() throws ObjectDisposedException { if (disposed) { throw new ObjectDisposedException("PersistentSafeguardEventListener"); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.INFO, "Internal event listener requested to start."); + logger.info("Internal event listener requested to start."); persistentReconnectAndStart(); } @@ -107,7 +106,7 @@ public void stop() throws ObjectDisposedException, SafeguardForJavaException { if (disposed) { throw new ObjectDisposedException("PersistentSafeguardEventListener"); } - Logger.getLogger(PersistentSafeguardEventListenerBase.class.getName()).log(Level.INFO, "Internal event listener requested to stop."); + logger.info("Internal event listener requested to stop."); this.isCancellationRequested = true; if (eventListener != null) { eventListener.stop(); @@ -118,7 +117,7 @@ public void stop() throws ObjectDisposedException, SafeguardForJavaException { public boolean isStarted() { return this.eventListener == null ? false : this.eventListener.isStarted(); } - + @Override public void dispose() { if (this.eventListener != null) { diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java index a5f158d..2ca3463 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/event/SafeguardEventListener.java @@ -24,8 +24,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -47,6 +47,8 @@ public void func() throws SafeguardEventListenerDisconnectedException { public class SafeguardEventListener implements ISafeguardEventListener, AutoCloseable { + private static final Logger logger = LoggerFactory.getLogger(SafeguardEventListener.class); + private boolean disposed; private final String eventUrl; @@ -83,7 +85,7 @@ public SafeguardEventListener(String eventUrl, char[] accessToken, boolean ignor this.accessToken = accessToken.clone(); } - public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, + public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, String certificateAlias, char[] apiKey, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKey == null) @@ -92,7 +94,7 @@ public SafeguardEventListener(String eventUrl, String clientCertificatePath, cha this.clientCertificate = new CertificateContext(certificateAlias, clientCertificatePath, null, certificatePassword); } - public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, + public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, char[] apiKey, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKey == null) @@ -101,7 +103,7 @@ public SafeguardEventListener(String eventUrl, CertificateContext clientCertific this.apiKey = apiKey.clone(); } - public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, + public SafeguardEventListener(String eventUrl, String clientCertificatePath, char[] certificatePassword, String certificateAlias, List apiKeys, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); if (apiKeys == null) @@ -115,7 +117,7 @@ public SafeguardEventListener(String eventUrl, String clientCertificatePath, cha throw new ArgumentException("The apiKeys parameter must include at least one item"); } - public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, + public SafeguardEventListener(String eventUrl, CertificateContext clientCertificate, List apiKeys, boolean ignoreSsl, HostnameVerifier validationCallback) throws ArgumentException { this(eventUrl, ignoreSsl, validationCallback); @@ -152,7 +154,7 @@ public void registerEventHandler(String eventName, ISafeguardEventHandler handle } eventHandlerRegistry.registerEventHandler(eventName, handler); } - + @Override public void SetEventListenerStateCallback(ISafeguardEventListenerStateCallback eventListenerStateCallback) { @@ -182,11 +184,11 @@ public void start() throws ObjectDisposedException, SafeguardForJavaException, S public void invoke(Exception exception){ try { if(exception != null) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.WARNING, "SignalR error detected!", exception); + logger.warn("SignalR error detected!", exception); } handleDisconnect(); } catch (SafeguardEventListenerDisconnectedException ex) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } } }); @@ -261,11 +263,11 @@ private void handleDisconnect() throws SafeguardEventListenerDisconnectedExcepti if(!this.isStarted()) { return; } - Logger.getLogger(EventHandlerRegistry.class.getName()).log(Level.WARNING, "SignalR disconnect detected, calling handler..."); + logger.warn("SignalR disconnect detected, calling handler..."); CallEventListenerStateCallback(SafeguardEventListenerState.Disconnected); disconnectHandler.func(); } - + private void CallEventListenerStateCallback(SafeguardEventListenerState newState) { if (eventListenerStateCallback != null) { @@ -301,15 +303,20 @@ private HubConnection CreateConnection(String eventUrl) throws SafeguardForJavaE if (apiKey != null) authKey = new String(apiKey); else if (apiKeys != null) { - for (char[] key : apiKeys) - authKey += new String(key) + " "; - authKey = authKey.trim(); + StringBuilder authKeyBuilder = new StringBuilder(); + for (char[] key : apiKeys) { + if (authKeyBuilder.length() > 0) { + authKeyBuilder.append(' '); + } + authKeyBuilder.append(key); + } + authKey = authKeyBuilder.toString(); } if (authKey.isEmpty()) throw new SafeguardForJavaException("No API keys found in the authorization header"); - builder.withHeader("Authorization", String.format("A2A %s", authKey)); + builder.withHeader("Authorization", String.format("A2A %s", authKey)); } builder.setHttpClientBuilderCallback(new Action1(){ @@ -330,30 +337,29 @@ private void ConfigureHttpClientBuilder(Builder builder) } KeyManager[] km = null; - if(clientCertificate != null && + if(clientCertificate != null && (clientCertificate.getCertificateData() != null || clientCertificate.getCertificatePath() != null)){ - + // If we have a client certificate, set it into the KeyStore/KeyManager try{ KeyStore keyStore = KeyStore.getInstance("PKCS12"); - InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) - : new ByteArrayInputStream(clientCertificate.getCertificateData()); - - keyStore.load(inputStream, clientCertificate.getCertificatePassword()); + try (InputStream inputStream = clientCertificate.getCertificatePath() != null ? new FileInputStream(clientCertificate.getCertificatePath()) + : new ByteArrayInputStream(clientCertificate.getCertificateData())) { + keyStore.load(inputStream, clientCertificate.getCertificatePassword()); + } KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, clientCertificate.getCertificatePassword()); km = keyManagerFactory.getKeyManagers(); // when we send a client certificate singlar resets the stream - // and requires 1.1. No idea why. okhttp3 doesn't handle this reset + // and requires 1.1. No idea why. okhttp3 doesn't handle this reset // automatically so the only option is to restrict the client to 1.1 builder.protocols(Arrays.asList(Protocol.HTTP_1_1)); } catch(Exception error) { - String msg = String.format("Error setting client authentication certificate: %s", error.getMessage()); - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, msg); + logger.error("Error setting client authentication certificate: {}", error.getMessage()); } } @@ -373,23 +379,23 @@ private void ConfigureHttpClientBuilder(Builder builder) if (tm.length != 1 || !(tm[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(tm)); } - x509tm = (X509TrustManager) tm[0]; + x509tm = (X509TrustManager) tm[0]; } - // Configure the SSL Context according to options and set the + // Configure the SSL Context according to options and set the // OkHttpClient builder SSL socket factory SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(km, tm, null); - builder.sslSocketFactory(sslContext.getSocketFactory(), x509tm); + builder.sslSocketFactory(sslContext.getSocketFactory(), x509tm); } catch(NoSuchAlgorithmException ex) { - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } catch(KeyManagementException ex){ - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } catch(KeyStoreException ex){ - Logger.getLogger(SafeguardEventListener.class.getName()).log(Level.SEVERE, ex.getMessage()); + logger.error(ex.getMessage()); } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java index c9ab038..f343d51 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/ArgumentException.java @@ -5,7 +5,7 @@ public class ArgumentException extends Exception { public ArgumentException(String msg) { super(msg); } - + public ArgumentException(String msg, Exception cause) { super(msg, cause); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java index 81c6ef4..558917b 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/exceptions/SafeguardForJavaException.java @@ -5,7 +5,7 @@ public class SafeguardForJavaException extends Exception { public SafeguardForJavaException(String msg) { super(msg); } - + public SafeguardForJavaException(String msg, Throwable cause) { super(msg, cause); } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java index e9ee59a..10b0e51 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/ByteArrayEntity.java @@ -2,24 +2,81 @@ import com.oneidentity.safeguard.safeguardjava.IProgressCallback; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.util.List; +import java.util.Set; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; + +public class ByteArrayEntity implements HttpEntity { -public class ByteArrayEntity extends org.apache.http.entity.ByteArrayEntity { - private OutputStreamProgress outstream; private final IProgressCallback progressCallback; private final long totalBytes; + private final org.apache.hc.core5.http.io.entity.ByteArrayEntity delegate; public ByteArrayEntity(byte[] b, IProgressCallback progressCallback) { - super(b); + this.delegate = new org.apache.hc.core5.http.io.entity.ByteArrayEntity(b, null); this.progressCallback = progressCallback; this.totalBytes = b.length; } + @Override + public boolean isRepeatable() { + return delegate.isRepeatable(); + } + + @Override + public long getContentLength() { + return delegate.getContentLength(); + } + + @Override + public String getContentType() { + return delegate.getContentType(); + } + + @Override + public String getContentEncoding() { + return delegate.getContentEncoding(); + } + + @Override + public InputStream getContent() throws IOException { + return delegate.getContent(); + } + @Override public void writeTo(OutputStream outstream) throws IOException { this.outstream = new OutputStreamProgress(outstream, this.progressCallback, totalBytes); - super.writeTo(this.outstream); + delegate.writeTo(this.outstream); + } + + @Override + public boolean isStreaming() { + return delegate.isStreaming(); + } + + @Override + public boolean isChunked() { + return delegate.isChunked(); + } + + @Override + public Set getTrailerNames() { + return delegate.getTrailerNames(); + } + + @Override + public Supplier> getTrailers() { + return delegate.getTrailers(); + } + + @Override + public void close() throws IOException { + delegate.close(); } public int getProgress() { @@ -27,10 +84,10 @@ public int getProgress() { return 0; } long contentLength = getContentLength(); - if (contentLength <= 0) { // Prevent division by zero and negative values + if (contentLength <= 0) { return 0; } long writtenLength = outstream.getWrittenLength(); return (int) (100*writtenLength/contentLength); - } + } } diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java index ebb0989..b51622f 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/OutputStreamProgress.java @@ -4,6 +4,7 @@ import com.oneidentity.safeguard.safeguardjava.data.TransferProgress; import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicLong; public class OutputStreamProgress extends OutputStream { @@ -11,27 +12,27 @@ public class OutputStreamProgress extends OutputStream { private final IProgressCallback progressCallback; private final TransferProgress transferProgress = new TransferProgress(); private int lastSentPercent = 5; - private volatile long bytesWritten=0; + private final AtomicLong bytesWritten = new AtomicLong(0); public OutputStreamProgress(OutputStream outstream, IProgressCallback progressCallback, long totalBytes) { this.outstream = outstream; this.progressCallback = progressCallback; this.transferProgress.setBytesTotal(totalBytes); - this.transferProgress.setBytesTransferred(bytesWritten); + this.transferProgress.setBytesTransferred(0); } private void sendProgress() { - transferProgress.setBytesTransferred(bytesWritten); + transferProgress.setBytesTransferred(bytesWritten.get()); if (transferProgress.getPercentComplete() >= lastSentPercent) { lastSentPercent += 5; progressCallback.checkProgress(transferProgress); } } - + @Override public void write(int b) throws IOException { outstream.write(b); - bytesWritten++; + bytesWritten.incrementAndGet(); if (progressCallback != null) { sendProgress(); } @@ -40,7 +41,7 @@ public void write(int b) throws IOException { @Override public void write(byte[] b) throws IOException { outstream.write(b); - bytesWritten += b.length; + bytesWritten.addAndGet(b.length); if (progressCallback != null) { sendProgress(); } @@ -49,7 +50,7 @@ public void write(byte[] b) throws IOException { @Override public void write(byte[] b, int off, int len) throws IOException { outstream.write(b, off, len); - bytesWritten += len; + bytesWritten.addAndGet(len); if (progressCallback != null) { sendProgress(); } @@ -59,13 +60,13 @@ public void write(byte[] b, int off, int len) throws IOException { public void flush() throws IOException { outstream.flush(); } - + @Override public void close() throws IOException { outstream.close(); } public long getWrittenLength() { - return bytesWritten; + return bytesWritten.get(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java index d182af5..348df39 100644 --- a/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java +++ b/src/main/java/com/oneidentity/safeguard/safeguardjava/restclient/RestClient.java @@ -31,10 +31,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -44,34 +42,37 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.CookieSpecs; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.RequestBuilder; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.mime.HttpMultipartMode; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; -import org.apache.http.entity.StringEntity; -import org.apache.http.entity.mime.MultipartEntityBuilder; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.util.Timeout; public class RestClient { + /** Default timeout in milliseconds for HTTP requests (100 seconds, matching SafeguardDotNet). */ + public static final int DEFAULT_TIMEOUT_MS = 100_000; + private CloseableHttpClient client = null; private BasicCookieStore cookieStore = new BasicCookieStore(); @@ -80,7 +81,7 @@ public class RestClient { private boolean ignoreSsl = false; private HostnameVerifier validationCallback = null; - Logger logger = Logger.getLogger(getClass().getName()); + private static final Logger logger = LoggerFactory.getLogger(RestClient.class); public RestClient(String connectionAddr, boolean ignoreSsl, HostnameVerifier validationCallback) { @@ -88,62 +89,64 @@ public RestClient(String connectionAddr, boolean ignoreSsl, HostnameVerifier val } public RestClient(String connectionAddr, String userName, char[] password, boolean ignoreSsl, HostnameVerifier validationCallback) { - + HttpClientBuilder builder = createClientBuilder(connectionAddr, ignoreSsl, validationCallback); - CredentialsProvider provider = new BasicCredentialsProvider(); - provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, new String(password))); - - RequestConfig customizedRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); - + BasicCredentialsProvider provider = new BasicCredentialsProvider(); + provider.setCredentials(new AuthScope(null, -1), new UsernamePasswordCredentials(userName, password)); + client = builder.setDefaultCredentialsProvider(provider) - .setDefaultRequestConfig(customizedRequestConfig) .setDefaultCookieStore(cookieStore) .build(); } private HttpClientBuilder createClientBuilder(String connectionAddr, boolean ignoreSsl, HostnameVerifier validationCallback) { - // Used to produce debug output + // Used to produce debug output - enable SLF4J debug level instead if (false) { - Handler handlerObj = new ConsoleHandler(); - handlerObj.setLevel(Level.ALL); - logger.addHandler(handlerObj); - logger.setLevel(Level.ALL); - logger.setUseParentHandlers(false); + // Debug logging is now controlled via SLF4J configuration } this.ignoreSsl = ignoreSsl; this.serverUrl = connectionAddr; - + try { URL aUrl = new URL(connectionAddr); this.hostDomain = aUrl.getHost(); } catch (MalformedURLException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Invalid URL", ex); + logger.error("Invalid URL", ex); } - SSLConnectionSocketFactory sslsf = null; + SSLConnectionSocketFactory sslsf = null; if (ignoreSsl) { this.validationCallback = null; sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), NoopHostnameVerifier.INSTANCE); } else if (validationCallback != null) { this.validationCallback = validationCallback; - sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), validationCallback); + sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null), validationCallback); } else { sslsf = new SSLConnectionSocketFactory(getSSLContext(null, null, null, null)); } Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - - return HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connectionManager); + + return HttpClients.custom().setConnectionManager(connectionManager); } - + private URI getBaseURI(String segments) { try { - return new URI(serverUrl+"/"+segments); + String fullUrl = serverUrl + "/" + segments; + int queryIdx = fullUrl.indexOf('?'); + if (queryIdx >= 0) { + URI base = new URI(fullUrl.substring(0, queryIdx)); + String rawQuery = fullUrl.substring(queryIdx + 1); + return new URI(base.getScheme(), base.getUserInfo(), + base.getHost(), base.getPort(), base.getPath(), + rawQuery, null); + } + return new URI(fullUrl); } catch (URISyntaxException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Invalid URI", ex); + logger.error("Invalid URI", ex); } return null; } @@ -153,7 +156,7 @@ public String getBaseURL() { } private Map parseKeyValue(String value) { - + HashMap keyValues = new HashMap<>(); String[] parts = value.split(";"); for (String p : parts) { @@ -165,16 +168,16 @@ private Map parseKeyValue(String value) { keyValues.put(kv[0].trim(), kv[1].trim()); } } - + return keyValues; } - + public void addSessionId(String cookieValue) { if (cookieValue == null) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Session cookie cannot be null"); + logger.error("Session cookie cannot be null"); return; } - + try { Map keyValues = parseKeyValue(cookieValue); @@ -186,7 +189,7 @@ public void addSessionId(String cookieValue) { if (expiryDate != null) { SimpleDateFormat formatter = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z"); Date date = formatter.parse(expiryDate); - cookie.setExpiryDate(date); + cookie.setExpiryDate(date.toInstant()); } String path = keyValues.get("Path"); @@ -197,7 +200,7 @@ public void addSessionId(String cookieValue) { if (this.hostDomain != null) { cookie.setDomain(this.hostDomain); } - + String secure = keyValues.get("Secure"); if (secure != null) { cookie.setSecure(true); @@ -207,16 +210,16 @@ public void addSessionId(String cookieValue) { } } catch (Exception ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Failed to set session cookie.", ex); + logger.error("Failed to set session cookie.", ex); } } - + public CloseableHttpResponse execGET(String path, Map queryParams, Map headers, Integer timeout) { - RequestBuilder rb = prepareRequest (RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -228,10 +231,10 @@ public CloseableHttpResponse execGET(String path, Map queryParam CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -239,38 +242,38 @@ public CloseableHttpResponse execGET(String path, Map queryParam } return null; } - - public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, + + public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, Integer timeout, IProgressCallback progressCallback) { if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) { headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.ACCEPT, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; } } - - public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, + + public CloseableHttpResponse execGETBytes(String path, Map queryParams, Map headers, Integer timeout, CertificateContext certificateContext, IProgressCallback progressCallback) { CloseableHttpClient certClient = getClientWithCertificate(certificateContext); - + if (certClient != null) { if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) { headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.ACCEPT, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.get(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.get(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -281,29 +284,29 @@ public CloseableHttpResponse execGETBytes(String path, Map query public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity) { - RequestBuilder rb = prepareRequest(RequestBuilder.put(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.put(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; } } - public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, + public CloseableHttpResponse execPUT(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity, CertificateContext certificateContext) { CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.put(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.put(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -311,15 +314,15 @@ public CloseableHttpResponse execPUT(String path, Map queryParam } return null; } - + public CloseableHttpResponse execPOST(String path, Map queryParams, Map headers, Integer timeout, JsonObject requestEntity) { - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -332,12 +335,12 @@ public CloseableHttpResponse execPOST(String path, Map queryPara CloseableHttpClient certClient = getClientWithCertificate(certificateContext); if (certClient != null) { - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { String body = requestEntity.toJson(); rb.setEntity(new StringEntity(body == null ? "{}" : body)); - CloseableHttpResponse r = certClient.execute(rb.build()); + CloseableHttpResponse r = certClient.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -346,25 +349,25 @@ public CloseableHttpResponse execPOST(String path, Map queryPara return null; } - - public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, + + public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, byte[] requestEntity, IProgressCallback progressCallback) { if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.CONTENT_TYPE, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(new ByteArrayEntity(requestEntity, progressCallback)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; } } - + public CloseableHttpResponse execPOSTBytes(String path, Map queryParams, Map headers, Integer timeout, byte[] requestEntity, CertificateContext certificateContext, IProgressCallback progressCallback) { @@ -375,11 +378,11 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer headers = headers == null ? new HashMap<>() : headers; headers.put(HttpHeaders.CONTENT_TYPE, "application/octet-stream"); } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(new ByteArrayEntity(requestEntity, progressCallback)); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -388,28 +391,28 @@ public CloseableHttpResponse execPOSTBytes(String path, Map quer return null; } - public CloseableHttpResponse execPOSTFile(String path, Map queryParams, Map queryParams, Map headers, Integer timeout, String fileName) { File file = new File(fileName); - - HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + + HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(data); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; } } - + public CloseableHttpResponse execPOSTFile(String path, Map queryParams, Map headers, Integer timeout, String fileName, CertificateContext certificateContext) { @@ -417,17 +420,17 @@ public CloseableHttpResponse execPOSTFile(String path, Map query if (certClient != null) { File file = new File(fileName); - HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + HttpEntity data = MultipartEntityBuilder.create().setMode(HttpMultipartMode.LEGACY) .addBinaryBody("firmware", file, ContentType.MULTIPART_FORM_DATA, file.getName()).build(); - + if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) { headers = headers == null ? new HashMap<>() : headers; } - RequestBuilder rb = prepareRequest(RequestBuilder.post(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.post(getBaseURI(path)), queryParams, headers); try { rb.setEntity(data); - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (IOException ex) { return null; @@ -435,13 +438,13 @@ public CloseableHttpResponse execPOSTFile(String path, Map query } return null; } - + public CloseableHttpResponse execDELETE(String path, Map queryParams, Map headers, Integer timeout) { - RequestBuilder rb = prepareRequest(RequestBuilder.delete(getBaseURI(path)), queryParams, headers, timeout); + ClassicRequestBuilder rb = prepareRequest(ClassicRequestBuilder.delete(getBaseURI(path)), queryParams, headers); try { - CloseableHttpResponse r = client.execute(rb.build()); + CloseableHttpResponse r = client.execute(rb.build(), createContext(timeout)); return r; } catch (Exception ex) { return null; @@ -451,11 +454,10 @@ public CloseableHttpResponse execDELETE(String path, Map queryPa private CloseableHttpClient getClientWithCertificate(CertificateContext certificateContext) { CloseableHttpClient certClient = null; - if (certificateContext.getCertificatePath() != null - || certificateContext.getCertificateData() != null + if (certificateContext.getCertificatePath() != null + || certificateContext.getCertificateData() != null || certificateContext.getCertificateThumbprint() != null) { - InputStream in; KeyStore clientKs = null; List aliases = null; char[] keyPass = certificateContext.getCertificatePassword(); @@ -467,47 +469,47 @@ private CloseableHttpClient getClientWithCertificate(CertificateContext certific aliases = new ArrayList<>(); aliases = Collections.list(clientKs.aliases()); } else { - in = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) - : new ByteArrayInputStream(certificateContext.getCertificateData()); - try { - clientKs = KeyStore.getInstance("JKS"); - } catch (KeyStoreException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, "Could not get instance of JDK, trying PKCS12", ex); - clientKs = KeyStore.getInstance("PKCS12"); + try (InputStream in2 = certificateContext.getCertificatePath() != null ? new FileInputStream(certificateContext.getCertificatePath()) + : new ByteArrayInputStream(certificateContext.getCertificateData())) { + try { + clientKs = KeyStore.getInstance("JKS"); + } catch (KeyStoreException ex) { + logger.error("Could not get instance of JDK, trying PKCS12", ex); + clientKs = KeyStore.getInstance("PKCS12"); + } + clientKs.load(in2, keyPass); + aliases = Collections.list(clientKs.aliases()); } - clientKs.load(in, keyPass); - aliases = Collections.list(clientKs.aliases()); - in.close(); } } catch (FileNotFoundException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException ex) { - Logger.getLogger(RestClient.class.getName()).log(Level.SEVERE, null, ex); + logger.error("Exception occurred", ex); } - SSLConnectionSocketFactory sslsf = null; + SSLConnectionSocketFactory sslsf = null; if (ignoreSsl) { sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), NoopHostnameVerifier.INSTANCE); } else if (validationCallback != null) { - sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), validationCallback); + sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext), validationCallback); } else { sslsf = new SSLConnectionSocketFactory(getSSLContext(clientKs, keyPass, certificateAlias == null ? aliases.get(0) : certificateAlias, certificateContext)); } Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - certClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connectionManager).build(); + certClient = HttpClients.custom().setConnectionManager(connectionManager).build(); } return certClient; } - private RequestBuilder prepareRequest(RequestBuilder rb, Map queryParams, Map headers, Integer timeout) { - + private ClassicRequestBuilder prepareRequest(ClassicRequestBuilder rb, Map queryParams, Map headers) { + if (headers == null || !headers.containsKey(HttpHeaders.ACCEPT)) rb.addHeader(HttpHeaders.ACCEPT, "application/json"); if (headers == null || !headers.containsKey(HttpHeaders.CONTENT_TYPE)) rb.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - + if (headers != null) { headers.entrySet().forEach((entry) -> { rb.addHeader(entry.getKey(), entry.getValue()); @@ -518,16 +520,20 @@ private RequestBuilder prepareRequest(RequestBuilder rb, Map que rb.addParameter(entry.getKey(), entry.getValue()); }); } - if (timeout != null) { - RequestConfig rconfig = RequestConfig.custom() - .setConnectTimeout(timeout) - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout).build(); - rb.setConfig(rconfig); - } return rb; } + private HttpClientContext createContext(Integer timeout) { + HttpClientContext context = HttpClientContext.create(); + int effectiveTimeout = (timeout != null) ? timeout : DEFAULT_TIMEOUT_MS; + RequestConfig rconfig = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(effectiveTimeout)) + .setConnectionRequestTimeout(Timeout.ofMilliseconds(effectiveTimeout)) + .setResponseTimeout(Timeout.ofMilliseconds(effectiveTimeout)).build(); + context.setRequestConfig(rconfig); + return context; + } + private SSLContext getSSLContext(KeyStore keyStorePath, char[] keyStorePassword, String alias, CertificateContext certificateContext) { TrustManager[] customTrustManager = null; @@ -615,5 +621,5 @@ public PrivateKey getPrivateKey(String string) { return defaultKeyManager.getPrivateKey(string); } } - + } diff --git a/tests/safeguardjavaclient/pom.xml b/tests/safeguardjavaclient/pom.xml index 3a8fb9b..6b22c5c 100644 --- a/tests/safeguardjavaclient/pom.xml +++ b/tests/safeguardjavaclient/pom.xml @@ -15,30 +15,45 @@ - commons-cli - commons-cli - 1.5.0 + info.picocli + picocli + 4.7.6 + + + + com.fasterxml.jackson.core + jackson-databind + 2.18.3 com.oneidentity.safeguard safeguardjava - 7.5.0-SNAPSHOT + 8.2.0-SNAPSHOT org.slf4j slf4j-simple - 1.7.36 + 2.0.17 + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 1.8 + 1.8 + + org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.8.1 copy-dependencies @@ -58,7 +73,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.1.0 + 3.4.2 @@ -69,8 +84,7 @@ - - \ No newline at end of file + diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java index 82416f6..14672aa 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/CertificateValidator.java @@ -6,12 +6,12 @@ public class CertificateValidator implements HostnameVerifier { public static final CertificateValidator INSTANCE = new CertificateValidator(); - + @Override public boolean verify(final String s, final SSLSession sslSession) { return true; } - + @Override public final String toString() { return "CertificateValidator"; diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java index 4024d67..8f0c961 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ProgressNotification.java @@ -9,5 +9,5 @@ public class ProgressNotification implements IProgressCallback { public void checkProgress(TransferProgress transferProgress) { System.out.println(String.format("\tBytes transfered %d done", transferProgress.getPercentComplete())); } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java index ac31a92..0481514 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardJavaClient.java @@ -1,37 +1,387 @@ package com.oneidentity.safeguard.safeguardclient; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.oneidentity.safeguard.safeguardjava.ISafeguardA2AContext; import com.oneidentity.safeguard.safeguardjava.ISafeguardConnection; import com.oneidentity.safeguard.safeguardjava.ISafeguardSessionsConnection; +import com.oneidentity.safeguard.safeguardjava.Safeguard; +import com.oneidentity.safeguard.safeguardjava.SafeguardForPrivilegedSessions; +import com.oneidentity.safeguard.safeguardjava.data.FullResponse; +import com.oneidentity.safeguard.safeguardjava.data.Method; +import com.oneidentity.safeguard.safeguardjava.data.Service; import com.oneidentity.safeguard.safeguardjava.event.ISafeguardEventListener; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; +import org.apache.hc.core5.http.Header; +import picocli.CommandLine; + import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; import java.util.Scanner; public class SafeguardJavaClient { + private static final ObjectMapper mapper = new ObjectMapper(); + public static void main(String[] args) { + if (args.length == 0) { + runInteractive(); + return; + } + + ToolOptions opts = new ToolOptions(); + CommandLine cmd = new CommandLine(opts); + + try { + cmd.parseArgs(args); + } catch (CommandLine.ParameterException ex) { + System.err.println("Error: " + ex.getMessage()); + cmd.usage(System.err); + System.exit(1); + return; + } + + if (cmd.isUsageHelpRequested()) { + cmd.usage(System.out); + return; + } + + if (opts.interactive) { + runInteractive(); + return; + } + + try { + if (opts.sps) { + handleSpsRequest(opts); + System.exit(0); + return; + } + + ISafeguardConnection connection = createConnection(opts); + + if (opts.resourceOwner != null) { + setResourceOwnerGrant(connection, opts.resourceOwner); + System.exit(0); + return; + } + + if (opts.tokenLifetime) { + int remaining = connection.getAccessTokenLifetimeRemaining(); + ObjectNode json = mapper.createObjectNode(); + json.put("TokenLifetimeRemaining", remaining); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + if (opts.getToken) { + char[] token = connection.getAccessToken(); + ObjectNode json = mapper.createObjectNode(); + json.put("AccessToken", new String(token)); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + if (opts.refreshToken) { + connection.refreshAccessToken(); + int remaining = connection.getAccessTokenLifetimeRemaining(); + ObjectNode json = mapper.createObjectNode(); + json.put("TokenLifetimeRemaining", remaining); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + if (opts.logout) { + char[] token = connection.getAccessToken(); + ObjectNode json = mapper.createObjectNode(); + json.put("AccessToken", new String(token)); + connection.logOut(); + json.put("LoggedOut", true); + System.out.println(mapper.writeValueAsString(json)); + System.exit(0); + return; + } + + Service service = parseService(opts.service); + Method method = parseMethod(opts.method); + Map headers = parseKeyValuePairs(opts.headers); + Map parameters = parseKeyValuePairs(opts.parameters); + + if (opts.file != null) { + String result = handleStreamingRequest(opts, connection, service, method, headers, parameters); + System.out.println(result); + } else if (opts.full) { + FullResponse response = connection.invokeMethodFull(service, method, + opts.relativeUrl, opts.body, parameters, headers, null); + ObjectNode json = mapper.createObjectNode(); + json.put("StatusCode", response.getStatusCode()); + ArrayNode headersArray = json.putArray("Headers"); + for (Header h : response.getHeaders()) { + ObjectNode headerObj = mapper.createObjectNode(); + headerObj.put("Name", h.getName()); + headerObj.put("Value", h.getValue()); + headersArray.add(headerObj); + } + json.put("Body", response.getBody()); + System.out.println(mapper.writeValueAsString(json)); + } else { + String result = connection.invokeMethod(service, method, + opts.relativeUrl, opts.body, parameters, headers, null); + System.out.println(result); + } + + System.exit(0); + } catch (Exception ex) { + System.err.println("Error: " + ex.getMessage()); + if (opts.verbose) { + ex.printStackTrace(System.err); + } + System.exit(1); + } + } + + private static String handleStreamingRequest(ToolOptions opts, ISafeguardConnection connection, + Service service, Method method, Map headers, + Map parameters) throws Exception { + if (method == Method.Post) { + byte[] fileContent = Files.readAllBytes(new File(opts.file).toPath()); + return connection.getStreamingRequest().uploadStream(service, opts.relativeUrl, + fileContent, null, parameters, headers); + } else if (method == Method.Get) { + File outFile = new File(opts.file); + if (outFile.exists()) { + throw new IllegalStateException("File exists, remove it first: " + opts.file); + } + connection.getStreamingRequest().downloadStream(service, opts.relativeUrl, + opts.file, null, parameters, headers); + return "Download written to " + opts.file; + } else { + throw new IllegalArgumentException("Streaming is not supported for HTTP method: " + opts.method); + } + } + + private static void handleSpsRequest(ToolOptions opts) throws Exception { + char[] password = null; + if (opts.readPassword) { + System.err.print("Password: "); + password = new Scanner(System.in).nextLine().toCharArray(); + } + + System.err.println("Connecting to SPS " + opts.appliance + " as " + opts.username); + ISafeguardSessionsConnection spsConnection = SafeguardForPrivilegedSessions.Connect( + opts.appliance, opts.username, password, opts.insecure); + + Method method = parseMethod(opts.method); + + if (opts.full) { + FullResponse response = spsConnection.invokeMethodFull(method, opts.relativeUrl, opts.body); + ObjectNode json = mapper.createObjectNode(); + json.put("StatusCode", response.getStatusCode()); + ArrayNode headersArray = json.putArray("Headers"); + for (Header h : response.getHeaders()) { + ObjectNode headerObj = mapper.createObjectNode(); + headerObj.put("Name", h.getName()); + headerObj.put("Value", h.getValue()); + headersArray.add(headerObj); + } + json.put("Body", response.getBody()); + System.out.println(mapper.writeValueAsString(json)); + } else { + String result = spsConnection.invokeMethod(method, opts.relativeUrl, opts.body); + System.out.println(result); + } + } + + private static ISafeguardConnection createConnection(ToolOptions opts) throws Exception { + char[] password = null; + if (opts.readPassword) { + System.err.print("Password: "); + password = new Scanner(System.in).nextLine().toCharArray(); + } + + // Resource owner toggle always uses PKCE + boolean usePkce = opts.pkce || opts.resourceOwner != null; + + if (opts.username != null) { + String provider = opts.identityProvider != null ? opts.identityProvider : "local"; + if (usePkce) { + System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider + " (PKCE)"); + return Safeguard.connectPkce(opts.appliance, provider, opts.username, password, (Integer) null, opts.insecure); + } else { + System.err.println("Connecting to " + opts.appliance + " as " + opts.username + " via " + provider); + return Safeguard.connect(opts.appliance, provider, opts.username, password, null, opts.insecure); + } + } + + if (opts.certificateFile != null) { + System.err.println("Connecting to " + opts.appliance + " with certificate file " + opts.certificateFile); + return Safeguard.connect(opts.appliance, opts.certificateFile, password, null, opts.insecure, null); + } + + if (opts.thumbprint != null) { + System.err.println("Connecting to " + opts.appliance + " with thumbprint " + opts.thumbprint); + return Safeguard.connect(opts.appliance, opts.thumbprint, null, opts.insecure); + } + + if (opts.accessToken != null) { + System.err.println("Connecting to " + opts.appliance + " with access token"); + return Safeguard.connect(opts.appliance, opts.accessToken.toCharArray(), null, opts.insecure); + } + + if (opts.anonymous) { + System.err.println("Connecting to " + opts.appliance + " anonymously"); + return Safeguard.connect(opts.appliance, null, opts.insecure); + } + + throw new IllegalArgumentException( + "No authentication method specified. Use -u, -c, -t, -k, or -A."); + } + + private static Service parseService(String serviceStr) { + if (serviceStr == null) { + throw new IllegalArgumentException("Service (-s) is required for API invocation"); + } + switch (serviceStr.toLowerCase()) { + case "core": return Service.Core; + case "appliance": return Service.Appliance; + case "notification": return Service.Notification; + case "a2a": return Service.A2A; + default: + throw new IllegalArgumentException("Unknown service: " + serviceStr + + ". Valid values: Core, Appliance, Notification, A2A"); + } + } + + private static Method parseMethod(String methodStr) { + if (methodStr == null) { + throw new IllegalArgumentException("Method (-m) is required for API invocation"); + } + switch (methodStr.toLowerCase()) { + case "get": return Method.Get; + case "post": return Method.Post; + case "put": return Method.Put; + case "delete": return Method.Delete; + default: + throw new IllegalArgumentException("Unknown method: " + methodStr + + ". Valid values: Get, Post, Put, Delete"); + } + } + + private static Map parseKeyValuePairs(String[] pairs) { + Map map = new HashMap<>(); + if (pairs == null) { + return map; + } + for (String pair : pairs) { + int idx = pair.indexOf('='); + if (idx < 0) { + throw new IllegalArgumentException("Invalid Key=Value pair: " + pair); + } + map.put(pair.substring(0, idx), pair.substring(idx + 1)); + } + return map; + } + + private static final String GRANT_TYPE_SETTING_NAME = "Allowed OAuth2 Grant Types"; + + private static void setResourceOwnerGrant(ISafeguardConnection connection, boolean enable) + throws Exception { + String settingsJson = connection.invokeMethod(Service.Core, Method.Get, "Settings", + null, null, null, null); + JsonNode settings = mapper.readTree(settingsJson); + + JsonNode grantSetting = null; + if (settings.isArray()) { + for (JsonNode node : settings) { + JsonNode nameNode = node.get("Name"); + if (nameNode != null && GRANT_TYPE_SETTING_NAME.equals(nameNode.asText())) { + grantSetting = node; + break; + } + } + } + if (grantSetting == null) { + throw new RuntimeException("Setting '" + GRANT_TYPE_SETTING_NAME + "' not found"); + } + + String currentValue = grantSetting.has("Value") && !grantSetting.get("Value").isNull() + ? grantSetting.get("Value").asText() : ""; + + String[] parts = currentValue.split(","); + java.util.List grantTypes = new java.util.ArrayList<>(); + for (String part : parts) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + grantTypes.add(trimmed); + } + } + + boolean hasResourceOwner = false; + for (String gt : grantTypes) { + if (gt.equalsIgnoreCase("ResourceOwner")) { + hasResourceOwner = true; + break; + } + } + + if (enable && !hasResourceOwner) { + grantTypes.add("ResourceOwner"); + } else if (!enable && hasResourceOwner) { + java.util.Iterator it = grantTypes.iterator(); + while (it.hasNext()) { + if (it.next().equalsIgnoreCase("ResourceOwner")) { + it.remove(); + } + } + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < grantTypes.size(); i++) { + if (i > 0) sb.append(", "); + sb.append(grantTypes.get(i)); + } + String newValue = sb.toString(); + + ObjectNode body = mapper.createObjectNode(); + body.put("Value", newValue); + connection.invokeMethod(Service.Core, Method.Put, + "Settings/" + java.net.URLEncoder.encode(GRANT_TYPE_SETTING_NAME, "UTF-8") + .replace("+", "%20"), + mapper.writeValueAsString(body), null, null, null); + + ObjectNode envelope = mapper.createObjectNode(); + envelope.put("Setting", GRANT_TYPE_SETTING_NAME); + envelope.put("PreviousValue", currentValue); + envelope.put("NewValue", newValue); + envelope.put("ResourceOwnerEnabled", enable); + System.out.println(mapper.writeValueAsString(envelope)); + } + + private static void runInteractive() { + ISafeguardConnection connection = null; ISafeguardSessionsConnection sessionConnection = null; ISafeguardA2AContext a2aContext = null; ISafeguardEventListener eventListener = null; ISafeguardEventListener a2aEventListener = null; - + boolean done = false; SafeguardTests tests = new SafeguardTests(); - + // Uncomment the lines below to enable console debug logging of the http requests //System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog"); //System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); - //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG"); + //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire", "DEBUG"); //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.impl.conn", "DEBUG"); //System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.impl.client", "DEBUG"); @@ -160,12 +510,12 @@ public static void main(String[] args) { System.out.println("/tException Stack: "); ex.printStackTrace(); } - + System.out.println("All done."); } private static Integer displayMenu() { - + System.out.println("Select an option:"); System.out.println ("\t1. Connect by user/password"); System.out.println ("\t2. Connect by thumbprint"); @@ -200,52 +550,14 @@ private static Integer displayMenu() { System.out.println ("\t31. Test Join SPS"); System.out.println ("\t32. Test Management Interface API"); System.out.println ("\t33. Test Anonymous Connection"); - + System.out.println ("\t99. Exit"); - + System.out.print ("Selection: "); Scanner in = new Scanner(System.in); int selection = in.nextInt(); - - return selection; - } - - private static CommandLine parseArguments(String[] args) { - - Options options = getOptions(); - CommandLine line = null; - - CommandLineParser parser = new DefaultParser(); - - try { - line = parser.parse(options, args); - - } catch (ParseException ex) { - - System.err.println(ex); - printAppHelp(); - System.exit(1); - } - - return line; - } - - private static Options getOptions() { - - Options options = new Options(); - - options.addOption("f", "filename", true, - "file name to load data from"); - return options; - } - - private static void printAppHelp() { - - Options options = getOptions(); - - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("SafeguardClientCli", options, true); + return selection; } public static String toJsonString(String name, Object value, boolean prependSep) { @@ -256,7 +568,7 @@ public static String toJsonString(String name, Object value, boolean prependSep) } public static String readLine(String format, String defaultStr, Object... args) { - + String value = defaultStr; format += "["+defaultStr+"] "; try { @@ -271,23 +583,9 @@ public static String readLine(String format, String defaultStr, Object... args) System.out.println(ex.getMessage()); return defaultStr; } - + if (value.trim().length() == 0) return defaultStr; return value.trim(); } - - - - - - } - -//class EventHandler implements ISafeguardEventHandler { -// -// @Override -// public void onEventReceived(String eventName, String eventBody) { -// System.out.println("Got the event"); -// } -//} diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java index 738b07d..d161e01 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/SafeguardTests.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; +import com.fasterxml.jackson.databind.ObjectMapper; import static com.oneidentity.safeguard.safeguardclient.SafeguardJavaClient.readLine; import com.oneidentity.safeguard.safeguardclient.data.SafeguardAppliance; import com.oneidentity.safeguard.safeguardclient.data.SafeguardApplianceStatus; @@ -48,7 +48,7 @@ public class SafeguardTests { public SafeguardTests() { } - + private void logResponseDetails(FullResponse fullResponse) { System.out.println(String.format("\t\tReponse status code: %d", fullResponse.getStatusCode())); @@ -59,16 +59,16 @@ private void logResponseDetails(FullResponse fullResponse) } public ISafeguardConnection safeguardConnectByUserPassword() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String user = readLine("User:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardConnection connection = null; - + try { if (withCertValidator) { connection = Safeguard.connect(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -80,12 +80,12 @@ public ISafeguardConnection safeguardConnectByUserPassword() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectByThumbprint() { ISafeguardConnection connection = null; @@ -93,7 +93,7 @@ public ISafeguardConnection safeguardConnectByThumbprint() { String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "y").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, thumbprint, new CertificateValidator(), null); @@ -105,7 +105,7 @@ public ISafeguardConnection safeguardConnectByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -113,14 +113,14 @@ public ISafeguardConnection safeguardConnectByThumbprint() { public ISafeguardConnection safeguardConnectByCertificate() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, certificatePath, password.toCharArray(), new CertificateValidator(), provider, null); @@ -132,7 +132,7 @@ public ISafeguardConnection safeguardConnectByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -140,12 +140,12 @@ public ISafeguardConnection safeguardConnectByCertificate() { public ISafeguardConnection safeguardConnectByToken() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String token = readLine("Token:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, token.toCharArray(), new CertificateValidator(), null); @@ -157,32 +157,32 @@ public ISafeguardConnection safeguardConnectByToken() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectAnonymous() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { connection = Safeguard.connect(address, null, ignoreSsl); } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public ISafeguardConnection safeguardConnectByKeystore() { ISafeguardConnection connection = null; - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", null); String keystorePath = readLine("Keystore Path:", null); @@ -190,7 +190,7 @@ public ISafeguardConnection safeguardConnectByKeystore() { String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { connection = Safeguard.connect(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), provider, null); @@ -202,42 +202,42 @@ public ISafeguardConnection safeguardConnectByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; } - + public void safeguardTestConnection(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } - + try { char[] token = connection.getAccessToken(); System.out.println(String.format("\tAccess Token: %s", new String(token))); - + int remaining = connection.getAccessTokenLifetimeRemaining(); System.out.println(String.format("\tTime remaining: %d", remaining)); - + String response = connection.invokeMethod(Service.Core, Method.Get, "Users", null, null, null, null); System.out.println(String.format("\t\\Users response:")); System.out.println(response); - + FullResponse fullResponse = connection.invokeMethodFull(Service.Core, Method.Get, "Users", null, null, null, null); System.out.println(String.format("\t\\Users full response:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Core, Method.Post, "Events/FireTestEvent", null, null, null, null); System.out.println(String.format("\t\\FireTestEvent response:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Notification, Method.Get, "Status", null, null, null, null); System.out.println(String.format("\t\\Appliance status:")); logResponseDetails(fullResponse); - + fullResponse = connection.invokeMethodFull(Service.Appliance, Method.Get, "NetworkInterfaces", null, null, null, null); System.out.println(String.format("\t\\NetworkInterfaces response:")); logResponseDetails(fullResponse); @@ -246,7 +246,7 @@ public void safeguardTestConnection(ISafeguardConnection connection) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + public ISafeguardConnection safeguardDisconnect(ISafeguardConnection connection) { if (connection != null) { connection.dispose(); @@ -256,19 +256,19 @@ public ISafeguardConnection safeguardDisconnect(ISafeguardConnection connection) public ISafeguardA2AContext safeguardGetA2AContextByCertificate() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + return safeguardGetA2AContextByCertificate(address, certificatePath, password, withCertValidator, ignoreSsl); } - + private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, String certificatePath, String password, boolean withCertValidator, boolean ignoreSsl) { ISafeguardA2AContext a2aContext = null; - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, certificatePath, password.toCharArray(), new CertificateValidator(), null); @@ -278,7 +278,7 @@ private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; @@ -286,14 +286,14 @@ private ISafeguardA2AContext safeguardGetA2AContextByCertificate(String address, public ISafeguardA2AContext safeguardGetA2AContextByKeystore() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String keystorePath = readLine("Keystore Path:", null); String password = readLine("Password: ", null); String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), null); @@ -303,14 +303,14 @@ public ISafeguardA2AContext safeguardGetA2AContextByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; } public ISafeguardA2AContext safeguardGetA2AContextByThumbprint() { - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "y").equalsIgnoreCase("y"); @@ -318,10 +318,10 @@ public ISafeguardA2AContext safeguardGetA2AContextByThumbprint() { return safeguardGetA2AContextByThumbprint(address, thumbprint, withCertValidator, ignoreSsl); } - + private ISafeguardA2AContext safeguardGetA2AContextByThumbprint(String address, String thumbprint, boolean withCertValidator, boolean ignoreSsl) { ISafeguardA2AContext a2aContext = null; - + try { if (withCertValidator) { a2aContext = Safeguard.A2A.getContext(address, thumbprint, new CertificateValidator(), null); @@ -331,19 +331,19 @@ private ISafeguardA2AContext safeguardGetA2AContextByThumbprint(String address, } catch (Exception ex) { System.out.println("\t[ERROR]A2AContext failed: " + ex.getMessage()); } - + if (a2aContext != null) System.out.println("\tSuccessful A2A context connection."); return a2aContext; } - + private byte[] readAllBytes(InputStream in) throws IOException { ByteArrayOutputStream baos= new ByteArrayOutputStream(); byte[] buf = new byte[1024]; for (int read=0; read != -1; read = in.read(buf)) { baos.write(buf, 0, read); } return baos.toByteArray(); } - + private String formatPEM(String resource) throws IOException { InputStream in = new ByteArrayInputStream(resource.getBytes()); String pem = new String(readAllBytes(in), StandardCharsets.ISO_8859_1); @@ -351,14 +351,14 @@ private String formatPEM(String resource) throws IOException { String encoded = parse.matcher(pem).replaceFirst("$1"); return encoded.replace("\r", "").replace("\n", ""); } - + public void safeguardTestA2AContext(ISafeguardA2AContext a2aContext) { - + if (a2aContext == null) { System.out.println(String.format("Missing Safeguard A2A context.")); return; } - + if (readLine("Test Credential Retrieval(y/n): ", "y").equalsIgnoreCase("y")) { String typeOfRelease = readLine("Password, Private Key or API Key Secret (p/k/a): ", "p"); String apiKey = readLine("API Key: ", null); @@ -401,11 +401,11 @@ else if (typeOfRelease.equalsIgnoreCase("a")) { if (typeOfRelease.equalsIgnoreCase("p")) { String newPassword = readLine("New Password: ", ""); a2aContext.SetPassword(apiKey.toCharArray(), newPassword.toCharArray()); - + String password = new String(a2aContext.retrievePassword(apiKey.toCharArray())); if (password.compareTo(newPassword) == 0) System.out.println(String.format("\tSuccessfully set password")); - else + else System.out.println(String.format("\tFailed to set password")); } else if (typeOfRelease.equalsIgnoreCase("k")) { @@ -415,15 +415,15 @@ else if (typeOfRelease.equalsIgnoreCase("k")) { String privateKey = new String(Files.readAllBytes(filePath)); a2aContext.SetPrivateKey(apiKey.toCharArray(), privateKey.toCharArray(), privateKeyPassword.toCharArray(), KeyFormat.OpenSsh); - + String key = new String(a2aContext.retrievePrivateKey(apiKey.toCharArray(), KeyFormat.OpenSsh)); - + String privkey1 = formatPEM(privateKey); String privkey2 = formatPEM(key); - + if (privkey1.compareTo(privkey2) == 0) System.out.println(String.format("\tSuccessful private key release")); - else + else System.out.println(String.format("\tFailed to set private key")); } else { @@ -434,32 +434,32 @@ else if (typeOfRelease.equalsIgnoreCase("k")) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + if (readLine("Test Access Request Broker(y/n): ", "y").equalsIgnoreCase("y")) { try { List registrations = a2aContext.getRetrievableAccounts(); System.out.println(String.format("\tRetrievable accounts:")); for (IA2ARetrievableAccount reg : registrations) { - System.out.println(String.format("\t\tAssetId: %d AssetName: %s AccountId: %d AccountName: %s AccountDescription: %s", + System.out.println(String.format("\t\tAssetId: %d AssetName: %s AccountId: %d AccountName: %s AccountDescription: %s", reg.getAssetId(), reg.getAssetName(), reg.getAccountId(), reg.getAccountName(), reg.getAccountDescription())); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Failed to get the retrievable accounts: " + ex.getMessage()); } - + String accountId = readLine("Account Id: ", null); String assetId = readLine("Asset Id:", null); String forUserId = readLine("For User Id:", null); String accessRequestType = readLine("Access Request Type((p)assword/(s)sh/(r)dp): ", "p"); String apiKey = readLine("Api Key: ", null); - + try { IBrokeredAccessRequest accessRequest = new BrokeredAccessRequest(); accessRequest.setAccountId(Integer.parseInt(accountId)); accessRequest.setForUserId(Integer.parseInt(forUserId)); accessRequest.setAssetId(Integer.parseInt(assetId)); - accessRequest.setAccessType(accessRequestType.toLowerCase().equals("p") ? BrokeredAccessRequestType.Password - : accessRequestType.toLowerCase().equals("s") ? BrokeredAccessRequestType.Ssh + accessRequest.setAccessType(accessRequestType.toLowerCase().equals("p") ? BrokeredAccessRequestType.Password + : accessRequestType.toLowerCase().equals("s") ? BrokeredAccessRequestType.Ssh : BrokeredAccessRequestType.Rdp); String result = a2aContext.brokerAccessRequest(apiKey.toCharArray(), accessRequest); @@ -489,9 +489,9 @@ private List ReadAllApiKeys(ISafeguardA2AContext context) throws ObjectD return apiKeys; } - + public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); @@ -501,20 +501,20 @@ public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { ISafeguardA2AContext a2aContext = safeguardGetA2AContextByCertificate(address, certificatePath, password, withCertValidator, ignoreSsl); ISafeguardEventListener eventListener = null; - + try { List apiKeys = ReadAllApiKeys(a2aContext); - ISafeguardEventHandler a2aHandler = + ISafeguardEventHandler a2aHandler = (String eventName, String eventBody) -> { System.out.println(String.format("\tEvent body for %s event", eventName)); System.out.println(String.format("\t\t%s", eventBody)); }; if (withCertValidator) { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, certificatePath, password.toCharArray(), new CertificateValidator(), null); } else { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, certificatePath, password.toCharArray(), null, ignoreSsl); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -522,14 +522,14 @@ public ISafeguardEventListener safeguardA2AEventListenerByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); @@ -539,20 +539,20 @@ public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { ISafeguardA2AContext a2aContext = safeguardGetA2AContextByThumbprint(address, thumbprint, withCertValidator, ignoreSsl); ISafeguardEventListener eventListener = null; - + try { List apiKeys = ReadAllApiKeys(a2aContext); - ISafeguardEventHandler a2aHandler = + ISafeguardEventHandler a2aHandler = (String eventName, String eventBody) -> { System.out.println(String.format("\tEvent body for %s event", eventName)); System.out.println(String.format("\t\t%s", eventBody)); }; if (withCertValidator) { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, thumbprint, new CertificateValidator(), null); } else { - eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, + eventListener = Safeguard.A2A.Event.getPersistentA2AEventListener(apiKeys, a2aHandler, address, thumbprint, null, ignoreSsl); } } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -560,23 +560,23 @@ public ISafeguardEventListener safeguardA2AEventListenerByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardEventListenerByUserPassword() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String user = readLine("User:", null); String password = readLine("Password: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -588,14 +588,14 @@ public ISafeguardEventListener safeguardEventListenerByUserPassword() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + public ISafeguardEventListener safeguardEventListenerByCertificate() { - + String address = readLine("SPP address: ", null); String certificatePath = readLine("Certificate Path:", null); String password = readLine("Password: ", null); @@ -603,7 +603,7 @@ public ISafeguardEventListener safeguardEventListenerByCertificate() { boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, certificatePath, password.toCharArray(), new CertificateValidator(), null); @@ -615,14 +615,14 @@ public ISafeguardEventListener safeguardEventListenerByCertificate() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } public ISafeguardEventListener safeguardEventListenerByKeystore() { - + String address = readLine("SPP address: ", null); String provider = readLine("Provider:", "local"); String keystorePath = readLine("Keystore Path:", null); @@ -630,9 +630,9 @@ public ISafeguardEventListener safeguardEventListenerByKeystore() { String alias = readLine("Alias: ", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, keystorePath, password.toCharArray(), alias, new CertificateValidator(), provider, null); @@ -644,22 +644,22 @@ public ISafeguardEventListener safeguardEventListenerByKeystore() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } - + ISafeguardEventListener safeguardEventListenerByThumbprint() { ISafeguardA2AContext a2aContext = null; - + String address = readLine("SPP address: ", null); String thumbprint = readLine("Thumbprint:", null); boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardEventListener eventListener = null; - + try { if (withCertValidator) { eventListener = Safeguard.Event.getPersistentEventListener(address, thumbprint, new CertificateValidator(), null); @@ -671,21 +671,21 @@ ISafeguardEventListener safeguardEventListenerByThumbprint() { } catch (Exception ex) { System.out.println("\t[ERROR]Event listener failed: " + ex.getMessage()); } - + if (eventListener != null) System.out.println("\tSuccessfully create an event listener."); return eventListener; } public void safeguardTestEventListener(ISafeguardEventListener eventListener) { - + if (eventListener == null) { System.out.println(String.format("\t[ERROR]Missing event listener")); return; } - + String e = readLine("Comma delimited events: ", "UserCreated,UserDeleted,TestConnectionFailed,TestConnectionStarted,TestConnectionSucceeded"); - + try { String[] events = e.split(","); if (events.length == 0) { @@ -701,7 +701,7 @@ public void onEventReceived(String eventName, String eventBody) { } }); } - + System.out.print("\tStarting the event listener"); eventListener.start(); readLine("Press enter to stop...", null); @@ -714,18 +714,18 @@ public void onEventReceived(String eventName, String eventBody) { } public void safeguardTestA2AEventListener(ISafeguardEventListener eventListener) { - + if (eventListener == null) { System.out.println(String.format("\t[ERROR]Missing event listener")); return; } - + try { eventListener.SetEventListenerStateCallback((SafeguardEventListenerState eventListenerState) -> { System.out.println(String.format("\tGot a SignalR connection state change: %s", eventListenerState.toString())); }); - + System.out.print("\tStarting the event listener"); eventListener.start(); readLine("Press enter to stop...", null); @@ -736,7 +736,7 @@ public void safeguardTestA2AEventListener(ISafeguardEventListener eventListener) System.out.println("\t[ERROR]Test event listener failed: " + ex.getMessage()); } } - + public ISafeguardEventListener safeguardDisconnectEventListener(ISafeguardEventListener eventListener) { if (eventListener != null) { eventListener.dispose(); @@ -745,7 +745,7 @@ public ISafeguardEventListener safeguardDisconnectEventListener(ISafeguardEventL } public void safeguardTestBackupDownload(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -754,12 +754,12 @@ public void safeguardTestBackupDownload(ISafeguardConnection connection) { String backupId = readLine("Backup Id: ", null); String backupFileName = readLine("Backup File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (backupId == null || backupFileName == null) { System.out.println(String.format("Missing id or file name")); return; } - + try { String filePath = Paths.get(".", backupFileName).toAbsolutePath().toString(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; @@ -774,7 +774,7 @@ public void safeguardTestBackupDownload(ISafeguardConnection connection) { } public void safeguardListBackups(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -782,20 +782,20 @@ public void safeguardListBackups(ISafeguardConnection connection) { try { String response = connection.invokeMethod(Service.Appliance, Method.Get, "Backups", null, null, null, null); - - SafeguardBackup[] backups = new Gson().fromJson(response, SafeguardBackup[].class); + + SafeguardBackup[] backups = new ObjectMapper().readValue(response, SafeguardBackup[].class); System.out.println(String.format("\t\\Backups response:")); for (SafeguardBackup backup : backups) { System.out.println(String.format("Id: %s - File Name: %s", backup.getId(), backup.getFilename())); } - } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException | java.io.IOException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } - + public void safeguardTestBackupUpload(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; @@ -803,12 +803,12 @@ public void safeguardTestBackupUpload(ISafeguardConnection connection) { String backupFileName = readLine("Backup File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (backupFileName == null) { System.out.println(String.format("Missing id or file name")); return; } - + try { Path filePath = Paths.get(".", backupFileName).toAbsolutePath(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; @@ -829,9 +829,9 @@ ISafeguardSessionsConnection safeguardSessionsConnection() { String password = readLine("Password: ", null); // boolean withCertValidator = readLine("With Certificate Validator(y/n): ", "n").equalsIgnoreCase("y"); boolean ignoreSsl = readLine("Ignore SSL(y/n): ", "y").equalsIgnoreCase("y"); - + ISafeguardSessionsConnection connection = null; - + try { // if (withCertValidator) { // connection = Safeguard.connect(address, provider, user, password.toCharArray(), new CertificateValidator(), null); @@ -843,7 +843,7 @@ ISafeguardSessionsConnection safeguardSessionsConnection() { } catch (Exception ex) { System.out.println("\t[ERROR]Connection failed: " + ex.getMessage()); } - + if (connection != null) System.out.println("\tSuccessful connection."); return connection; @@ -854,35 +854,35 @@ void safeguardSessionsApi(ISafeguardSessionsConnection connection) { System.out.println(String.format("Safeguard sessions not connected")); return; } - + try { FullResponse fullResponse = connection.invokeMethodFull(Method.Get, "configuration/network/naming", null); System.out.println(String.format("\t\\Network Naming full response:")); logResponseDetails(fullResponse); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } } public void safeguardSessionsFileUpload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println("Safeguard not connected"); return; } String patchFileName = readLine("SPS Firmware File Name: ", null); - + if (patchFileName == null) { System.out.println("Missing file name"); return; } - + try { Path filePath = Paths.get(patchFileName).toAbsolutePath(); System.out.println(String.format("\tFile path: %s", filePath.toAbsolutePath())); - + connection.getStreamingRequest().uploadStream("upload/firmware", filePath.toString(), null, null); System.out.println(String.format("\tUploaded file: %s", patchFileName)); } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -893,7 +893,7 @@ public void safeguardSessionsFileUpload(ISafeguardSessionsConnection connection) } public void safeguardSessionsStreamUpload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard Sessions not connected")); return; @@ -901,18 +901,18 @@ public void safeguardSessionsStreamUpload(ISafeguardSessionsConnection connectio String patchFileName = readLine("SPS Firmware File Name: ", null); boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); - + if (patchFileName == null) { System.out.println(String.format("file name")); return; } - + try { Path filePath = Paths.get(patchFileName).toAbsolutePath(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; byte[] fileContent = Files.readAllBytes(filePath); System.out.println(String.format("\tFile path: %s", filePath.toAbsolutePath())); - + connection.getStreamingRequest().uploadStream("upload/firmware", fileContent, progressCallback, null, null); System.out.println(String.format("\tUploaded file: %s", patchFileName)); } catch (ObjectDisposedException | SafeguardForJavaException ex) { @@ -927,34 +927,34 @@ private String[] safeguardSessionsGetRecordings(ISafeguardSessionsConnection con FullResponse fullResponse = connection.invokeMethodFull(Method.Get, "audit/sessions", null); System.out.println(String.format("\t\\Session Id's full response:")); logResponseDetails(fullResponse); - + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); SessionRecordings sessionIds = mapper.readValue(fullResponse.getBody(), SessionRecordings.class); return sessionIds.toArray(); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Get session recordings failed: " + ex.getMessage()); } catch (JsonProcessingException ex) { System.out.println("JSON deserialization failed: " + ex.getMessage()); } - + return null; } public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } String[] sessions = safeguardSessionsGetRecordings(connection); - + if (sessions == null) { System.out.println(String.format("Failed to get the session id's")); return; } - + for (int x = 0; x < sessions.length; x++) { System.out.println(String.format("\t%d. %s", x, sessions[x])); } @@ -965,14 +965,14 @@ public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection c System.out.println(String.format("Invalid session selection")); return; } - + String sessionId = sessions[sessionSelection]; String recordingFileName = sessionId + ".zat"; boolean withProgress = readLine("With Progress Notification(y/n): ", "n").equalsIgnoreCase("y"); String filePath = Paths.get(".", recordingFileName).toAbsolutePath().toString(); IProgressCallback progressCallback = withProgress ? new ProgressNotification() : null; - + try { System.out.println(String.format("\tSession recording file path: %s", filePath)); connection.getStreamingRequest().downloadStream(String.format("audit/sessions/%s/audit_trail", sessionId), filePath, progressCallback, null, null); @@ -983,7 +983,7 @@ public void safeguardSessionTestRecordingDownload(ISafeguardSessionsConnection c System.out.println("\t[ERROR]Test backup download failed: " + ex.getMessage()); } } - + public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardSessionsConnection spsConnection) { if (sppConnection == null) { System.out.println(String.format("Safeguard SPP not connected")); @@ -993,21 +993,21 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS System.out.println(String.format("Safeguard SPS not connected")); return; } - + SafeguardSslCertificate[] sslCerts = null; SafeguardApplianceStatus applianceStatus = null; try { FullResponse fullResponse = sppConnection.invokeMethodFull(Service.Core, Method.Get, "SslCertificates", null, null, null, null); System.out.println(String.format("\t\\SslCertificates full response:")); logResponseDetails(fullResponse); - + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); sslCerts = mapper.readValue(fullResponse.getBody(), SafeguardSslCertificate[].class); fullResponse = sppConnection.invokeMethodFull(Service.Appliance, Method.Get, "ApplianceStatus", null, null, null, null); System.out.println(String.format("\t\\ApplianceStatus full response:")); logResponseDetails(fullResponse); - + applianceStatus = mapper.readValue(fullResponse.getBody(), SafeguardApplianceStatus.class); } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { @@ -1020,7 +1020,7 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS System.out.println("Test Join Sps failed: failed to get the Safeguard appliance information"); return; } - + String certChain = null; String sppAddress = null; for (SafeguardSslCertificate cert : sslCerts) { @@ -1034,7 +1034,7 @@ public void safeguardTestJoinSps(ISafeguardConnection sppConnection, ISafeguardS } } } - + try { sppConnection.JoinSps(spsConnection, certChain, sppAddress); } catch (ObjectDisposedException | SafeguardForJavaException | ArgumentException ex) { @@ -1047,9 +1047,9 @@ void safeguardTestManagementConnection(ISafeguardConnection connection) { System.out.println(String.format("Safeguard not connected. This test requires an annonymous connection.")); return; } - + String address = readLine("SPP address(management service): ", null); - + try { ISafeguardConnection managementConnection = connection.GetManagementServiceConnection(address); FullResponse response = managementConnection.invokeMethodFull(Service.Management, Method.Get, "ApplianceInformation", null, null, null, null); @@ -1058,24 +1058,24 @@ void safeguardTestManagementConnection(ISafeguardConnection connection) { } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test management connection failed: " + ex.getMessage()); } - + } - + public void safeguardTestAnonymousConnection(ISafeguardConnection connection) { - + if (connection == null) { System.out.println(String.format("Safeguard not connected")); return; } - + try { int remaining = connection.getAccessTokenLifetimeRemaining(); System.out.println(String.format("\tTime remaining: %d", remaining)); - + FullResponse fullResponse = connection.invokeMethodFull(Service.Notification, Method.Get, "Status", null, null, null, null); System.out.println(String.format("\t\\Appliance status:")); logResponseDetails(fullResponse); - + } catch (ArgumentException | ObjectDisposedException | SafeguardForJavaException ex) { System.out.println("\t[ERROR]Test connection failed: " + ex.getMessage()); } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java new file mode 100644 index 0000000..9049d3d --- /dev/null +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/ToolOptions.java @@ -0,0 +1,113 @@ +package com.oneidentity.safeguard.safeguardclient; + +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command(name = "SafeguardJavaTool", mixinStandardHelpOptions = true, + description = "Non-interactive CLI tool for testing SafeguardJava SDK") +public class ToolOptions { + + @Option(names = {"-a", "--appliance"}, required = true, + description = "IP address or hostname of Safeguard appliance") + String appliance; + + @Option(names = {"-x", "--insecure"}, defaultValue = "false", + description = "Ignore validation of Safeguard appliance SSL certificate") + boolean insecure; + + @Option(names = {"-p", "--read-password"}, defaultValue = "false", + description = "Read any required password from console stdin") + boolean readPassword; + + @Option(names = {"-u", "--username"}, + description = "Safeguard username to use to authenticate") + String username; + + @Option(names = {"-i", "--identity-provider"}, + description = "Safeguard identity provider to use for rSTS") + String identityProvider; + + @Option(names = {"-c", "--certificate-file"}, + description = "File path for client certificate") + String certificateFile; + + @Option(names = {"-t", "--thumbprint"}, + description = "Thumbprint for client certificate in cert store") + String thumbprint; + + @Option(names = {"-k", "--access-token"}, + description = "Pre-obtained access token for authentication") + String accessToken; + + @Option(names = {"-A", "--anonymous"}, defaultValue = "false", + description = "Do not authenticate, call API anonymously") + boolean anonymous; + + @Option(names = {"-s", "--service"}, + description = "Safeguard service: Core, Appliance, Notification, A2A") + String service; + + @Option(names = {"-m", "--method"}, + description = "HTTP method: Get, Post, Put, Delete") + String method; + + @Option(names = {"-U", "--relative-url"}, + description = "API endpoint relative URL") + String relativeUrl; + + @Option(names = {"-b", "--body"}, + description = "JSON body as string") + String body; + + @Option(names = {"-f", "--full"}, defaultValue = "false", + description = "Use InvokeMethodFull and output JSON envelope with StatusCode, Headers, Body") + boolean full; + + @Option(names = {"-H", "--header"}, split = ",", + description = "Additional HTTP headers as Key=Value pairs (comma-separated)") + String[] headers; + + @Option(names = {"-P", "--parameter"}, split = ",", + description = "Query parameters as Key=Value pairs (comma-separated)") + String[] parameters; + + @Option(names = {"-T", "--token-lifetime"}, defaultValue = "false", + description = "Output token lifetime remaining as JSON and skip API invocation") + boolean tokenLifetime; + + @Option(names = {"-G", "--get-token"}, defaultValue = "false", + description = "Output the current access token as JSON and exit") + boolean getToken; + + @Option(names = {"-L", "--logout"}, defaultValue = "false", + description = "Log out the connection (invalidate token) and output result as JSON") + boolean logout; + + @Option(names = {"--refresh-token"}, defaultValue = "false", + description = "Refresh the access token and output new token lifetime as JSON") + boolean refreshToken; + + @Option(names = {"--pkce"}, defaultValue = "false", + description = "Use PKCE (Proof Key for Code Exchange) authentication instead of password grant") + boolean pkce; + + @Option(names = {"-R", "--resource-owner"}, arity = "1", + description = "Enable (true) or disable (false) the resource owner password grant type and exit") + Boolean resourceOwner; + + @Option(names = {"-V", "--verbose"}, defaultValue = "false", + description = "Display verbose debug output") + boolean verbose; + + @Option(names = {"-F", "--file"}, + description = "File path for streaming upload (POST) or download (GET)") + String file; + + @Option(names = {"--sps"}, defaultValue = "false", + description = "Connect to Safeguard for Privileged Sessions (SPS) instead of SPP") + boolean sps; + + @Option(names = {"--interactive"}, defaultValue = "false", + description = "Run in interactive menu mode (legacy)") + boolean interactive; +} diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java index 1c966e3..a8d4e77 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardAppliance.java @@ -43,5 +43,5 @@ public String getIpv6Address() { public void setIpv6Address(String Ipv6Address) { this.Ipv6Address = Ipv6Address; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java index c33e0e6..3ef187a 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardApplianceStatus.java @@ -23,5 +23,5 @@ public String getName() { public void setName(String Name) { this.Name = Name; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java index bc8ad61..5e7f745 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SafeguardSslCertificate.java @@ -43,6 +43,6 @@ public SafeguardAppliance[] getAppliances() { public void setAppliances(SafeguardAppliance[] Appliances) { this.Appliances = Appliances; } - + } diff --git a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java index 6ed9b53..4e2a4b8 100644 --- a/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java +++ b/tests/safeguardjavaclient/src/main/java/com/oneidentity/safeguard/safeguardclient/data/SessionRecordings.java @@ -11,10 +11,10 @@ class SessionRecording { } public class SessionRecordings { - + @JsonProperty("items") public SessionRecording[] items; - + public String[] toArray() { List sessionIds = new ArrayList<>(); for(SessionRecording sr : items) { @@ -22,5 +22,5 @@ public String[] toArray() { } return sessionIds.toArray(new String[sessionIds.size()]); } - -} \ No newline at end of file + +}