feat(xep): Allow negotiating SM enabling inline with Bind2
This commit is contained in:
parent
51edb61443
commit
91f763ac26
@ -218,6 +218,7 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
attributes.sendNonza(authenticate);
|
attributes.sendNonza(authenticate);
|
||||||
return const Result(NegotiatorState.ready);
|
return const Result(NegotiatorState.ready);
|
||||||
case Sasl2State.authenticateSent:
|
case Sasl2State.authenticateSent:
|
||||||
|
// TODO(PapaTutuWawa): Handle failure
|
||||||
if (nonza.tag == 'success') {
|
if (nonza.tag == 'success') {
|
||||||
// Tell the dependent negotiators about the result
|
// Tell the dependent negotiators about the result
|
||||||
final negotiators = _featureNegotiators
|
final negotiators = _featureNegotiators
|
||||||
|
@ -12,6 +12,7 @@ import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
|||||||
import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0352.dart';
|
import 'package:moxxmpp/src/xeps/xep_0352.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0386.dart';
|
||||||
|
|
||||||
enum _StreamManagementNegotiatorState {
|
enum _StreamManagementNegotiatorState {
|
||||||
// We have not done anything yet
|
// We have not done anything yet
|
||||||
@ -25,7 +26,8 @@ enum _StreamManagementNegotiatorState {
|
|||||||
/// NOTE: The stream management negotiator requires that loadState has been called on the
|
/// NOTE: The stream management negotiator requires that loadState has been called on the
|
||||||
/// StreamManagementManager at least once before connecting, if stream resumption
|
/// StreamManagementManager at least once before connecting, if stream resumption
|
||||||
/// is wanted.
|
/// is wanted.
|
||||||
class StreamManagementNegotiator extends Sasl2FeatureNegotiator {
|
class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
||||||
|
implements Bind2FeatureNegotiator {
|
||||||
StreamManagementNegotiator()
|
StreamManagementNegotiator()
|
||||||
: super(10, false, smXmlns, streamManagementNegotiator);
|
: super(10, false, smXmlns, streamManagementNegotiator);
|
||||||
|
|
||||||
@ -200,6 +202,22 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator {
|
|||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<XMLNode>> onBind2FeaturesReceived(
|
||||||
|
List<String> bind2Features,
|
||||||
|
) async {
|
||||||
|
if (!bind2Features.contains(smXmlns)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'enable',
|
||||||
|
xmlns: smXmlns,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||||
final inline = sasl2Features.firstTag('inline')!;
|
final inline = sasl2Features.firstTag('inline')!;
|
||||||
@ -231,6 +249,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
|
// TODO(PapaTutuWawa): Handle SM failures.
|
||||||
final resumed = response.firstTag('resumed', xmlns: smXmlns);
|
final resumed = response.firstTag('resumed', xmlns: smXmlns);
|
||||||
if (resumed == null) {
|
if (resumed == null) {
|
||||||
_log.warning('Inline stream resumption failed');
|
_log.warning('Inline stream resumption failed');
|
||||||
@ -254,5 +273,8 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator {
|
|||||||
attributes
|
attributes
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
||||||
?.registerNegotiator(this);
|
?.registerNegotiator(this);
|
||||||
|
attributes
|
||||||
|
.getNegotiatorById<Bind2Negotiator>(bind2Negotiator)
|
||||||
|
?.registerNegotiator(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,33 @@ import 'package:moxxmpp/src/negotiators/sasl2.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';
|
||||||
|
|
||||||
|
/// An interface that allows registering against Bind2's feature list in order to
|
||||||
|
/// negotiate features inline with Bind2.
|
||||||
|
// ignore: one_member_abstracts
|
||||||
|
abstract class Bind2FeatureNegotiator {
|
||||||
|
/// Called by the Bind2 negotiator when Bind2 features are received. The returned
|
||||||
|
/// [XMLNode]s are added to Bind2's bind request.
|
||||||
|
Future<List<XMLNode>> onBind2FeaturesReceived(List<String> bind2Features);
|
||||||
|
}
|
||||||
|
|
||||||
/// A negotiator implementing XEP-0386. This negotiator is useless on its own
|
/// A negotiator implementing XEP-0386. This negotiator is useless on its own
|
||||||
/// and requires a [Sasl2Negotiator] to be registered.
|
/// and requires a [Sasl2Negotiator] to be registered.
|
||||||
class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
||||||
Bind2Negotiator() : super(0, false, bind2Xmlns, bind2Negotiator);
|
Bind2Negotiator() : super(0, false, bind2Xmlns, bind2Negotiator);
|
||||||
|
|
||||||
|
/// A list of negotiators that can work with Bind2.
|
||||||
|
final List<Bind2FeatureNegotiator> _negotiators =
|
||||||
|
List<Bind2FeatureNegotiator>.empty(growable: true);
|
||||||
|
|
||||||
/// A tag to sent to the server when requesting Bind2.
|
/// A tag to sent to the server when requesting Bind2.
|
||||||
String? tag;
|
String? tag;
|
||||||
|
|
||||||
|
/// Register [negotiator] against the Bind2 negotiator to append data to the Bind2
|
||||||
|
/// negotiation.
|
||||||
|
void registerNegotiator(Bind2FeatureNegotiator negotiator) {
|
||||||
|
_negotiators.add(negotiator);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
XMLNode nonza,
|
XMLNode nonza,
|
||||||
@ -23,6 +42,25 @@ class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||||
|
final children = List<XMLNode>.empty(growable: true);
|
||||||
|
if (_negotiators.isNotEmpty) {
|
||||||
|
final inline = sasl2Features
|
||||||
|
.firstTag('inline')!
|
||||||
|
.firstTag('bind', xmlns: bind2Xmlns)!
|
||||||
|
.firstTag('inline');
|
||||||
|
if (inline != null) {
|
||||||
|
final features = inline.children
|
||||||
|
.where((child) => child.tag == 'feature')
|
||||||
|
.map((child) => child.attributes['var']! as String)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Only call the negotiators if Bind2 allows doing stuff inline
|
||||||
|
for (final negotiator in _negotiators) {
|
||||||
|
children.addAll(await negotiator.onBind2FeaturesReceived(features));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'bind',
|
tag: 'bind',
|
||||||
@ -33,6 +71,7 @@ class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
|||||||
tag: 'tag',
|
tag: 'tag',
|
||||||
text: tag,
|
text: tag,
|
||||||
),
|
),
|
||||||
|
...children,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -882,4 +882,104 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(conn.resource, 'test-resource');
|
expect(conn.resource, 'test-resource');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test SASL2 inline stream resumption with Bind2', () 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>
|
||||||
|
</mechanisms>
|
||||||
|
<authentication xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
<inline>
|
||||||
|
<resume xmlns="urn:xmpp:sm:3" />
|
||||||
|
<bind xmlns="urn:xmpp:bind:0">
|
||||||
|
<inline>
|
||||||
|
<feature var="urn:xmpp:sm:3" />
|
||||||
|
</inline>
|
||||||
|
</bind>
|
||||||
|
</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><bind xmlns='urn:xmpp:bind:0'><enable xmlns='urn:xmpp:sm:3' /></bind><resume xmlns='urn:xmpp:sm:3' previd='test-prev-id' h='2' /></authenticate>",
|
||||||
|
'''
|
||||||
|
<success xmlns='urn:xmpp:sasl:2'>
|
||||||
|
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
||||||
|
<resumed xmlns='urn:xmpp:sm:3' h='25' previd='test-prev-id' />
|
||||||
|
</success>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final sm = StreamManagementManager();
|
||||||
|
await sm.setState(
|
||||||
|
sm.state.copyWith(
|
||||||
|
c2s: 25,
|
||||||
|
s2c: 2,
|
||||||
|
streamResumptionId: 'test-prev-id',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
)
|
||||||
|
..setConnectionSettings(
|
||||||
|
ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
useDirectTLS: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
..setResource('test-resource', triggerEvent: false);
|
||||||
|
await conn.registerManagers([
|
||||||
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
|
DiscoManager([]),
|
||||||
|
sm,
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
StreamManagementNegotiator(),
|
||||||
|
Bind2Negotiator(),
|
||||||
|
Sasl2Negotiator(
|
||||||
|
userAgent: const UserAgent(
|
||||||
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
|
software: 'moxxmpp',
|
||||||
|
device: "PapaTutuWawa's awesome device",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final result = await conn.connect(
|
||||||
|
waitUntilLogin: true,
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
);
|
||||||
|
expect(result.isType<NegotiatorError>(), false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
sm.state.c2s,
|
||||||
|
25,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
sm.state.s2c,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
expect(conn.resource, 'test-resource');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user