Interface Sasl2TaskProvider
public interface Sasl2TaskProvider
Supplies SASL2 tasks (XEP-0388 ยง 2.5) and decides when they apply.
This is the extension point for third-party code. An implementation is registered with
Sasl2TaskManager.register(Sasl2TaskProvider), typically from a plugin's
initializePlugin(...), and unregistered again from destroyPlugin().
Lifecycle
For every SASL2 negotiation on a client session, the manager invokes the methods of every registered provider in this order:getStreamFeatureElements(LocalSession), when stream features are advertised. This happens before the negotiation starts, and is repeated whenever features are re-advertised (for example after a stream restart). It is the hook that lets a peer discover, and opt in to, an optional task. This is the mechanism that XEP-0480 uses.onAuthenticateReceived(Sasl2TaskContext, Element), when the peer's<authenticate/>element arrives. Authentication has not happened yet. This is where an opt-in that the peer expressed in that element is recorded, as a context attribute, for later use.getOfferedTasks(Sasl2TaskContext), once the SASL exchange has succeeded, and again after each task completes. This is the eligibility check: it decides whether this user, on this session, at this moment, is to be presented with a task.createTask(String, Sasl2TaskContext), when the peer selects one of the offered tasks.onNegotiationEnded(Sasl2TaskContext, boolean), when the negotiation ends, successfully or not.
Offering semantics
The tasks named in one<continue/> element are alternatives: the peer picks exactly one of them,
or aborts. To require several tasks in sequence, return the next one from getOfferedTasks(Sasl2TaskContext)
after the previous one has completed; the manager will send a further <continue/>. To offer a choice
between second factors, return them all at once, and return none of them once
Sasl2TaskContext.getCompletedTaskNames() shows that one has been performed.
There is no way for a peer to decline a task: a peer faced with a <continue/> it does not understand can
only abort, which ends the negotiation unauthenticated. Offering a task unconditionally therefore locks out every
client that has not implemented it. Two patterns avoid that:
- Opt-in (recommended for optional tasks): advertise the task in stream features, and offer
it only to peers that asked for it in their
<authenticate/>element. - Client-conditional: use
Sasl2TaskContext.getUserAgentInfo()to restrict the offer to software known to support the task.
Cost
getStreamFeatureElements(LocalSession) runs for every session that reaches stream feature advertisement,
including unauthenticated ones. Anything it does is reachable by an unauthenticated peer, and anything it varies on
is observable by one. Tailoring the advertisement to the user named in the stream's from attribute leaks
whether that user exists, which is the same trade-off that Openfire's
xmpp.auth.scram.mechanisms-per-user property governs for SASL mechanisms. Keep the method cheap and,
preferably, independent of the claimed identity.
Threading
Implementations must be thread-safe: a single provider instance serves all sessions concurrently. The per-session state belongs in theSasl2TaskContext and in the Sasl2Task instances that
createTask(String, Sasl2TaskContext) returns.-
Method Summary
Modifier and TypeMethodDescriptioncreateTask(String taskName, Sasl2TaskContext context) Creates the task that the peer selected.getContinueText(Sasl2TaskContext context) Human-readable text to include in the<continue/>element, explaining to the user why the negotiation did not complete.A stable identifier for this provider, unique among all registered providers.getOfferedTasks(Sasl2TaskContext context) Decides which tasks, if any, are to be offered to this session at this point in the negotiation.default intThe relative order in which this provider's tasks appear in the<continue/>element.default List<org.dom4j.Element>getStreamFeatureElements(LocalSession session) Contributes elements to the SASL2<authentication/>stream feature, so that a peer can discover the availability of a task and opt in to it.Every task name that this provider can ever offer.default voidonAuthenticateReceived(Sasl2TaskContext context, org.dom4j.Element authenticate) Inspects the peer's<authenticate/>element, before authentication is attempted.default voidonNegotiationEnded(Sasl2TaskContext context, boolean successful) Notifies the provider that the negotiation has ended.
-
Method Details
-
getIdentifier
A stable identifier for this provider, unique among all registered providers. It is used in logging, in thexmpp.auth.sasl2.tasks.disabledsystem property, and to scope the attributes and advertised features that the provider stores on a session. A reverse-DNS-style or plugin-style name is recommended, for exampleorg.example.totp.- Returns:
- an identifier (never null or empty).
-
getTaskNames
Every task name that this provider can ever offer. The manager uses this to detect collisions between providers at registration time, and to route a peer's<next/>element to the right provider. A name returned fromgetOfferedTasks(Sasl2TaskContext)that is not in this set is ignored. Task names are case-sensitive and are exchanged verbatim on the wire. Follow the conventions of whichever specification defines the task, and use a distinctive prefix for task names of your own devising.- Returns:
- an immutable set of task names (never null or empty).
-
getStreamFeatureElements
Contributes elements to the SASL2<authentication/>stream feature, so that a peer can discover the availability of a task and opt in to it. The returned elements are copied into the<authentication/>element as-is, and are recorded on the session so that they can later be retrieved throughSasl2TaskContext.getAdvertisedFeatureElements(). Each element must carry its own namespace. Returning an empty list (the default) advertises nothing, which is appropriate for a task that the server imposes rather than one the peer can request.- Parameters:
session- the session that features are being advertised to (never null). Not authenticated.- Returns:
- elements to add to the
<authentication/>feature (never null, possibly empty).
-
onAuthenticateReceived
default void onAuthenticateReceived(@Nonnull Sasl2TaskContext context, @Nonnull org.dom4j.Element authenticate) throws SaslFailureException Inspects the peer's<authenticate/>element, before authentication is attempted. This is where a request that the peer inlined into that element - an opt-in to an optional task, a parameter for a task - is parsed and recorded on the context withSasl2TaskContext.setAttribute(String, Object), forgetOfferedTasks(Sasl2TaskContext)to act on later. Nothing about the peer has been verified at this point. An implementation should validate the request against what was actually advertised (seeSasl2TaskContext.getAdvertisedFeatureElements()) rather than trust it, and should not perform expensive work on its basis.- Parameters:
context- the negotiation context (never null).Sasl2TaskContext.isAuthenticated()is false here.authenticate- the peer's<authenticate/>element (never null).- Throws:
SaslFailureException- to reject the authentication attempt outright, for a request that is malformed.
-
getOfferedTasks
Decides which tasks, if any, are to be offered to this session at this point in the negotiation. This is the eligibility check. Typical conditions are whether the account has the relevant feature enabled, whether the peer opted in, whether the interval since the task was last performed has elapsed, whether the client is known to support it, and whether the SASL mechanism that was used is compatible with it. Invoked once per round, on the thread that processes the peer's connection, with authentication complete. It must return quickly. Names that are not ingetTaskNames(), that another provider already offered in this round, or that have already completed during this negotiation, are discarded with a warning. The order of the returned list is preserved in the<continue/>element, which conveys the server's preference. Any provider implementing a mandatory task must fail closed within its own eligibility check, because the manager's own error handling fails open by design (to isolate providers from each other).- Parameters:
context- the negotiation context (never null).- Returns:
- task names to offer (never null, possibly empty).
-
createTask
@Nonnull Sasl2Task createTask(@Nonnull String taskName, @Nonnull Sasl2TaskContext context) throws SaslFailureException Creates the task that the peer selected. Only invoked for a name that this provider offered in the current round, so an implementation need not re-verify eligibility.- Parameters:
taskName- the name of the selected task (never null).context- the negotiation context (never null).- Returns:
- a new task instance (never null).
- Throws:
SaslFailureException- to abort the negotiation, if the task cannot be created.
-
getContinueText
Human-readable text to include in the<continue/>element, explaining to the user why the negotiation did not complete. Used only when this provider contributed at least one task to the current round. When several providers supply text, the texts are joined.- Parameters:
context- the negotiation context (never null).- Returns:
- text for the peer's user, or an empty optional (the default).
-
onNegotiationEnded
Notifies the provider that the negotiation has ended. Invoked exactly once per negotiation that this provider saw an<authenticate/>element for, whether or not it offered any task. AnySasl2Taskthat was still in progress has already hadSasl2Task.onAborted()invoked on it. The context must not be used after this call returns, and the session must not be written to. Note thatsuccessfulreports the outcome of the task flow, not of the SASL2 negotiation as a whole. It is true once every eligible task has completed (including when no task applied at all), which is before the identity is applied to the session and before any inlined Bind2 resource binding or XEP-0198 resumption is attempted. A failure in one of those later steps is not reflected here. A provider that must know whether the peer actually ended up authenticated should observeSessionEventDispatcherinstead.- Parameters:
context- the negotiation context (never null).successful- true if every eligible task completed, false if the negotiation failed or was aborted.
-
getPriority
default int getPriority()The relative order in which this provider's tasks appear in the<continue/>element. Providers are consulted in descending order of priority.- Returns:
- a priority. Defaults to zero.
-