Java SDK for the One Identity Safeguard for Privileged Passwords REST API. Published on
Maven Central
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).
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
|
|-- pipeline-templates/ # Azure Pipelines shared templates
| |-- global-variables.yml # Pipeline-level vars: semanticVersion, isPrerelease, versionSuffix
| |-- job-variables.yml # Job-level vars: version, targetDir, gpgKeyName
| `-- build-steps.yml # Parameterized build template (Maven + optional JAR signing)
|
|-- pom.xml # Maven build descriptor
|-- azure-pipelines.yml # CI/CD pipeline definition (two jobs)
|-- .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
Prerequisites: JDK 8+ and Maven 3.0.5+. Maven does not need to be on PATH.
# 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.xmlPowerShell note: When running Maven from PowerShell, -D flags get parsed by
PowerShell's parameter parser. You must quote them:
# 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.
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:
$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.
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:
mvn package -Dspotbugs.skip=trueSpotBugs exclusions are defined in spotbugs-exclude.xml. When adding new exclusions,
prefer fixing the code over suppressing the warning.
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.
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:
- Appliance address (IP or hostname of a Safeguard for Privileged Passwords appliance)
- Admin password (for the built-in
adminaccount — the bootstrap administrator) - (Optional) SPS appliance address (for Safeguard for Privileged Sessions tests)
- (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.
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 (
--pkceflag in the test tool) - Enable ROG via the appliance Settings API
# Build first (always build before testing)
mvn clean package "-Dspotbugs.skip=true"
# Run all suites
cd TestFramework
pwsh -File Invoke-SafeguardTests.ps1 `
-Appliance <address> -AdminPassword <password>
# Run with SPS tests
pwsh -File Invoke-SafeguardTests.ps1 `
-Appliance <address> -AdminPassword <password> `
-SpsAppliance <sps-address> -SpsPassword <sps-password>
# Run a specific suite by name
pwsh -File Invoke-SafeguardTests.ps1 `
-Appliance <address> -AdminPassword <password> `
-Suite PasswordAuth
# List available suites
pwsh -File Invoke-SafeguardTests.ps1 -ListSuitesImportant: 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
| 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 |
The test tool is a standalone Java CLI in tests/safeguardjavaclient/:
# 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 '<password>' | java -jar $jar -a <appliance> -u admin -p -x -s Core -m Get -U Me
# PKCE auth — GET Me endpoint
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x -s Core -m Get -U Me --pkce
# Anonymous — GET Notification Status
java -jar $jar -a <appliance> -x -s Notification -m Get -U Status -A
# Access token auth (provide token directly)
java -jar $jar -a <appliance> -x -s Core -m Get -U Me -T <access-token>
# Full response (includes StatusCode, Headers, Body)
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x -s Core -m Get -U Me -F
# POST with body
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x -s Core -m Post -U Users -B '{"Name":"test"}'
# Token operations
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --get-token
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --token-lifetime
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --refresh-token
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --logout
# SPS connection
echo '<password>' | java -jar $jar --sps <sps-address> -u admin -p -x -s Core -m Get -U "/api/configuration/management/email"
# Streaming download
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --download-stream Appliance/Backups/<id>
# Streaming upload
echo '<password>' | java -jar $jar -a <appliance> -u admin -p -x --upload-stream Appliance/Backups/Upload --upload-file backup.sgbWhen 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) |
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.
The appliance exposes Swagger UI for each service at:
https://<appliance>/service/core/swagger— Core service (assets, users, policies, requests)https://<appliance>/service/appliance/swagger— Appliance service (networking, diagnostics, backups)https://<appliance>/service/notification/swagger— Notification service (status, events)https://<appliance>/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.
The static Safeguard class is the SDK's public entry point. All SDK usage starts through
static factory methods:
Safeguard.connect(...)— CreatesISafeguardConnectioninstances. Multiple overloads support password, certificate (keystore/file/thumbprint/byte array), and access token authentication.Safeguard.A2A.getContext(...)— CreatesISafeguardA2AContextfor 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 aPersistentSafeguardConnectionthat auto-refreshes tokens.SafeguardForPrivilegedSessions.Connect(...)— CreatesISafeguardSessionsConnectionfor Safeguard for Privileged Sessions (SPS).
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 |
All authenticators implement IAuthenticationMechanism. When adding a new authentication
method:
- Implement
IAuthenticationMechanismin theauthentication/package - Add
Safeguard.connect()overload(s) inSafeguard.java - Follow the pattern of existing authenticators (extend
AuthenticatorBase)
SafeguardConnection— BaseISafeguardConnectionimplementation. Makes HTTP calls viainvokeMethod()/invokeMethodFull().PersistentSafeguardConnection— Decorator that checksgetAccessTokenLifetimeRemaining() <= 0before each call and auto-refreshes tokens.
All authenticators obtain tokens via the embedded Safeguard RSTS (Resource Security Token
Service) at https://{host}/RSTS/oauth2/token.
- Password authentication uses the
passwordgrant 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 (
PasswordAuthenticatorwithusePkce=true) drives the rSTS login controller at/RSTS/UserLogin/LoginControllerprogrammatically, 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_credentialsgrant 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.
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/JsonObjecttypes (transitive from the SignalR Java client'sGsonHubProtocol).
Certificate-only authentication for automated credential retrieval. Key types:
ISafeguardA2AContext, A2ARegistration, BrokeredAccessRequest.
Integration with Safeguard for Privileged Sessions. ISafeguardSessionsConnection /
SafeguardSessionsConnection. Connects using basic auth (username/password) over HTTPS.
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.
Every public type has a corresponding I-prefixed interface (ISafeguardConnection,
ISafeguardA2AContext, ISafeguardEventListener, IAuthenticationMechanism). Code
against interfaces, not implementations.
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:
if (disposed) {
throw new ObjectDisposedException("ClassName");
}Follow this pattern in any new connection or context class.
- Parameter validation throws
ArgumentException - HTTP failures throw
SafeguardForJavaExceptionwith status code and response body - Null HTTP responses throw
SafeguardForJavaException("Unable to connect to ...") - Disposed object access throws
ObjectDisposedException
ignoreSsl=trueusesNoopHostnameVerifier(development only)- Custom
HostnameVerifiercallback 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
ignoreSslfor production without explicit warning
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.
- Java standard naming: camelCase for fields/methods, PascalCase for classes
- Interfaces use
Iprefix:ISafeguardConnection,IAuthenticationMechanism - Constants: UPPER_SNAKE_CASE
- Package:
com.oneidentity.safeguard.safeguardjava
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.
The project uses Azure Pipelines (azure-pipelines.yml) with shared templates in
pipeline-templates/.
The pipeline has two jobs, matching the SafeguardDotNet pattern:
| Job | Runs when | What it does |
|---|---|---|
| PRValidation | Pull requests only | mvn package — compile, lint, SpotBugs |
| BuildAndPublish | Merges to master/release-* |
Build, GPG sign, JAR sign, publish to Maven Central + GitHub Packages, create GitHub Release |
Both jobs share build-steps.yml with different parameters. PRValidation uses defaults
(package, no signing); BuildAndPublish passes deploy, release profile flags, and
signJars: true.
| File | Scope | Contents |
|---|---|---|
global-variables.yml |
Pipeline-level | semanticVersion, isPrerelease, versionSuffix |
job-variables.yml |
Job-level | version (composed), targetDir, gpgKeyName |
build-steps.yml |
Steps template | Maven task + optional Docker JAR signing + artifact publishing |
Version is composed from runtime variables: $(semanticVersion).$(Build.BuildId)$(versionSuffix)
- When
isPrerelease: 'true'→versionSuffixis-SNAPSHOT→ e.g.8.2.0.355537-SNAPSHOT - When
isPrerelease: 'false'→versionSuffixis empty → e.g.8.2.0.355537
The pom.xml uses <version>${revision}</version> with CI-friendly properties. The actual
version is always injected via -Drevision=$(version) on the Maven command line.
| Service connection | Key vault | Secrets |
|---|---|---|
SafeguardOpenSource |
SafeguardBuildSecrets |
SonatypeUserToken, SonatypeRepositoryPassword, GpgCodeSigningKey, SigningStorePassword, GitHubPackagesToken |
OneIdentity.Infrastructure.SPPCodeSigning |
SPPCodeSigning |
SPPCodeSigning-Password, SPPCodeSigning-TotpPrivateKey |
PangaeaBuild-GitHub |
(none) | GitHub service connection for Release creation |
The pipeline uses two independent signing mechanisms that serve different purposes:
1. GPG signing (maven-gpg-plugin) — Produces .asc detached signature files required
by Maven Central for release validation. Configured in the release Maven profile.
- GPG private key: vault secret
GpgCodeSigningKey, imported viagpg --batch --import - GPG passphrase: vault secret
SigningStorePassword settings/settings.xmlmaps the passphrase via a server entry:<id>${gpgkeyname}</id>+<passphrase>${signingkeystorepassword}</passphrase>- Maven command line needs both
-Dsigningkeystorepassword=$(SigningStorePassword)and-Dgpgkeyname=$(gpgKeyName) - The plugin uses
--batch --pinentry-mode loopbackfor non-interactive passphrase input - CRITICAL: The GPG plugin is bound to the
verifylifecycle phase.mvn packagestops at thepackagephase and never triggers GPG signing. Onlymvn deploy(ormvn verify/mvn install) reaches the GPG plugin. This means you cannot test GPG signing withmvn package -P release— it will succeed silently without signing.
2. JAR code signing (SSL.com CodeSigner Docker image) — Embeds certificates in
META-INF/ (CERT.SF, CERT.RSA) inside the JAR for Java runtime signature verification.
- Uses Docker image
ghcr.io/sslcom/codesigner:latestwhich bundles its own JRE - eSigner account:
ssl.oid.safeguardpp@groups.quest.com - Credentials:
SPPCodeSigning-Password(password) +SPPCodeSigning-TotpPrivateKey(TOTP) - The
-overrideflag signs the JAR in place (no separate output file) ENVIRONMENT_NAME=PRODis required for production signing (vs sandbox)- No
CREDENTIAL_IDis needed when the eSigner account has only one certificate - Runs as a post-build step after Maven completes, not as a Maven plugin
Do NOT attempt to run CodeSignTool directly on the build agent. The standalone CodeSignTool v1.3.2 is compiled for Java 11 and has strict JVM compatibility requirements:
- Java 8 →
UnsupportedClassVersionError(class file version 55.0, needs Java 11+) - Java 11 → Works, but requires a separate JDK install on the build agent
- Java 17+ →
IllegalAccessErrordue to JPMS module access restrictions
The Docker image is the correct approach — it bundles a compatible JRE inside the
container, isolating CodeSignTool from the host Java version entirely. The build agent
only needs Docker installed (which Azure Pipelines ubuntu-latest provides by default).
Publishing uses the central-publishing-maven-plugin v0.7.0 with autoPublish: false
and waitUntil: validated. The plugin's publishingServerId: central maps to the
<server id="central"> entry in settings/settings.xml.
SNAPSHOT publishing requires:
central-publishing-maven-pluginv0.7.0+ (earlier versions don't support SNAPSHOTs)- Explicit enablement per namespace at central.sonatype.com → Namespaces → dropdown → "Enable SNAPSHOTs"
- Without enablement, SNAPSHOT deploys return 403 Forbidden
- SNAPSHOTs are not validated, can be overwritten, and are cleaned up after ~90 days
Uses mvn deploy:deploy-file with explicit artifact coordinates. Do NOT use
-DpomFile=pom.xml — the deploy-file goal reads the pom literally without resolving
Maven properties. Since our pom has <version>${revision}</version>, it would see
${revision} as the literal version string (invalid characters: $, {, }). Instead,
specify coordinates directly: -DgroupId=... -DartifactId=... -Dversion=... -Dpackaging=jar.
Authentication uses a <server id="github"> entry in settings/settings.xml with
${githubusername} and ${githubtoken} properties, resolved via -D flags on the
command line.
Azure Pipelines has two expression syntaxes with very different scoping rules:
${{ variables.X }}— Compile-time expression. Can only see variables defined in the same template file. Variables from other templates are invisible at compile time, even if they are in the same pipeline.$(X)— Runtime macro. Resolves after all variable scopes (pipeline + job) are merged. Works across template boundaries.
This means:
${{ if eq(variables.isPrerelease, 'true') }}injob-variables.ymlcannot seeisPrereleasedefined inglobal-variables.yml— it will always evaluate to false.- The fix: compute dependent values (like
versionSuffix) in the same template as the variables they reference, then use$(versionSuffix)runtime macros elsewhere. - Also note:
${{ true }}as a variable value becomes the string'True'(capital T). Use the string'true'(lowercase) to avoid comparison mismatches.
When modifying the CI/CD pipeline:
- Test GPG signing with
mvn deploy, notmvn package(GPG is inverifyphase) - Never use
-DpomFile=pom.xmlwithdeploy:deploy-file— use explicit coordinates - Keep compile-time conditionals in the same template as the variables they reference
- Use string
'true'/'false'for boolean pipeline variables, not${{ true }} - Verify CodeSignTool via Docker only — never install it directly on the build agent
- All credentials flow through
settings/settings.xmlvia Maven property placeholders resolved by-Dflags — Azure DevOps auto-masks vault secrets in logs
Create TestFramework/Suites/Suite-YourFeature.ps1 returning a hashtable:
@{
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.
}
}| 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 |
All test objects should use $Context.TestPrefix (default: SgJTest) to avoid collisions:
$userName = "$($Context.TestPrefix)_MyTestUser"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
If a test requires functionality not exposed by the CLI test tool:
- Add the CLI option in
tests/safeguardjavaclient/.../ToolOptions.java - Add the handling logic in
tests/safeguardjavaclient/.../SafeguardJavaClient.java - Add framework support in
TestFramework/SafeguardTestFramework.psm1if needed - Rebuild the test tool before running tests
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:
cd Samples/PasswordConnect
mvn package
java -jar target/PasswordConnect-1.0.jar