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/types/result.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/xep_0004.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.
|
||||
/// Returns the registered [PresenceManager].
|
||||
PresenceManager getPresenceManager() {
|
||||
assert(
|
||||
_xmppManagers.containsKey(presenceManager),
|
||||
'A PresenceManager is mandatory',
|
||||
);
|
||||
|
||||
return getManagerById(presenceManager)!;
|
||||
PresenceManager? getPresenceManager() {
|
||||
return getManagerById(presenceManager);
|
||||
}
|
||||
|
||||
/// A [DiscoManager] is required so, have a wrapper for getting it.
|
||||
@ -1030,6 +1025,9 @@ class XmppConnection {
|
||||
for (final manager in _xmppManagers.values) {
|
||||
await manager.onXmppEvent(event);
|
||||
}
|
||||
for (final negotiator in _featureNegotiators.values) {
|
||||
await negotiator.onXmppEvent(event);
|
||||
}
|
||||
|
||||
_eventStreamController.add(event);
|
||||
}
|
||||
@ -1068,7 +1066,7 @@ class XmppConnection {
|
||||
await _reconnectionPolicy.setShouldReconnect(false);
|
||||
|
||||
if (triggeredByUser) {
|
||||
getPresenceManager().sendUnavailablePresence();
|
||||
getPresenceManager()?.sendUnavailablePresence();
|
||||
}
|
||||
|
||||
_socket.prepareDisconnect();
|
||||
@ -1136,6 +1134,8 @@ class XmppConnection {
|
||||
|
||||
if (lastResource != null) {
|
||||
setResource(lastResource, triggerEvent: false);
|
||||
} else {
|
||||
setResource('', triggerEvent: false);
|
||||
}
|
||||
|
||||
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
||||
|
@ -160,3 +160,6 @@ const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
||||
|
||||
// ???
|
||||
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 sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
|
||||
const bind2Negotiator = 'org.moxxmpp.bind2';
|
||||
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
||||
|
@ -124,6 +124,9 @@ abstract class XmppFeatureNegotiatorBase {
|
||||
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.
|
||||
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
||||
/// the <stream:features /> nonza.
|
||||
|
@ -98,6 +98,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||
|
||||
@override
|
||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||
return [];
|
||||
|
@ -356,6 +356,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
return [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
// 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/negotiators/namespaces.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/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
@ -28,6 +29,10 @@ abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
||||
/// item with xmlns equal to [negotiatingXmlns].
|
||||
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
|
||||
/// to inline a feature. [features] is the list of elements inside the <inline />
|
||||
/// element.
|
||||
@ -39,10 +44,26 @@ abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator
|
||||
implements Sasl2FeatureNegotiator {
|
||||
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
|
||||
/// the base64-encoded response data.
|
||||
Future<String> getRawStep(String input);
|
||||
|
||||
void pickForSasl2() {
|
||||
_pickedForSasl2 = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_pickedForSasl2 = false;
|
||||
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
bool canInlineFeature(List<XMLNode> features) {
|
||||
return true;
|
||||
@ -174,6 +195,7 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
||||
for (final negotiator in _saslNegotiators) {
|
||||
if (negotiator.matchesFeature([mechanisms])) {
|
||||
_currentSaslNegotiator = negotiator;
|
||||
_currentSaslNegotiator!.pickForSasl2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -256,6 +278,20 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
||||
text: await _currentSaslNegotiator!.getRawStep(challenge),
|
||||
);
|
||||
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:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
@ -57,6 +58,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
||||
/// True if we requested stream enablement inline
|
||||
bool _inlineStreamEnablementRequested = false;
|
||||
|
||||
/// Cached resource for stream resumption
|
||||
String _resource = '';
|
||||
@visibleForTesting
|
||||
void setResource(String resource) {
|
||||
_resource = resource;
|
||||
}
|
||||
|
||||
@override
|
||||
bool canInlineFeature(List<XMLNode> features) {
|
||||
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
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||
@ -116,6 +131,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
||||
|
||||
_resumeFailed = false;
|
||||
_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 {
|
||||
|
@ -855,7 +855,7 @@ void main() {
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
StreamManagementNegotiator()..setResource('test-resource'),
|
||||
Sasl2Negotiator(
|
||||
userAgent: const UserAgent(
|
||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||
@ -954,7 +954,7 @@ void main() {
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StreamManagementNegotiator(),
|
||||
StreamManagementNegotiator()..setResource('test-resource'),
|
||||
Bind2Negotiator(),
|
||||
Sasl2Negotiator(
|
||||
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