feat(xep): Handle inline stream enablement with Bind2
This commit is contained in:
parent
91f763ac26
commit
24cb05f91b
@ -37,9 +37,15 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
|
|
||||||
/// Flag indicating whether the resume failed (true) or succeeded (false).
|
/// Flag indicating whether the resume failed (true) or succeeded (false).
|
||||||
bool _resumeFailed = false;
|
bool _resumeFailed = false;
|
||||||
|
bool get resumeFailed => _resumeFailed;
|
||||||
|
|
||||||
/// Flag indicating whether the current stream is resumed (true) or not (false).
|
/// Flag indicating whether the current stream is resumed (true) or not (false).
|
||||||
bool _isResumed = false;
|
bool _isResumed = false;
|
||||||
|
bool get isResumed => _isResumed;
|
||||||
|
|
||||||
|
/// Flag indicating that stream enablement failed
|
||||||
|
bool _streamEnablementFailed = false;
|
||||||
|
bool get streamEnablementFailed => _streamEnablementFailed;
|
||||||
|
|
||||||
/// Logger
|
/// Logger
|
||||||
final Logger _log = Logger('StreamManagementNegotiator');
|
final Logger _log = Logger('StreamManagementNegotiator');
|
||||||
@ -48,8 +54,8 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
bool _supported = false;
|
bool _supported = false;
|
||||||
bool get isSupported => _supported;
|
bool get isSupported => _supported;
|
||||||
|
|
||||||
/// True if the current stream is resumed. False if not.
|
/// True if we requested stream enablement inline
|
||||||
bool get isResumed => _isResumed;
|
bool _inlineStreamEnablementRequested = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
bool canInlineFeature(List<XMLNode> features) {
|
||||||
@ -112,6 +118,28 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
_isResumed = true;
|
_isResumed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onStreamEnablementSuccessful(XMLNode enabled) async {
|
||||||
|
assert(enabled.tag == 'enabled', 'The correct element must be used');
|
||||||
|
assert(enabled.xmlns == smXmlns, 'The correct element must be used');
|
||||||
|
|
||||||
|
final id = enabled.attributes['id'] as String?;
|
||||||
|
if (id != null && ['true', '1'].contains(enabled.attributes['resume'])) {
|
||||||
|
_log.info('Stream Resumption available');
|
||||||
|
}
|
||||||
|
|
||||||
|
await attributes.sendEvent(
|
||||||
|
StreamManagementEnabledEvent(
|
||||||
|
resource: attributes.getFullJID().resource,
|
||||||
|
id: id,
|
||||||
|
location: enabled.attributes['location'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStreamEnablementFailed() {
|
||||||
|
_streamEnablementFailed = true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
XMLNode nonza,
|
XMLNode nonza,
|
||||||
@ -168,25 +196,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
case _StreamManagementNegotiatorState.enableRequested:
|
case _StreamManagementNegotiatorState.enableRequested:
|
||||||
if (nonza.tag == 'enabled') {
|
if (nonza.tag == 'enabled') {
|
||||||
_log.finest('Stream Management enabled');
|
_log.finest('Stream Management enabled');
|
||||||
|
await _onStreamEnablementSuccessful(nonza);
|
||||||
final id = nonza.attributes['id'] as String?;
|
|
||||||
if (id != null &&
|
|
||||||
['true', '1'].contains(nonza.attributes['resume'])) {
|
|
||||||
_log.info('Stream Resumption available');
|
|
||||||
}
|
|
||||||
|
|
||||||
await attributes.sendEvent(
|
|
||||||
StreamManagementEnabledEvent(
|
|
||||||
resource: attributes.getFullJID().resource,
|
|
||||||
id: id,
|
|
||||||
location: nonza.attributes['location'] as String?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
} else {
|
} else {
|
||||||
// We assume a <failed />
|
// We assume a <failed />
|
||||||
_log.warning('Stream Management enablement failed');
|
_log.warning('Stream Management enablement failed');
|
||||||
|
_onStreamEnablementFailed();
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,6 +214,8 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
_supported = false;
|
_supported = false;
|
||||||
_resumeFailed = false;
|
_resumeFailed = false;
|
||||||
_isResumed = false;
|
_isResumed = false;
|
||||||
|
_inlineStreamEnablementRequested = false;
|
||||||
|
_streamEnablementFailed = false;
|
||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
@ -210,11 +228,9 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_inlineStreamEnablementRequested = true;
|
||||||
return [
|
return [
|
||||||
XMLNode.xmlns(
|
StreamManagementEnableNonza(),
|
||||||
tag: 'enable',
|
|
||||||
xmlns: smXmlns,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,25 +252,36 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
XMLNode.xmlns(
|
StreamManagementResumeNonza(
|
||||||
tag: 'resume',
|
srid,
|
||||||
xmlns: smXmlns,
|
h,
|
||||||
attributes: {
|
|
||||||
'h': h.toString(),
|
|
||||||
'previd': srid,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
// TODO(PapaTutuWawa): Handle SM failures.
|
final enabled = response
|
||||||
|
.firstTag('bound', xmlns: bind2Xmlns)
|
||||||
|
?.firstTag('enabled', xmlns: smXmlns);
|
||||||
final resumed = response.firstTag('resumed', xmlns: smXmlns);
|
final resumed = response.firstTag('resumed', xmlns: smXmlns);
|
||||||
|
// We can only enable or resume->fail->enable. Thus, we check for enablement first
|
||||||
|
// and then exit.
|
||||||
|
if (_inlineStreamEnablementRequested) {
|
||||||
|
if (enabled != null) {
|
||||||
|
_log.finest('Inline stream enablement successful');
|
||||||
|
await _onStreamEnablementSuccessful(enabled);
|
||||||
|
return const Result(true);
|
||||||
|
} else {
|
||||||
|
_log.warning('Inline stream enablement failed');
|
||||||
|
_onStreamEnablementFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (resumed == null) {
|
if (resumed == null) {
|
||||||
_log.warning('Inline stream resumption failed');
|
_log.warning('Inline stream resumption failed');
|
||||||
await _onStreamResumptionFailed();
|
await _onStreamResumptionFailed();
|
||||||
state = NegotiatorState.retryLater;
|
state = NegotiatorState.done;
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -915,7 +915,7 @@ void main() {
|
|||||||
</stream:features>''',
|
</stream:features>''',
|
||||||
),
|
),
|
||||||
StanzaExpectation(
|
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>",
|
"<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' resume='true' /></bind><resume xmlns='urn:xmpp:sm:3' previd='test-prev-id' h='2' /></authenticate>",
|
||||||
'''
|
'''
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
<success xmlns='urn:xmpp:sasl:2'>
|
||||||
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
||||||
@ -982,4 +982,104 @@ void main() {
|
|||||||
);
|
);
|
||||||
expect(conn.resource, 'test-resource');
|
expect(conn.resource, 'test-resource');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test failed 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' resume='true' /></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/test-resource</authorization-identifier>
|
||||||
|
<failed xmlns='urn:xmpp:sm:3' />
|
||||||
|
<bound xmlns='urn:xmpp:sm:3'>
|
||||||
|
<failed xmlns='urn:xmpp:sm:3' />
|
||||||
|
</bound>
|
||||||
|
</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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
final smn = StreamManagementNegotiator();
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
smn,
|
||||||
|
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(smn.isResumed, false);
|
||||||
|
expect(smn.resumeFailed, true);
|
||||||
|
expect(smn.streamEnablementFailed, true);
|
||||||
|
expect(conn.resource, 'test-resource');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user