From 4a6aa79e56c75342258a9bc337d4eba93626f2bb Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 1 Apr 2023 21:42:52 +0200 Subject: [PATCH] fix(xep): When using FAST, fallback to other SASL mechanisms on failure --- .../moxxmpp/lib/src/negotiators/sasl2.dart | 15 +++ .../moxxmpp/lib/src/xeps/staging/fast.dart | 18 ++- .../moxxmpp/test/xeps/xep_xxxx_fast_test.dart | 106 ++++++++++++++++++ 3 files changed, 134 insertions(+), 5 deletions(-) diff --git a/packages/moxxmpp/lib/src/negotiators/sasl2.dart b/packages/moxxmpp/lib/src/negotiators/sasl2.dart index 28d01b3..f260037 100644 --- a/packages/moxxmpp/lib/src/negotiators/sasl2.dart +++ b/packages/moxxmpp/lib/src/negotiators/sasl2.dart @@ -53,10 +53,15 @@ abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator /// the base64-encoded response data. Future getRawStep(String input); + /// Tells the negotiator that it has been selected as the SASL negotiator for SASL2. void pickForSasl2() { _pickedForSasl2 = true; } + /// When SASL2 fails, should we retry (true) or just fail (false). + /// Defaults to just returning false. + bool shouldRetrySasl() => false; + @override void reset() { _pickedForSasl2 = false; @@ -289,6 +294,16 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase { await negotiator.onSasl2Failure(nonza); } + // Check if we should retry and, if we should, reset the current + // negotiator, this negotiator, and retry. + if (_currentSaslNegotiator!.shouldRetrySasl()) { + _currentSaslNegotiator!.reset(); + reset(); + return const Result( + NegotiatorState.retryLater, + ); + } + return Result( SaslError.fromFailure(nonza), ); diff --git a/packages/moxxmpp/lib/src/xeps/staging/fast.dart b/packages/moxxmpp/lib/src/xeps/staging/fast.dart index 76088b1..39d32f2 100644 --- a/packages/moxxmpp/lib/src/xeps/staging/fast.dart +++ b/packages/moxxmpp/lib/src/xeps/staging/fast.dart @@ -30,10 +30,14 @@ class FASTToken { ); factory FASTToken.fromXml(XMLNode token) { - assert(token.tag == 'token', - 'Token can only be deserialised from a element',); - assert(token.xmlns == fastXmlns, - 'Token can only be deserialised from a element',); + assert( + token.tag == 'token', + 'Token can only be deserialised from a element', + ); + assert( + token.xmlns == fastXmlns, + 'Token can only be deserialised from a element', + ); return FASTToken( token.attributes['token']! as String, @@ -80,7 +84,8 @@ class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator { @override bool canInlineFeature(List features) { return features.firstWhereOrNull( - (child) => child.tag == 'fast' && child.xmlns == fastXmlns,) != + (child) => child.tag == 'fast' && child.xmlns == fastXmlns, + ) != null; } @@ -114,6 +119,9 @@ class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator { ); } + @override + bool shouldRetrySasl() => true; + @override Future> onSasl2FeaturesReceived(XMLNode sasl2Features) async { if (fastToken != null && pickedForSasl2) { diff --git a/packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart b/packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart index b59a22a..e844527 100644 --- a/packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart +++ b/packages/moxxmpp/test/xeps/xep_xxxx_fast_test.dart @@ -160,4 +160,110 @@ void main() { expect(conn.resource, 'MU29eEZn'); expect(fakeSocket.getState(), 7); }); + + test('Test failed FAST authentication with a token', () async { + final fakeSocket = StubTCPSocket([ + StringExpectation( + "", + ''' + + + + PLAIN + HT-SHA-256-NONE + + + PLAIN + HT-SHA-256-NONE + HT-SHA-256-ENDP + + + + HT-SHA-256-NONE + HT-SHA-256-ENDP + + + + + + + ''', + ), + StanzaExpectation( + "moxxmppPapaTutuWawa's awesome deviceWXZzciBwYmFmdmZnZiBqdmd1IGp2eXFhcmZm", + ''' + + + + ''', + ), + StanzaExpectation( + "moxxmppPapaTutuWawa's awesome deviceAHBvbHlub21kaXZpc2lvbgBhYWFh", + ''' + + polynomdivision@test.server + + + ''', + ), + StanzaExpectation( + '', + 'polynomdivision@test.server/MU29eEZn', + 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() + ..fastToken = const FASTToken( + 'WXZzciBwYmFmdmZnZiBqdmd1IGp2eXFhcmZm', + '2020-03-12T14:36:15Z', + ), + 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(), false); + expect(conn.resource, 'MU29eEZn'); + expect(fakeSocket.getState(), 4); + + final token = conn + .getNegotiatorById(saslFASTNegotiator)! + .fastToken; + expect(token != null, true); + expect(token!.token, 'ed00e36cb42449a365a306a413f51ffd5ea8'); + }); }