feat(xep): Implement FAST
This commit is contained in:
parent
24cb05f91b
commit
0033d0eb6e
@ -39,6 +39,7 @@ export 'package:moxxmpp/src/stanza.dart';
|
|||||||
export 'package:moxxmpp/src/stringxml.dart';
|
export 'package:moxxmpp/src/stringxml.dart';
|
||||||
export 'package:moxxmpp/src/types/result.dart';
|
export 'package:moxxmpp/src/types/result.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
|
@ -314,13 +314,8 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// A [PresenceManager] is required, so have a wrapper for getting it.
|
/// A [PresenceManager] is required, so have a wrapper for getting it.
|
||||||
/// Returns the registered [PresenceManager].
|
/// Returns the registered [PresenceManager].
|
||||||
PresenceManager getPresenceManager() {
|
PresenceManager? getPresenceManager() {
|
||||||
assert(
|
return getManagerById(presenceManager);
|
||||||
_xmppManagers.containsKey(presenceManager),
|
|
||||||
'A PresenceManager is mandatory',
|
|
||||||
);
|
|
||||||
|
|
||||||
return getManagerById(presenceManager)!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [DiscoManager] is required so, have a wrapper for getting it.
|
/// A [DiscoManager] is required so, have a wrapper for getting it.
|
||||||
@ -1030,6 +1025,9 @@ class XmppConnection {
|
|||||||
for (final manager in _xmppManagers.values) {
|
for (final manager in _xmppManagers.values) {
|
||||||
await manager.onXmppEvent(event);
|
await manager.onXmppEvent(event);
|
||||||
}
|
}
|
||||||
|
for (final negotiator in _featureNegotiators.values) {
|
||||||
|
await negotiator.onXmppEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
_eventStreamController.add(event);
|
_eventStreamController.add(event);
|
||||||
}
|
}
|
||||||
@ -1068,7 +1066,7 @@ class XmppConnection {
|
|||||||
await _reconnectionPolicy.setShouldReconnect(false);
|
await _reconnectionPolicy.setShouldReconnect(false);
|
||||||
|
|
||||||
if (triggeredByUser) {
|
if (triggeredByUser) {
|
||||||
getPresenceManager().sendUnavailablePresence();
|
getPresenceManager()?.sendUnavailablePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
_socket.prepareDisconnect();
|
_socket.prepareDisconnect();
|
||||||
@ -1136,6 +1134,8 @@ class XmppConnection {
|
|||||||
|
|
||||||
if (lastResource != null) {
|
if (lastResource != null) {
|
||||||
setResource(lastResource, triggerEvent: false);
|
setResource(lastResource, triggerEvent: false);
|
||||||
|
} else {
|
||||||
|
setResource('', triggerEvent: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
||||||
|
@ -160,3 +160,6 @@ const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
|||||||
|
|
||||||
// ???
|
// ???
|
||||||
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
||||||
|
|
||||||
|
// XEP-XXXX
|
||||||
|
const fastXmlns = 'urn:xmpp:fast:0';
|
||||||
|
@ -9,3 +9,4 @@ const streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
|
|||||||
const startTlsNegotiator = 'im.moxxmpp.core.starttls';
|
const startTlsNegotiator = 'im.moxxmpp.core.starttls';
|
||||||
const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
|
const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
|
||||||
const bind2Negotiator = 'org.moxxmpp.bind2';
|
const bind2Negotiator = 'org.moxxmpp.bind2';
|
||||||
|
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
||||||
|
@ -124,6 +124,9 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
null;
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when an event is triggered in the [XmppConnection].
|
||||||
|
Future<void> onXmppEvent(XmppEvent event) async {}
|
||||||
|
|
||||||
/// Called with the currently received nonza [nonza] when the negotiator is active.
|
/// Called with the currently received nonza [nonza] when the negotiator is active.
|
||||||
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
||||||
/// the <stream:features /> nonza.
|
/// the <stream:features /> nonza.
|
||||||
|
@ -98,6 +98,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||||
return [];
|
return [];
|
||||||
|
@ -356,6 +356,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
// When we're done with SASL2, check the additional data to verify the server
|
// When we're done with SASL2, check the additional data to verify the server
|
||||||
|
@ -2,6 +2,7 @@ import 'package:moxxmpp/src/jid.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
@ -28,6 +29,10 @@ abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
/// item with xmlns equal to [negotiatingXmlns].
|
/// item with xmlns equal to [negotiatingXmlns].
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response);
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response);
|
||||||
|
|
||||||
|
/// Called by the SASL2 negotiator when the SASL2 negotiations have failed. [response]
|
||||||
|
/// is the entire response nonza.
|
||||||
|
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||||
|
|
||||||
/// Called by the SASL2 negotiator to find out whether the negotiator is willing
|
/// Called by the SASL2 negotiator to find out whether the negotiator is willing
|
||||||
/// to inline a feature. [features] is the list of elements inside the <inline />
|
/// to inline a feature. [features] is the list of elements inside the <inline />
|
||||||
/// element.
|
/// element.
|
||||||
@ -39,10 +44,26 @@ abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator
|
|||||||
implements Sasl2FeatureNegotiator {
|
implements Sasl2FeatureNegotiator {
|
||||||
Sasl2AuthenticationNegotiator(super.priority, super.id, super.mechanismName);
|
Sasl2AuthenticationNegotiator(super.priority, super.id, super.mechanismName);
|
||||||
|
|
||||||
|
/// Flag indicating whether this negotiator was chosen during SASL2 as the SASL
|
||||||
|
/// negotiator to use.
|
||||||
|
bool _pickedForSasl2 = false;
|
||||||
|
bool get pickedForSasl2 => _pickedForSasl2;
|
||||||
|
|
||||||
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
||||||
/// the base64-encoded response data.
|
/// the base64-encoded response data.
|
||||||
Future<String> getRawStep(String input);
|
Future<String> getRawStep(String input);
|
||||||
|
|
||||||
|
void pickForSasl2() {
|
||||||
|
_pickedForSasl2 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reset() {
|
||||||
|
_pickedForSasl2 = false;
|
||||||
|
|
||||||
|
super.reset();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
return true;
|
return true;
|
||||||
@ -174,6 +195,7 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
for (final negotiator in _saslNegotiators) {
|
for (final negotiator in _saslNegotiators) {
|
||||||
if (negotiator.matchesFeature([mechanisms])) {
|
if (negotiator.matchesFeature([mechanisms])) {
|
||||||
_currentSaslNegotiator = negotiator;
|
_currentSaslNegotiator = negotiator;
|
||||||
|
_currentSaslNegotiator!.pickForSasl2();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,6 +278,20 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
text: await _currentSaslNegotiator!.getRawStep(challenge),
|
text: await _currentSaslNegotiator!.getRawStep(challenge),
|
||||||
);
|
);
|
||||||
attributes.sendNonza(response);
|
attributes.sendNonza(response);
|
||||||
|
} else if (nonza.tag == 'failure') {
|
||||||
|
final negotiators = _featureNegotiators
|
||||||
|
.where(
|
||||||
|
(negotiator) => _activeSasl2Negotiators.contains(negotiator.id),
|
||||||
|
)
|
||||||
|
.toList()
|
||||||
|
..add(_currentSaslNegotiator!);
|
||||||
|
for (final negotiator in negotiators) {
|
||||||
|
await negotiator.onSasl2Failure(nonza);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(
|
||||||
|
SaslError.fromFailure(nonza),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
158
packages/moxxmpp/lib/src/xeps/staging/fast.dart
Normal file
158
packages/moxxmpp/lib/src/xeps/staging/fast.dart
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl2.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
|
|
||||||
|
/// This event is triggered whenever a new FAST token is received.
|
||||||
|
class NewFASTTokenReceivedEvent extends XmppEvent {
|
||||||
|
NewFASTTokenReceivedEvent(this.token);
|
||||||
|
|
||||||
|
/// The token.
|
||||||
|
final FASTToken token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This event is triggered whenever a new FAST token is invalidated because it's
|
||||||
|
/// invalid.
|
||||||
|
class InvalidateFASTTokenEvent extends XmppEvent {
|
||||||
|
InvalidateFASTTokenEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The description of a token for FAST authentication.
|
||||||
|
class FASTToken {
|
||||||
|
const FASTToken(
|
||||||
|
this.token,
|
||||||
|
this.expiry,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory FASTToken.fromXml(XMLNode token) {
|
||||||
|
assert(token.tag == 'token',
|
||||||
|
'Token can only be deserialised from a <token /> element',);
|
||||||
|
assert(token.xmlns == fastXmlns,
|
||||||
|
'Token can only be deserialised from a <token /> element',);
|
||||||
|
|
||||||
|
return FASTToken(
|
||||||
|
token.attributes['token']! as String,
|
||||||
|
token.attributes['expiry']! as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The actual token.
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
/// The token's expiry.
|
||||||
|
final String expiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Unknown): Implement multiple hash functions, similar to how we do SCRAM
|
||||||
|
class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator {
|
||||||
|
FASTSaslNegotiator() : super(20, saslFASTNegotiator, 'HT-SHA-256-NONE');
|
||||||
|
|
||||||
|
final Logger _log = Logger('FASTSaslNegotiator');
|
||||||
|
|
||||||
|
/// The token, if non-null, to use for authentication.
|
||||||
|
FASTToken? fastToken;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
|
if (fastToken == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (super.matchesFeature(features)) {
|
||||||
|
if (!attributes.getSocket().isSecure()) {
|
||||||
|
_log.warning(
|
||||||
|
'Refusing to match SASL feature due to unsecured connection',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
|
return features.firstWhereOrNull(
|
||||||
|
(child) => child.tag == 'fast' && child.xmlns == fastXmlns,) !=
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
|
XMLNode nonza,
|
||||||
|
) async {
|
||||||
|
// TODO(Unknown): Is FAST supposed to work without SASL2?
|
||||||
|
return const Result(NegotiatorState.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
|
final token = response.firstTag('token', xmlns: fastXmlns);
|
||||||
|
if (token != null) {
|
||||||
|
fastToken = FASTToken.fromXml(token);
|
||||||
|
await attributes.sendEvent(
|
||||||
|
NewFASTTokenReceivedEvent(fastToken!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
state = NegotiatorState.done;
|
||||||
|
return const Result(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onSasl2Failure(XMLNode response) async {
|
||||||
|
fastToken = null;
|
||||||
|
await attributes.sendEvent(
|
||||||
|
InvalidateFASTTokenEvent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||||
|
if (fastToken != null && pickedForSasl2) {
|
||||||
|
// Specify that we are using a token
|
||||||
|
return [
|
||||||
|
// As we don't do TLS 0-RTT, we don't have to specify `count`.
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'fast',
|
||||||
|
xmlns: fastXmlns,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only request a new token when we don't already have one and we are not picked
|
||||||
|
// for SASL
|
||||||
|
if (!pickedForSasl2) {
|
||||||
|
return [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'request-token',
|
||||||
|
xmlns: fastXmlns,
|
||||||
|
attributes: {
|
||||||
|
'mechanism': 'HT-SHA-256-NONE',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> getRawStep(String input) async {
|
||||||
|
return fastToken!.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
attributes
|
||||||
|
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
||||||
|
?.registerSaslNegotiator(this);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
@ -57,6 +58,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
/// True if we requested stream enablement inline
|
/// True if we requested stream enablement inline
|
||||||
bool _inlineStreamEnablementRequested = false;
|
bool _inlineStreamEnablementRequested = false;
|
||||||
|
|
||||||
|
/// Cached resource for stream resumption
|
||||||
|
String _resource = '';
|
||||||
|
@visibleForTesting
|
||||||
|
void setResource(String resource) {
|
||||||
|
_resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
@ -78,6 +86,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
|
if (event is ResourceBoundEvent) {
|
||||||
|
_resource = event.resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
@ -116,6 +131,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
|
|
||||||
_resumeFailed = false;
|
_resumeFailed = false;
|
||||||
_isResumed = true;
|
_isResumed = true;
|
||||||
|
|
||||||
|
if (attributes.getConnection().resource.isEmpty && _resource.isNotEmpty) {
|
||||||
|
attributes.setResource(_resource);
|
||||||
|
} else if (attributes.getConnection().resource.isNotEmpty &&
|
||||||
|
_resource.isEmpty) {
|
||||||
|
_resource = attributes.getConnection().resource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onStreamEnablementSuccessful(XMLNode enabled) async {
|
Future<void> _onStreamEnablementSuccessful(XMLNode enabled) async {
|
||||||
|
@ -855,7 +855,7 @@ void main() {
|
|||||||
await conn.registerFeatureNegotiators([
|
await conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator()..setResource('test-resource'),
|
||||||
Sasl2Negotiator(
|
Sasl2Negotiator(
|
||||||
userAgent: const UserAgent(
|
userAgent: const UserAgent(
|
||||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
@ -954,7 +954,7 @@ void main() {
|
|||||||
await conn.registerFeatureNegotiators([
|
await conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator()..setResource('test-resource'),
|
||||||
Bind2Negotiator(),
|
Bind2Negotiator(),
|
||||||
Sasl2Negotiator(
|
Sasl2Negotiator(
|
||||||
userAgent: const UserAgent(
|
userAgent: const UserAgent(
|
||||||
|
163
packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart
Normal file
163
packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import '../helpers/logging.dart';
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
initLogger();
|
||||||
|
|
||||||
|
test('Test FAST authentication without a token', () async {
|
||||||
|
final fakeSocket = StubTCPSocket([
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
<authentication xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-ENDP</mechanism>
|
||||||
|
<inline>
|
||||||
|
<bind xmlns="urn:xmpp:bind:0" />
|
||||||
|
<fast xmlns="urn:xmpp:fast:0">
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-ENDP</mechanism>
|
||||||
|
</fast>
|
||||||
|
</inline>
|
||||||
|
</authentication>
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='PLAIN'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>AHBvbHlub21kaXZpc2lvbgBhYWFh</initial-response><request-token xmlns='urn:xmpp:fast:0' mechanism='HT-SHA-256-NONE' /></authenticate>",
|
||||||
|
'''
|
||||||
|
<success xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
||||||
|
<token xmlns='urn:xmpp:fast:0'
|
||||||
|
expiry='2020-03-12T14:36:15Z'
|
||||||
|
token='WXZzciBwYmFmdmZnZiBqdmd1IGp2eXFhcmZm' />
|
||||||
|
</success>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||||
|
ignoreId: true,
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
<authentication xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-ENDP</mechanism>
|
||||||
|
<inline>
|
||||||
|
<bind xmlns="urn:xmpp:bind:0" />
|
||||||
|
<fast xmlns="urn:xmpp:fast:0">
|
||||||
|
<mechanism>HT-SHA-256-NONE</mechanism>
|
||||||
|
<mechanism>HT-SHA-256-ENDP</mechanism>
|
||||||
|
</fast>
|
||||||
|
</inline>
|
||||||
|
</authentication>
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='HT-SHA-256-NONE'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>WXZzciBwYmFmdmZnZiBqdmd1IGp2eXFhcmZm</initial-response><fast xmlns='urn:xmpp:fast:0' /></authenticate>",
|
||||||
|
'''
|
||||||
|
<success xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
||||||
|
</success>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||||
|
ignoreId: true,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
)..setConnectionSettings(
|
||||||
|
ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
useDirectTLS: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await conn.registerManagers([
|
||||||
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
|
DiscoManager([]),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
FASTSaslNegotiator(),
|
||||||
|
Sasl2Negotiator(
|
||||||
|
userAgent: const UserAgent(
|
||||||
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
|
software: 'moxxmpp',
|
||||||
|
device: "PapaTutuWawa's awesome device",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final result1 = await conn.connect(
|
||||||
|
waitUntilLogin: true,
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
);
|
||||||
|
expect(result1.isType<NegotiatorError>(), false);
|
||||||
|
expect(conn.resource, 'MU29eEZn');
|
||||||
|
expect(fakeSocket.getState(), 3);
|
||||||
|
|
||||||
|
final token = conn
|
||||||
|
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
|
||||||
|
.fastToken;
|
||||||
|
expect(token != null, true);
|
||||||
|
expect(token!.token, 'WXZzciBwYmFmdmZnZiBqdmd1IGp2eXFhcmZm');
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
await conn.disconnect();
|
||||||
|
|
||||||
|
// Connect again, but use FAST this time
|
||||||
|
final result2 = await conn.connect(
|
||||||
|
waitUntilLogin: true,
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
);
|
||||||
|
expect(result2.isType<NegotiatorError>(), false);
|
||||||
|
expect(conn.resource, 'MU29eEZn');
|
||||||
|
expect(fakeSocket.getState(), 7);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user