Skip to content

NMS-19751: Support for Token Based Authentication with XML/JSON Collector (and HTTP collector)#8495

Merged
marshallmassengill merged 27 commits into
release-36.xfrom
develop-xml-dynamic-auth-smoke
May 22, 2026
Merged

NMS-19751: Support for Token Based Authentication with XML/JSON Collector (and HTTP collector)#8495
marshallmassengill merged 27 commits into
release-36.xfrom
develop-xml-dynamic-auth-smoke

Conversation

@marshallmassengill
Copy link
Copy Markdown
Contributor

@marshallmassengill marshallmassengill commented Apr 30, 2026

This adds support for token-based authentication against external HTTP APIs during data collection. Operators describe an API login flow once in etc/token-auth-configuration.xml and reference the resulting token from any XML/JSON or HTTP collector header via the ${token:} metadata placeholder. The runtime performs the login call, caches the token, refreshes on TTL, invalidates on 401/403, and supports reload via the standard daemon-reload event.

The motivating use case is vendor APIs like Cisco Catalyst Center, F5 BIG-IP, and VMware vSphere, where the collectors themselves can't perform a login flow.

What's in here:

  • JAXB model + XSD for token-auth-configuration.xml with three token-extraction modes (jsonpath, response header, body-as-token)
  • TokenAcquirer, TokenCache, TokenProviderImpl runtime, plus a TokenAuthCollectorAdaptor that resolves ${token:} placeholders on the controller before the collector dispatch
  • Two adaptor entry points so ${token:...} works wherever auth headers live:
  • beforeCollect substitutes inside the basic collection-parameter map (e.g. collectd-configuration.xml values)
  • beforeRuntimeInterpolation substitutes inside the JAXB-deserialized runtime-attribute tree (e.g. xml-datacollection-config.xml values, http-datacollection-config.xml values). Mate's standard pass then resolves the remaining ${node:...}, ${scv:...}, ${env:...} etc. without touching core/mate.
  • Per-node placeholder support inside blocks: ${node:label}, ${scv:alias:key}, ${env:NAME} all resolve before the auth call. Cache identity widens to (auth-name, SHA-256 fingerprint of resolved fields) so one auth definition can serve many regional endpoints with one cache entry per distinct resolution.
  • Early-refresh window of max(600s, 5% of ) on the cache so the controller never hands back a token within ten minutes of upstream expiry.
  • Passive invalidation on 401/403: the failing request isn't retried in-cycle; the adaptor invalidates the matching cache entry, and the next scheduled collection re-acquires.
  • Reload listener: opennms:send-event -p daemonName=TokenAuth uei.opennms.org/internal/reloadDaemonConfig flushes the cache and re-reads the file; reload failures publish reloadDaemonConfigFailed and leave the previous config in effect.
  • Custom block on in http-datacollection-config.xml so Authorization: Bearer ${token:foo} works in the HTTP collector too.
  • Reference docs at Operation → Deep Dive → Administration → Configuration → Collector Token Authentication (docs/.../token-authentication.adoc).

Validated end-to-end against Hashicorp Vault (auth/client_token JSON), PocketBase (superuser token JSON), and WireMock — including a Cisco Catalyst Center-style stub that exchanges HTTP Basic for a
Token JSON and gates data endpoints behind X-Auth-Token. Each test exercises SCV-resolved credentials plus per-node metadata in the auth definition (URL path, basic-auth, request body, and request
headers).

I'd love feedback on this. We were asked by a potential customer the other day about our capability to scrape some specific endpoints that use this style of authentication and I think this fills a
pretty big gap for us in the XML/JSON Collector today.

Assisted-by: Claude Code:Opus 4.7

@marshallmassengill
Copy link
Copy Markdown
Contributor Author

So this is passing except for the WmiPeerFactoryTest in CircleCI as far as I can tell. There were indeed some integration test failures that I needed to fix. They should be now.

@dino2gnt
Copy link
Copy Markdown
Contributor

So this is passing except for the WmiPeerFactoryTest in CircleCI as far as I can tell.

The WmiPeerFactoryTest is failing to instantiate the EntityScopeProvider, which seems like it could be related to your metadata changes.

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.context.support.ClassPathXmlApplicationContext]: Constructor threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityScopeProvider' defined in class path resource [test-dao-context.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.*******.core.mate.api.EntityScopeProvider]: Specified class is an interface

@marshallmassengill
Copy link
Copy Markdown
Contributor Author

I'll give it a look again.

@marshallmassengill
Copy link
Copy Markdown
Contributor Author

Integration tests all seem to be passing now. There were definitely a couple things in there. I've made a note to myself to run those locally first now.

@dino2gnt
Copy link
Copy Markdown
Contributor

dino2gnt commented May 1, 2026

Smooth-brain thought: Couldn't you accomplish this same thing using a shell script in cron that does a curl and feeds the token response to scvcli?

@marshallmassengill
Copy link
Copy Markdown
Contributor Author

Smooth-brain thought: Couldn't you accomplish this same thing using a shell script in cron that does a curl and feeds the token response to scvcli?

I think that can/will work... but there are some misses to me... It could definitely cover a single long lived token use case... I can't argue that really but I don't think it covers the shorter lived ones as nicely.

That being said, I think the biggest issue is that any 401/403 recoveries have to happen on the cron schedule and can't happen in-line with a response (granted, I guess you could use the failed collection in OpenNMS and have it do that with scripting but then you've got 3 things to mangle).

There is also something to be said that this works with metadata (and SCV) whereas cron wouldn't so you could do this across a fleet of systems instead of having to probably create a separate script for each one and having to manage other secure vault stuff.

I also think this keeps everything in OpenNMS and that wayt tokens live in memory (RAM) as a result so I think that fits with the idea of tokens being short lived and not meant to be permanent (semi-permanent?) and stored in a vault.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a dynamic-auth runtime to OpenNMS that can acquire/cache/refresh tokens from external HTTP login flows defined in etc/auth-configuration.xml, expose them via the metadata DSL ${auth:<name>}, and enable collector retry-on-401/403 behavior (XML/JSON collectors and HTTP collector headers).

Changes:

  • Introduces auth-configuration.xml schema/model, parsing/validation, token acquisition and caching runtime, and a reload listener to re-read config + flush cache.
  • Wires ${auth:<name>} into the metadata DSL via a new AuthScope, TokenProvider API, and EntityScopeProvider integration.
  • Adds collector-side support: HTTP collector <headers> block, and 401/403 retry logic in XML/JSON collectors (with token invalidation + refresh).

Reviewed changes

Copilot reviewed 51 out of 52 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
protocols/xml/src/test/java/org/opennms/protocols/xml/collector/RefreshAuthTokensTest.java Adds unit tests for request-header token refresh behavior on retry.
protocols/xml/src/main/java/org/opennms/protocols/xml/collector/AbstractXmlCollectionHandler.java Adds 401/403 retry-once path and token refresh/invalidation logic for XML collector requests.
protocols/xml/src/main/java/org/opennms/protocols/json/collector/AbstractJsonCollectionHandler.java Adds 401/403 retry-once path using shared token refresh logic for JSON collector requests.
protocols/xml/src/main/java/org/opennms/protocols/http/HttpUrlConnection.java Throws typed AuthFailureException on 401/403 to enable auth-aware retry upstream.
protocols/xml/src/main/java/org/opennms/protocols/http/AuthFailureException.java Adds typed exception carrying HTTP status for auth failures.
opennms-config/src/test/resources/JUnitHttpServer.keystore Adds test keystore for HTTPS acquisition tests.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/TokenProviderImplTest.java Unit tests for TokenProviderImpl behaviors.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/TokenCacheTest.java Unit tests for token caching, expiry, invalidation, and scope-fingerprinting behavior.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/TokenAcquirerTest.java Tests token acquisition and extraction modes (jsonpath/header/body-as-token) plus placeholder interpolation.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/TokenAcquirerHttpsTest.java Verifies disable-SSL-verification behavior against self-signed certs.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/AuthRuntimeIntegrationTest.java Integration test covering the full dynamic-auth stack composition.
opennms-config/src/test/java/org/opennms/netmgt/config/auth/AuthConfigReloadListenerTest.java Tests daemon reload event handling and cache flush behavior.
opennms-config/src/test/java/org/opennms/netmgt/config/AuthConfigFactoryTest.java Tests parsing/validation rules for auth-configuration.xml.
opennms-config/src/main/resources/META-INF/opennms/component-dao.xml Imports auth runtime Spring context and registers auth-configuration.xml as a JAXB config resource.
opennms-config/src/main/resources/META-INF/opennms/applicationContext-auth.xml Wires TokenAcquirer/TokenCache/TokenProvider + OSGi service + reload listener.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/TokenProviderImpl.java Implements TokenProvider backed by AuthConfigFactory + TokenCache.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/TokenCache.java Adds in-memory token cache keyed by (authName, resolved fingerprint), plus reverse-lookup invalidation.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/TokenAcquirerScopeBinder.java Late-binds EntityScopeProvider onto TokenAcquirer when available to avoid wiring cycles.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/TokenAcquirer.java Implements HTTP login flow execution, placeholder interpolation, token extraction, and fingerprinting.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/CachedToken.java Introduces token value + optional expiry container for caching.
opennms-config/src/main/java/org/opennms/netmgt/config/auth/AuthConfigReloadListener.java Implements daemon-reload listener that reloads auth config and flushes token cache.
opennms-config/src/main/java/org/opennms/netmgt/config/AuthConfigFactory.java Adds singleton config loader/validator for auth-configuration.xml.
opennms-config/pom.xml Adds runtime deps needed for auth acquisition (httpclient, core.web wrapper, jackson-databind).
opennms-config-tester/src/test/java/org/opennms/netmgt/config/tester/ConfigTesterTest.java Ensures config-tester validates auth-configuration.xml.
opennms-config-tester/src/main/resources/META-INF/opennms/applicationContext-configTester.xml Wires AuthConfigFactory for config-tester context.
opennms-config-model/src/test/java/org/opennms/netmgt/config/httpdatacollection/HttpDatacollectionConfigTest.java Updates HTTP DC model test to include new <headers> element.
opennms-config-model/src/test/java/org/opennms/netmgt/config/auth/AuthConfigurationTest.java Adds JAXB/XSD round-trip tests for new auth config model.
opennms-config-model/src/main/resources/xsds/http-datacollection-config.xsd Adds <headers> block support under <url> for HTTP collector.
opennms-config-model/src/main/resources/xsds/auth-configuration.xsd Adds schema for auth-configuration.xml.
opennms-config-model/src/main/java/org/opennms/netmgt/config/httpdatacollection/Url.java Adds headers list to HTTP DC Url model.
opennms-config-model/src/main/java/org/opennms/netmgt/config/httpdatacollection/Header.java Adds new HTTP DC header model type.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/package-info.java Defines JAXB namespace for new auth config model package.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/TokenFrom.java Adds token extraction configuration model.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/Header.java Adds auth-request header model type.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/Content.java Adds auth-request content/body model type.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/BasicAuth.java Adds basic-auth model for auth request.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/AuthConfiguration.java Adds top-level auth configuration model.
opennms-config-model/src/main/java/org/opennms/netmgt/config/auth/Auth.java Adds per-auth definition model (URL/method/headers/content/token-from/TTL/flags).
opennms-base-assembly/src/main/filtered/etc/auth-configuration.xml Adds default stub config file shipped in base assembly.
integration-tests/config/src/test/java/org/opennms/netmgt/config/WillItUnmarshalIT.java Adds unmarshal IT coverage for auth-configuration.xml.
features/collection/collectors/src/test/java/org/opennms/netmgt/collectd/HttpCollectorHeaderTest.java Adds focused unit tests for applying configured headers to outgoing HTTP requests.
features/collection/collectors/src/main/java/org/opennms/netmgt/collectd/HttpCollector.java Applies configured URL headers to outgoing requests via new helper.
core/mate/model/src/main/java/org/opennms/core/mate/model/EntityScopeProviderImpl.java Wires optional TokenProvider to provide AuthScope in entity scope chains.
core/mate/api/src/test/java/org/opennms/core/mate/api/AuthScopeTest.java Adds unit tests for AuthScope behavior.
core/mate/api/src/test/java/org/opennms/core/mate/api/AuthScopeInterpolatorTest.java Adds tests confirming ${auth:...} flows through Interpolator with AuthScope.
core/mate/api/src/main/java/org/opennms/core/mate/api/TokenProvider.java Introduces TokenProvider API used by metadata DSL auth namespace.
core/mate/api/src/main/java/org/opennms/core/mate/api/Interpolator.java Tracks calling scope stack for auth placeholder resolution to inherit per-node context.
core/mate/api/src/main/java/org/opennms/core/mate/api/EntityScopeProvider.java Adds default getScopeForAuth() hook for ${auth:...} support.
core/mate/api/src/main/java/org/opennms/core/mate/api/AuthScope.java Adds new metadata DSL scope implementation for ${auth:<name>}.
core/lib/src/main/java/org/opennms/core/utils/ConfigFileConstants.java Registers auth-configuration.xml in config file constants mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/mate/api/src/main/java/org/opennms/core/mate/api/Interpolator.java Outdated
Comment thread opennms-config/src/main/java/org/opennms/netmgt/config/auth/TokenAcquirer.java Outdated
marshallmassengill added a commit to marshallmassengill/opennms that referenced this pull request May 1, 2026
Seven follow-ups from the PR OpenNMS#8495 review pass:

- Interpolator: when scope is null, substitute EmptyScope.EMPTY
  before recursing so inputs that contain placeholders no longer
  NPE inside interpolateSingle when scope.get(...) is invoked.
  Plain non-templated inputs still fall through unchanged.

- AuthConfigFactory.reload: parse and validate into a fresh factory
  first, then swap m_singleton on success. A failed reload now
  leaves the previously-loaded configuration in effect, matching
  the behavior documented in auth-configuration.adoc.

- AbstractXmlCollectionHandler.refreshAuthTokensInRequest: two-pass
  refresh. First pass collects all distinct (oldToken -> freshToken)
  pairs by probing one header at a time and skipping headers
  already covered by a queued swap; second pass applies the swaps
  across every matching header. A request that uses the same
  ${auth:...} in multiple headers (Authorization plus X-Auth-Token,
  for example) now refreshes all of them before the retry instead
  of only the first.

- TokenAcquirer: take an injectable Clock so the cached-token
  expiry timestamp shares a time source with TokenCache. Production
  wiring stays on Clock.systemUTC(); test fixtures can pass a fake
  clock to make TTL behavior fast-forwardable.

- TokenProviderImpl: fix log message typo ${{auth:...}} ->
  ${auth:...}.

- TokenAcquirerHttpsTest: capture the original default
  HostnameVerifier in @BeforeClass and install Apache HttpClient's
  strict DefaultHostnameVerifier for the duration of the class,
  then restore in @afterclass. The negative test now runs against
  a known-strict verifier regardless of what other tests in the
  same surefire fork did to the global default.

- HttpCollector.applyConfiguredHeaders: guard against null Header
  entries and null header values, mirroring the existing name
  guard so a partially populated Header from programmatic
  construction can no longer reach method.setHeader(name, null).
@marshallmassengill
Copy link
Copy Markdown
Contributor Author

@cgorantla I added the minion 401/403 feature. Wasn't too bad to add but it does alter the HTTPCollector a bit given that those need to be retries but I could not come up with a reason someone would be using that collector for testing that and there is an alternative through PSM. Tests all seem to be passing. Let me know about feedback. I can adjust accordingly.

Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed few modules. Need to actually validate this by running this code.

Comment thread core/mate/api/src/main/java/org/opennms/core/mate/api/Interpolator.java Outdated
Comment thread core/mate/api/src/main/java/org/opennms/core/mate/api/Interpolator.java Outdated
@marshallmassengill marshallmassengill marked this pull request as draft May 11, 2026 12:25
@marshallmassengill
Copy link
Copy Markdown
Contributor Author

Converted to draft for now. Working on a revised plan based on the feedback.

Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added few more comments on what I think should be considered.

Comment thread core/mate/api/src/main/java/org/opennms/core/mate/api/Interpolator.java Outdated
@marshallmassengill marshallmassengill force-pushed the develop-xml-dynamic-auth-smoke branch from af53eb0 to d408642 Compare May 12, 2026 18:47
@marshallmassengill marshallmassengill changed the base branch from develop to release-36.x May 12, 2026 18:47
@marshallmassengill
Copy link
Copy Markdown
Contributor Author

This PR has been rewritten to address issues raised:

  • "Don't update core modules like metadata and RPC collections for the small benefit" — no changes to core/mate/api/Interpolator, EntityScopeProviderImpl, CollectorClientRpcModule, or CollectorResponseDTO.
  • "Consider similar thing like ServiceMonitorAdaptor for collectors" — new CollectorAdaptor interface in features/collection/api modeled on ServiceMonitorAdaptor. Token-auth logic lives in a single CollectorTokenAuthAdaptor class that runs on the OpenNMS controller (never on the Minion).
  • "BeanUtils.getBean will not work on Minion" — token resolution happens on the controller before RPC dispatch. The Minion sees already-substituted parameter values; no token-aware code on the Minion side.
  • "Interpolator ThreadLocal recursive lookup — clever but not intuitive" — removed entirely. The auth runtime no longer participates in the metadata DSL scope chain.
  • "ApplicationListener doesn't fit OpenNMS's multi-context model" — replaced with standard Spring DI of EventIpcManager plus an init-method, same pattern as Collectd/Statsd/etc.
  • "TokenAcquirerScopeBinder is a hacky workaround" — class deleted; the binding cycle it papered over is gone after AuthScope removal.

Other changes:

  • Feature renamed to "Collector Token Authentication" to match the docs page title. Placeholder syntax is now ${token:}. Config file is etc/collector-token-auth-configuration.xml. Daemon-reload name is CollectorTokenAuth.
  • PR base changed from develop to release-36.x
  • Passive cache invalidation on the adaptor: when the controller sees a FAILED CollectionSet whose request used a token, it invalidates that cache entry. Next cycle re-acquires naturally. No retry orchestration.

I'm going to kick off a copilot review @cgorantla but will wait to address anything until you've looked this over.

I also still need to run integration tests locally.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 43 out of 44 changed files in this pull request and generated 17 comments.

Define the user-facing config for token-based authentication against
external HTTP APIs used by the XML/JSON and HTTP collectors.

The schema covers:
- Token-auth definitions with login URL, method, basic-auth, content,
  headers
- Three token-extraction modes: jsonpath, response header, body-as-token
- Optional TTL for cache expiry; cache identity is widened at runtime by
  a fingerprint of the resolved request fields so one definition can
  serve many endpoints

This commit adds only the JAXB types, XSD, sample
etc/collector-token-auth-configuration.xml, and the ConfigFileConstants
entry. Runtime (factory, acquirer, cache, provider) and collector
integration land in follow-up commits.
The previous adaptor scanned only the flat collection-parameter map,
which made ${token:NAME} work for collectd-configuration.xml
<parameter> values but not for the placeholders customers actually
write -- headers inside xml-datacollection-config.xml /
http-datacollection-config.xml <xml-source>/<request>/<header> blocks.
Those headers travel as JAXB-deserialized runtime attributes, get
processed only by Mate's Interpolator, and unknown prefixes are
stripped to empty -- so the documented use case shipped broken.

CollectorAdaptor gains a beforeRuntimeInterpolation hook that runs
before Interpolator.interpolateAttributes. Adaptors that own a custom
${prefix:...} placeholder now have a chance to substitute on the
runtime-attribute tree first; Mate's standard pass then resolves the
remaining prefixes it knows. No changes to core/mate; the new
substitution is contained in the adaptor.

TokenAuthCollectorAdaptor's implementation walks each value:
  - String values: regex-substitute directly.
  - @XmlRootElement values: round-trip via JaxbUtils.marshal +
    regex-substitute + JaxbUtils.unmarshal, mirroring how Mate's own
    Interpolator.interpolate(Object, Scope) handles JAXB types.
  - Mate ToBeInterpolated wrappers (Interpolator.pleaseInterpolate)
    are unwrapped reflectively, the inner value is processed, and the
    result is re-wrapped with pleaseInterpolate so Mate's standard
    pass continues to recurse into it.

Names resolved during the runtime-attribute walk are merged into
AUTH_NAMES_USED_PARAM alongside anything beforeCollect already
stashed, so handleCollectionResult invalidates the right cache
entries on FAILED collections via either path.

Five new TokenAuthCollectorAdaptorTest cases cover the three value
shapes and the empty / no-placeholder / name-merge paths.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 47 changed files in this pull request and generated 6 comments.

- TokenAuthConfigFactoryTest, TokenAuthConfigReloadListenerTest:
  test XML used the stale /xsd/config/auth namespace; under that
  namespace JAXB unmarshalled an empty config and the assertions
  were exercising malformed input. Swap to the real
  /xsd/config/token-auth namespace.

- Delete the unused TokenAuthFailureException class.
  HttpUrlConnection never threw it and nothing else referenced it;
  401/403 handling lives on the HttpCollector path via
  HttpCollectorException, and the XML/JSON path surfaces failures
  through a FAILED CollectionSet so the adaptor invalidates on
  the next cycle.

- TokenAcquirer.fingerprint(): TokenAuth.getMethod() returns
  DEFAULT_METHOD ("POST") when <method> is omitted, so the
  null-coalesce in the fingerprint was dead and the comment
  copilot raised about implicit-vs-explicit POST hashing
  differently never actually happened. Drop the redundant guard
  and call out the model default in a comment.

- TokenAcquirerTest.acquireSetsExpiresAt: the original assertion
  message implied expiresAt should be roughly ttl seconds ahead.
  TokenAcquirer intentionally subtracts the early-refresh buffer
  (max(600s, 5% of ttl)) -- for ttl=900 the effective remaining
  time is ~300s. Update the comment and message to describe the
  actual contract; bounds stay as-is since they already pass.

- Url.equals/hashCode: include m_headers. Two Url values that
  differ only by their <headers> block were comparing equal,
  which would silently merge them in any value-equality cache or
  collection.
Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested few comments, I still need to go through whole code one more time

It is difficult to go through the code as there is too much commenting for every method.

Only add comments that are strictly necessary.

Also lot of places, package naming is expanded like java.util.List<org.opennms.netmgt.collection.api.CollectorAdaptor> which should be avoided.

Comment thread opennms-services/src/main/java/org/opennms/netmgt/collectd/Collectd.java Outdated
Comment thread opennms-services/src/main/java/org/opennms/netmgt/collectd/Collectd.java Outdated
- Add Interpolator.isPleaseInterpolate / unwrap helpers; drop the
  reflection on Interpolator$ToBeInterpolated in
  TokenAuthCollectorAdaptor.
- Wire TokenAuthConfigFactory directly via Spring (mirroring
  applicationContext-configTester.xml); remove the Supplier indirection
  and lenientFactoryLookup from TokenProviderImpl.
- Move test helpers in TokenAuthConfigFactory to the bottom of the
  class with @VisibleForTesting; collapse clearForTest into
  resetForTesting.
- Import StandardCharsets / Clock / Security; replace fully-qualified
  type names with imports across CollectionSpecification, Collectd,
  TokenAcquirer, HttpCollector, and TokenAuthConfigReloadListener.
- Strip repetitive method-level Javadoc and inline prose; keep the
  SHA-256 fingerprint rationale, the substring-match guard, the
  ConcurrentHashMap.compute caveat, and the late-binding listener note.
- Remove all comment blocks from applicationContext-token-auth.xml.
Missed in the previous sweep — the seven java.util.List.of(...) call
sites in the test file now use the imported short name.
When component-dao.xml imports applicationContext-token-auth.xml, the
MethodInvokingFactoryBean fires TokenAuthConfigFactory.init() at Spring
context load. In test contexts without an opennms.home (or without a
staged etc/token-auth-configuration.xml), getFile() throws
FileNotFoundException and the whole context fails to load -- breaking
ITs like LiquibaseUpgraderIT that legitimately need component-dao.xml
but have no use for token-auth.

Catch FileNotFoundException in init()/reload(), log INFO, and start
the singleton with an empty TokenAuthConfiguration. Production
behaviour is unchanged because opennms-base-assembly always ships the
file; a missing definition is still reported explicitly at first
${token:NAME} use.
…orAdaptor

Three gaps closed so token-auth invalidation actually fires end-to-end:

- features/collection/shell-commands/CollectCommand: bind every OSGi
  CollectorAdaptor on the request builder before .execute(), so
  `opennms:collect` resolves ${token:NAME} the same way the scheduled
  path does.

- features/collection/client-rpc/CollectorRequestBuilderImpl: replace
  thenApply with handle so adaptors run on both success and exceptional
  completion. On a failure, synthesize a FAILED CollectionSet, run the
  adaptors against it, then rethrow the original exception. Extracted
  the merge logic into a public static applyAdaptorsAndPropagate for
  direct testing.

- protocols/xml/HttpUrlConnection.getInputStream(): check the HTTP
  response status and throw IOException on >=400. Apache HttpClient
  otherwise returns the error body as a normal InputStream, which made
  401s look like valid (empty) collections and silently kept stale
  tokens cached.

Tests:
- CollectCommandAdaptorTest (shell command picks up OSGi adaptors)
- CollectorRequestBuilderImplTest (handle() runs adaptors on success
  and exception, FAILED synthesized, original exception rethrown,
  CompletionException unwrapped)
- HttpUrlConnectionAuthFailureTest (200 returns body, 401/500 throw)
- TokenAuthInvalidationIntegrationTest (cross-module: real
  TokenAuthCollectorAdaptor + applyAdaptorsAndPropagate together
  invalidate the right names on a thrown collection)

Doc: token-authentication.adoc now flags that a FAILED status from a
non-auth cause (RPC timeout, broker outage, transport error) also
invalidates the cached token. Benign at most one extra acquire per
transient cycle.
The TokenAuthInvalidationIntegrationTest added in the previous commit
pulled opennms-config (test scope) -> features.collection.client-rpc.
Combined with the existing
client-rpc -> core.ipc.rpc.utils -> core.mate.model -> dao-mock -> opennms-config
chain, that produced a reactor cycle that broke `mvn install` on CI.

Drop the test-scope dep and remove the integration test. The unit
coverage was already complete piecewise:
  - CollectorRequestBuilderImpl.applyAdaptorsAndPropagate is exercised
    by CollectorRequestBuilderImplTest (FAILED synthesis on exception,
    rethrow, CompletionException unwrap, adaptor invocation order).
  - TokenAuthCollectorAdaptor.handleCollectionResult is exercised by
    TokenAuthCollectorAdaptorTest (invalidate-on-FAILED across name
    sets, no-op on SUCCEEDED, etc.).
Both halves bind on the same `CollectionStatus.FAILED` enum, so the
composition can't drift silently.
Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we are adding tests for every source file.
Not sure if all of them are worth it. May be worth looking at reducing them.

…ilures

* Restrict TokenAuthCollectorAdaptor to invalidate only on FAILED
  collections whose exception cause chain carries the "auth failure:"
  marker (set by HttpCollector / HttpUrlConnection on 401 and 403 from a
  data endpoint). Transport errors, broker outages, and 5xx surface as
  FAILED but leave the cached token alone.
* HttpUrlConnection: prefix the IOException message with "auth failure:"
  on 401/403 so the controller-side wrapper can mark the synthetic
  FAILED CollectionSet.
* CollectorAdaptor: add AUTH_FAILURE_PARAM constant.
* CollectorRequestBuilderImpl: walk the cause chain in
  applyAdaptorsAndPropagate and set the marker before invoking adaptors.
* Drop the unused beforeCollect adaptor hook (no remaining callers
  carried token placeholders in the flat <parameter> map).
* Drop dead invalidateByTokenValue + InvalidationResult code from
  TokenProvider, TokenProviderImpl, TokenCache, and their tests; the
  remaining invalidate(name) path covers every actual call site.
* Drop CollectCommandAdaptorTest per reviewer feedback.
* Rewrite the token-authentication.adoc "benign false-positive" NOTE to
  state the new auth-only invalidation contract.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 50 out of 51 changed files in this pull request and generated 4 comments.

Comment thread protocols/xml/src/main/java/org/opennms/protocols/http/HttpUrlConnection.java Outdated
…rAdaptor services

* HttpUrlConnection.getInputStream: wrap the >=400 branch in
  try-with-resources so the CloseableHttpResponse is closed before
  throwing. Without this, the new auth-failure throw leaked the
  response object even though consumeQuietly had drained the entity.
* CollectCommand.registerAdaptors: pair each
  bundleContext.getService(ref) with bundleContext.ungetService(ref)
  in a finally block so repeated opennms:collect invocations don't
  accumulate OSGi service use-counts on each CollectorAdaptor.
…leContext

Replace the manual BundleContext.getServiceReferences/getService loop
in CollectCommand with @reference List<CollectorAdaptor> adaptors, which
Karaf's shell wires via MultiServiceTracker. This is the pattern
cgorantla pointed out and also retires the prior getService/ungetService
balance: there is no use-count to manage when the shell framework owns
the tracker lifecycle.
Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One improvement we can do otherwise LGTM!

…igured

Per cgorantla, exit beforeRuntimeInterpolation early when the factory
holds zero auth definitions. This skips the containsTokenPlaceholder
walk, whose JAXB-marshal branch otherwise runs on every collection.
The provider exposes hasAnyAuths() with a safe default of true so
unknown implementations don't silently bypass substitution.
Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one more bug that might still be there.

Per cgorantla's review (discussion_r3284280039), HttpCollectorAgent.collect()
caught its own HttpCollectorException and only set status=FAILED on the
CollectionSet, which meant the controller-side TokenAuthCollectorAdaptor
never saw the "auth failure:" marker on the cause chain and the
auth-only invalidation gate did not fire for the HttpCollector path.

Track auth-failure across the URI loop in HttpCollectorAgent.collect()
and rethrow at the end so the exception propagates through the RPC
future chain. Add a specific catch (HttpCollectorException) -> throw in
doCollection() so the auth-failure exception is not re-wrapped by the
generic catch (Throwable) (which obscured the marker message before).

Also add HttpCollectorAuthFailureTest covering 401 and 403 responses
against an embedded HttpServer, asserting the rethrown
HttpCollectorException carries the expected prefix.
Copy link
Copy Markdown
Contributor

@cgorantla cgorantla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@marshallmassengill marshallmassengill merged commit c414286 into release-36.x May 22, 2026
14 checks passed
@marshallmassengill marshallmassengill deleted the develop-xml-dynamic-auth-smoke branch May 22, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants