Compare commits
No commits in common. "2947e2c5393b263bd41557518230dd6087c16671" and "52ad9a7ddb80a7f8ad3287e2ccc5b924f7fb0d97" have entirely different histories.
2947e2c539
...
52ad9a7ddb
25
flake.lock
25
flake.lock
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1667395993,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -31,27 +31,10 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1680273054,
|
|
||||||
"narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
31
flake.nix
31
flake.nix
@ -2,11 +2,10 @@
|
|||||||
description = "moxxmpp";
|
description = "moxxmpp";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
|
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
|
||||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config = {
|
config = {
|
||||||
@ -14,9 +13,6 @@
|
|||||||
allowUnfree = true;
|
allowUnfree = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
unstable = import nixpkgs-unstable {
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
android = pkgs.androidenv.composeAndroidPackages {
|
android = pkgs.androidenv.composeAndroidPackages {
|
||||||
# TODO: Find a way to pin these
|
# TODO: Find a way to pin these
|
||||||
#toolsVersion = "26.1.1";
|
#toolsVersion = "26.1.1";
|
||||||
@ -50,26 +46,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShell = let
|
devShell = pkgs.mkShell {
|
||||||
prosody-newer-community-modules = unstable.prosody.overrideAttrs (old: {
|
|
||||||
communityModules = pkgs.fetchhg {
|
|
||||||
url = "https://hg.prosody.im/prosody-modules";
|
|
||||||
rev = "e3a3a6c86a9f";
|
|
||||||
sha256 = "sha256-C2x6PCv0sYuj4/SroDOJLsNPzfeNCodYKbMqmNodFrk=";
|
|
||||||
};
|
|
||||||
|
|
||||||
src = pkgs.fetchhg {
|
|
||||||
url = "https://hg.prosody.im/trunk";
|
|
||||||
rev = "8a2f75e38eb2";
|
|
||||||
sha256 = "sha256-zMNp9+wQ/hvUVyxFl76DqCVzQUPP8GkNdstiTDkG8Hw=";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
prosody-sasl2 = prosody-newer-community-modules.override {
|
|
||||||
withCommunityModules = [
|
|
||||||
"sasl2" "sasl2_fast" "sasl2_sm" "sasl2_bind2"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
flutter pinnedJDK android.platform-tools dart # Dart
|
flutter pinnedJDK android.platform-tools dart # Dart
|
||||||
gitlint # Code hygiene
|
gitlint # Code hygiene
|
||||||
@ -94,10 +71,6 @@
|
|||||||
|
|
||||||
# For the scripts in ./scripts/
|
# For the scripts in ./scripts/
|
||||||
pythonEnv
|
pythonEnv
|
||||||
|
|
||||||
# For integration testing against a local prosody server
|
|
||||||
prosody-sasl2
|
|
||||||
mkcert
|
|
||||||
];
|
];
|
||||||
|
|
||||||
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
||||||
|
6
integration_tests/.gitignore
vendored
6
integration_tests/.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
# Files and directories created by pub.
|
|
||||||
.dart_tool/
|
|
||||||
.packages
|
|
||||||
|
|
||||||
# Conventional directory for build output.
|
|
||||||
build/
|
|
@ -1,5 +0,0 @@
|
|||||||
# Integration Tests
|
|
||||||
|
|
||||||
The included `./prosody.cfg.lua` config file must be used for integration testing.
|
|
||||||
Additionally, ensure that a user `testuser@localhost` with the password `abc123`
|
|
||||||
exists. Note that this currently requires prosody-trunk.
|
|
@ -1 +0,0 @@
|
|||||||
include: ../analysis_options.yaml
|
|
@ -1,24 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEAzCCAmugAwIBAgIQd61NPnP8++X7h8a+85C6DjANBgkqhkiG9w0BAQsFADBZ
|
|
||||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFzAVBgNVBAsMDmFsZXhh
|
|
||||||
bmRlckBtaWt1MR4wHAYDVQQDDBVta2NlcnQgYWxleGFuZGVyQG1pa3UwHhcNMjMw
|
|
||||||
NDAyMTM1ODIxWhcNMjUwNzAyMTM1ODIxWjBCMScwJQYDVQQKEx5ta2NlcnQgZGV2
|
|
||||||
ZWxvcG1lbnQgY2VydGlmaWNhdGUxFzAVBgNVBAsMDmFsZXhhbmRlckBtaWt1MIIB
|
|
||||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1DElEXPY+VDQP7cSikK0ne0K
|
|
||||||
gDgorGYPG9R7lOeuPLHyFYYry78+hB037OT0BOyA2uTu1yrog0dI/4YGicPDIqXh
|
|
||||||
IgHfjV+4kMi5SgO7ECWOBmZFqTC3bBwvbNtoW40aFjYSFaOkm/nnfp+nalEJJZ/N
|
|
||||||
kSkD4gdT3pH1ClsovlI4BlsxeIoJtyGzxMidJVXDAqMNraLatzJBwnT3OEs93xTf
|
|
||||||
7Kd1KUpQp9OZFrGi15zv/n6tCmrcC3xMOVHuYkhW0UCTFmev7ZqbghQsQ9N9s0E6
|
|
||||||
kk9rUf9xtMNH4Af6+2YRkT1DAGQ6FkXl1nQdB5H5XRgOBl+3k9s8wUrxQvQddQID
|
|
||||||
AQABo14wXDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
|
|
||||||
VR0jBBgwFoAU54aUZ+dytAOBTsYIdGtSnjiig/gwFAYDVR0RBA0wC4IJbG9jYWxo
|
|
||||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBgQBU8p7Ua0Cs+lXlWmtCh2j+YF9R+dvc+3Iw
|
|
||||||
dYEzCmYd375uxPctyHXW0yYjyuH9WuYn0F7OicEFEeC2+exHND+/z0J2Zv5yu34r
|
|
||||||
SfgHVfvE/Vxisn9InYrUCVtfRwLDF3HgLyIlm8FVzIyiIANhpe6vJdqjEWTsiL2X
|
|
||||||
I6hoDf1xlRgEqUx+Wxl2IFWrg+1SPPGTQzDPImiRlz8d+9ZJ9v48vaV5+aITMvDP
|
|
||||||
Gfm/bnNXXd5Gf7nGwL8zFHiwLoYQ5AUYl0IfXYwFAXJ72+LjiRT33IOidVJF0gsQ
|
|
||||||
6k9cTsc4lIrt4FOzdchalbF1Eu2prieWoZxz0apG8OuUeAhaB+t8kT6swAkwvkLW
|
|
||||||
OnlSATm9Cls9Pc4XDHTbZlbMmwF2Jmukgz/l1vlTutt4ZgZwQkSEa9Qfoi9Zym0R
|
|
||||||
iKls1CgD49zguR/cFDKK3agvfv6Afw6HdgaS/WqcI/Ros7b+RCkbAlAG5gqr6BLQ
|
|
||||||
8RGyVjZSC4Mz/ddcnMEpRAnjuFJjhGA=
|
|
||||||
-----END CERTIFICATE-----
|
|
@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUMSURc9j5UNA/
|
|
||||||
txKKQrSd7QqAOCisZg8b1HuU5648sfIVhivLvz6EHTfs5PQE7IDa5O7XKuiDR0j/
|
|
||||||
hgaJw8MipeEiAd+NX7iQyLlKA7sQJY4GZkWpMLdsHC9s22hbjRoWNhIVo6Sb+ed+
|
|
||||||
n6dqUQkln82RKQPiB1PekfUKWyi+UjgGWzF4igm3IbPEyJ0lVcMCow2totq3MkHC
|
|
||||||
dPc4Sz3fFN/sp3UpSlCn05kWsaLXnO/+fq0KatwLfEw5Ue5iSFbRQJMWZ6/tmpuC
|
|
||||||
FCxD032zQTqST2tR/3G0w0fgB/r7ZhGRPUMAZDoWReXWdB0HkfldGA4GX7eT2zzB
|
|
||||||
SvFC9B11AgMBAAECggEAYaj4yY6LFzxVjG2i79WBsYnOonK2bZpPa9ygwEjdTXwM
|
|
||||||
0lE9SPoNONsFyVca5EVBjP1+27MY7orZkxlJWxCpeAHmmzNHg5bBqIlpliIfb3AJ
|
|
||||||
bPKXLyaH1Q8n2K8m2bQYhI6ARktZ0Jv1KrcqY2lGj3V8NEovSlFbDX4ZzJlmKCly
|
|
||||||
d4Ia6eQ7f9AjgsOwpQGeCTF7WLaVDnch6D4JfCGrW08lFeaqogiBQczsOE3hcNSd
|
|
||||||
tEul21Z0CkC7Iiw28KdkApPINquo1VYdAcOvUCOXkwJfPC1gsJwK4O2jxfi9v5NF
|
|
||||||
uU1niK0/00b396pQKvXpkfViynexwzK0MZCoo3zuQQKBgQDzaZexcniQNDyWqN3C
|
|
||||||
oMe4V3rnxs+aO/lu8Ed3mng+Jf4vuarZlxNot7WRBMGT/T+b7/UIrqRJy50CYAPY
|
|
||||||
3RRR84tLg3UMwUWhDYsPucNc2icODBG4c+QWJ300W19r+J+iT8PwS9AbH2n094Rn
|
|
||||||
LCRYFrX5aMsgIH5uwuncKzweMQKBgQDfKj2i1ptC53aOcr1tMCFYcnMGtaAZ8u6+
|
|
||||||
cKSgnzKlTw/g0EYlGcETUnCyZe0oVYWp3y859FBXU0JMDmxu84aYEZNF6BwRVlpF
|
|
||||||
feQgtUFZHyf9MepQGhjIJ5El8n7jhh1bsBY18QbDFe6/GtqPx/mQEF7vE+wPFl9h
|
|
||||||
putwdv3OhQKBgGKPyi2/BVSW4kW7IPiTM+vP+GNrnFp+mHS0dKvYb4HyzmcyzhyH
|
|
||||||
UQOhB7Mt8thivmP9GQIn/TwoZ24zxLsGYhkA/dFY7Id6pyAcpMd8V7/8Ub4dYvuG
|
|
||||||
acASw1709MF6jeEiXVuqxxyEbtoTc5h3Rkwo/gx8w2tB3RAqepl9JD2xAoGAfVL3
|
|
||||||
ci8a2iOqTKza/Cp/T3BWcHonAuuOb5xKl3lPs84GmLXd7o/cAcHWUBk1aeU9Pvx7
|
|
||||||
RQyS4bd8D8I52sUf3N5h2mxS9tmLsGLWbhfcLvR0PJh/gaRmLmEp/imEYLm8WvU0
|
|
||||||
Q+6rYXs7rE6kVwJygBjxd0m003Q49FoM9gec2RECgYEA5SLAe2UmJSLIb0DKk27o
|
|
||||||
nSfARDSdi9N40vIjDFHmDRdKTOYicED/f7KqXnxVpvFxDdCvJ7xeC4V7vkaqiiwd
|
|
||||||
/oMLQq0GjmBxG/PNd1AFIWDydyH+JcY6U4XWIzIw92OKVYC/KMvd2f9orTfmDyAU
|
|
||||||
RsGMfgV90kCzouAZKy3yPmo=
|
|
||||||
-----END PRIVATE KEY-----
|
|
@ -1,56 +0,0 @@
|
|||||||
admins = { }
|
|
||||||
plugin_paths = {}
|
|
||||||
|
|
||||||
modules_enabled = {
|
|
||||||
-- Generally required
|
|
||||||
"disco"; -- Service discovery
|
|
||||||
"roster"; -- Allow users to have a roster. Recommended ;)
|
|
||||||
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
|
||||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
|
||||||
|
|
||||||
-- Not essential, but recommended
|
|
||||||
"blocklist"; -- Allow users to block communications with other users
|
|
||||||
"bookmarks"; -- Synchronise the list of open rooms between clients
|
|
||||||
"carbons"; -- Keep multiple online clients in sync
|
|
||||||
"dialback"; -- Support for verifying remote servers using DNS
|
|
||||||
"limits"; -- Enable bandwidth limiting for XMPP connections
|
|
||||||
"pep"; -- Allow users to store public and private data in their account
|
|
||||||
"private"; -- Legacy account storage mechanism (XEP-0049)
|
|
||||||
"smacks"; -- Stream management and resumption (XEP-0198)
|
|
||||||
"vcard4"; -- User profiles (stored in PEP)
|
|
||||||
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
|
|
||||||
|
|
||||||
-- Nice to have
|
|
||||||
"csi_simple"; -- Simple but effective traffic optimizations for mobile devices
|
|
||||||
"invites"; -- Create and manage invites
|
|
||||||
"invites_adhoc"; -- Allow admins/users to create invitations via their client
|
|
||||||
"invites_register"; -- Allows invited users to create accounts
|
|
||||||
"ping"; -- Replies to XMPP pings with pongs
|
|
||||||
"register"; -- Allow users to register on this server using a client and change passwords
|
|
||||||
"time"; -- Let others know the time here on this server
|
|
||||||
"uptime"; -- Report how long server has been running
|
|
||||||
"version"; -- Replies to server version requests
|
|
||||||
|
|
||||||
-- SASL2
|
|
||||||
"sasl2";
|
|
||||||
"sasl2_sm";
|
|
||||||
"sasl2_fast";
|
|
||||||
"sasl2_bind2";
|
|
||||||
}
|
|
||||||
|
|
||||||
s2s_secure_auth = false
|
|
||||||
|
|
||||||
-- Authentication
|
|
||||||
authentication = "internal_plain"
|
|
||||||
|
|
||||||
-- Storage
|
|
||||||
storage = "internal"
|
|
||||||
data_path = "/tmp/prosody-data/"
|
|
||||||
log = {
|
|
||||||
debug = "*console";
|
|
||||||
}
|
|
||||||
|
|
||||||
pidfile = "/tmp/prosody.pid"
|
|
||||||
|
|
||||||
VirtualHost "localhost"
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
name: integration_tests
|
|
||||||
description: A sample command-line application.
|
|
||||||
version: 1.0.0
|
|
||||||
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.18.0 <3.0.0'
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
logging: ^1.0.2
|
|
||||||
moxxmpp: 0.2.0
|
|
||||||
moxxmpp_socket_tcp: 0.2.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
lints: ^2.0.0
|
|
||||||
test: ^1.16.0
|
|
||||||
very_good_analysis: ^3.0.1
|
|
@ -1,67 +0,0 @@
|
|||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
|
||||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
|
||||||
@override
|
|
||||||
bool onBadCertificate(dynamic certificate, String domain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
Logger.root.level = Level.ALL;
|
|
||||||
Logger.root.onRecord.listen((record) {
|
|
||||||
// ignore: avoid_print
|
|
||||||
print(
|
|
||||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test authenticating against Prosody with SASL2, Bind2, and FAST', () async {
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
TestingTCPSocketWrapper(),
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@localhost'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: false,
|
|
||||||
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 5222,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final csi = CSIManager();
|
|
||||||
await csi.setInactive(sendNonza: false);
|
|
||||||
await conn.registerManagers([
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
FASTSaslNegotiator(),
|
|
||||||
Bind2Negotiator(),
|
|
||||||
StartTlsNegotiator(),
|
|
||||||
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<bool>(), true);
|
|
||||||
expect(conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state, NegotiatorState.done);
|
|
||||||
expect(conn.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!.fastToken != null, true,);
|
|
||||||
});
|
|
||||||
}
|
|
@ -3,9 +3,6 @@
|
|||||||
- **BREAKING**: Removed `connectAwaitable` and merged it with `connect`.
|
- **BREAKING**: Removed `connectAwaitable` and merged it with `connect`.
|
||||||
- **BREAKING**: Removed `allowPlainAuth` from `ConnectionSettings`. If you don't want to use SASL PLAIN, don't register the negotiator. If you want to only conditionally use SASL PLAIN, extend the `SaslPlainNegotiator` and override its `matchesFeature` method to only call the super method when SASL PLAIN should be used.
|
- **BREAKING**: Removed `allowPlainAuth` from `ConnectionSettings`. If you don't want to use SASL PLAIN, don't register the negotiator. If you want to only conditionally use SASL PLAIN, extend the `SaslPlainNegotiator` and override its `matchesFeature` method to only call the super method when SASL PLAIN should be used.
|
||||||
- **BREAKING**: The user avatar's `subscribe` and `unsubscribe` no longer subscribe to the `:data` PubSub nodes
|
- **BREAKING**: The user avatar's `subscribe` and `unsubscribe` no longer subscribe to the `:data` PubSub nodes
|
||||||
- Renamed `ResourceBindingSuccessEvent` to `ResourceBoundEvent`
|
|
||||||
- **BREAKING**: Removed `isFeatureSupported` from the manager attributes. The managers now all have a method `isFeatureSupported` that works the same
|
|
||||||
- The `PresenceManager` is now optional
|
|
||||||
|
|
||||||
## 0.1.6+1
|
## 0.1.6+1
|
||||||
|
|
||||||
|
@ -18,17 +18,17 @@ export 'package:moxxmpp/src/namespaces.dart';
|
|||||||
export 'package:moxxmpp/src/negotiators/manager.dart';
|
export 'package:moxxmpp/src/negotiators/manager.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/resource_binding.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/sasl/plain.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/sasl/scram.dart';
|
||||||
|
export 'package:moxxmpp/src/negotiators/starttls.dart';
|
||||||
export 'package:moxxmpp/src/ping.dart';
|
export 'package:moxxmpp/src/ping.dart';
|
||||||
export 'package:moxxmpp/src/presence.dart';
|
export 'package:moxxmpp/src/presence.dart';
|
||||||
export 'package:moxxmpp/src/reconnect.dart';
|
export 'package:moxxmpp/src/reconnect.dart';
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_2782.dart';
|
export 'package:moxxmpp/src/rfcs/rfc_2782.dart';
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
export 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/resource_binding.dart';
|
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
|
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/plain.dart';
|
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/scram.dart';
|
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_6120/starttls.dart';
|
|
||||||
export 'package:moxxmpp/src/roster/errors.dart';
|
export 'package:moxxmpp/src/roster/errors.dart';
|
||||||
export 'package:moxxmpp/src/roster/roster.dart';
|
export 'package:moxxmpp/src/roster/roster.dart';
|
||||||
export 'package:moxxmpp/src/roster/state.dart';
|
export 'package:moxxmpp/src/roster/state.dart';
|
||||||
@ -38,7 +38,6 @@ 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';
|
||||||
@ -77,11 +76,6 @@ export 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0384/xep_0384.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/xep_0384.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0385.dart';
|
export 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0386.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/xep_0414.dart';
|
export 'package:moxxmpp/src/xeps/xep_0414.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
|
@ -9,7 +9,6 @@ import 'package:moxxmpp/src/connectivity.dart';
|
|||||||
import 'package:moxxmpp/src/errors.dart';
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/iq.dart';
|
import 'package:moxxmpp/src/iq.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
@ -64,15 +63,14 @@ enum StanzaFromType {
|
|||||||
|
|
||||||
/// Nonza describing the XMPP stream header.
|
/// Nonza describing the XMPP stream header.
|
||||||
class StreamHeaderNonza extends XMLNode {
|
class StreamHeaderNonza extends XMLNode {
|
||||||
StreamHeaderNonza(JID jid)
|
StreamHeaderNonza(String serverDomain)
|
||||||
: super(
|
: super(
|
||||||
tag: 'stream:stream',
|
tag: 'stream:stream',
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
'xmlns': stanzaXmlns,
|
'xmlns': stanzaXmlns,
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
'xmlns:stream': streamXmlns,
|
'xmlns:stream': streamXmlns,
|
||||||
'to': jid.domain,
|
'to': serverDomain,
|
||||||
'from': jid.toBare().toString(),
|
|
||||||
'xml:lang': 'en',
|
'xml:lang': 'en',
|
||||||
},
|
},
|
||||||
closeTag: false,
|
closeTag: false,
|
||||||
@ -135,6 +133,9 @@ class XmppConnection {
|
|||||||
StreamController.broadcast();
|
StreamController.broadcast();
|
||||||
final Map<String, XmppManagerBase> _xmppManagers = {};
|
final Map<String, XmppManagerBase> _xmppManagers = {};
|
||||||
|
|
||||||
|
/// Disco info we got after binding a resource (xmlns)
|
||||||
|
final List<String> _serverFeatures = List.empty(growable: true);
|
||||||
|
|
||||||
/// The buffer object to keep split up stanzas together
|
/// The buffer object to keep split up stanzas together
|
||||||
final XmlStreamBuffer _streamBuffer = XmlStreamBuffer();
|
final XmlStreamBuffer _streamBuffer = XmlStreamBuffer();
|
||||||
|
|
||||||
@ -149,12 +150,11 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// The currently bound resource or '' if none has been bound yet.
|
/// The currently bound resource or '' if none has been bound yet.
|
||||||
String _resource = '';
|
String _resource = '';
|
||||||
String get resource => _resource;
|
|
||||||
|
|
||||||
/// True if we are authenticated. False if not.
|
/// True if we are authenticated. False if not.
|
||||||
bool _isAuthenticated = false;
|
bool _isAuthenticated = false;
|
||||||
|
|
||||||
/// Timer for the connecting timeout.
|
/// Timer for the connecting timeout
|
||||||
Timer? _connectingTimeoutTimer;
|
Timer? _connectingTimeoutTimer;
|
||||||
|
|
||||||
/// Completers for certain actions
|
/// Completers for certain actions
|
||||||
@ -201,6 +201,8 @@ class XmppConnection {
|
|||||||
|
|
||||||
ReconnectionPolicy get reconnectionPolicy => _reconnectionPolicy;
|
ReconnectionPolicy get reconnectionPolicy => _reconnectionPolicy;
|
||||||
|
|
||||||
|
List<String> get serverFeatures => _serverFeatures;
|
||||||
|
|
||||||
bool get isAuthenticated => _isAuthenticated;
|
bool get isAuthenticated => _isAuthenticated;
|
||||||
|
|
||||||
/// Return the registered feature negotiator that has id [id]. Returns null if
|
/// Return the registered feature negotiator that has id [id]. Returns null if
|
||||||
@ -219,6 +221,7 @@ class XmppConnection {
|
|||||||
sendEvent: _sendEvent,
|
sendEvent: _sendEvent,
|
||||||
getConnectionSettings: () => _connectionSettings,
|
getConnectionSettings: () => _connectionSettings,
|
||||||
getManagerById: getManagerById,
|
getManagerById: getManagerById,
|
||||||
|
isFeatureSupported: _serverFeatures.contains,
|
||||||
getFullJID: () => _connectionSettings.jid.withResource(_resource),
|
getFullJID: () => _connectionSettings.jid.withResource(_resource),
|
||||||
getSocket: () => _socket,
|
getSocket: () => _socket,
|
||||||
getConnection: () => this,
|
getConnection: () => this,
|
||||||
@ -250,29 +253,13 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the current connection as authenticated.
|
|
||||||
void _setAuthenticated() {
|
|
||||||
_sendEvent(AuthenticationSuccessEvent());
|
|
||||||
_isAuthenticated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove [feature] from the stream features we are currently negotiating.
|
|
||||||
void _removeNegotiatingFeature(String feature) {
|
|
||||||
_streamFeatures.removeWhere((node) {
|
|
||||||
return node.attributes['xmlns'] == feature;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a list of negotiator with the connection.
|
/// Register a list of negotiator with the connection.
|
||||||
Future<void> registerFeatureNegotiators(
|
void registerFeatureNegotiators(List<XmppFeatureNegotiatorBase> negotiators) {
|
||||||
List<XmppFeatureNegotiatorBase> negotiators,
|
|
||||||
) async {
|
|
||||||
for (final negotiator in negotiators) {
|
for (final negotiator in negotiators) {
|
||||||
_log.finest('Registering ${negotiator.id}');
|
_log.finest('Registering ${negotiator.id}');
|
||||||
negotiator.register(
|
negotiator.register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
sendRawXML,
|
sendRawXML,
|
||||||
() => this,
|
|
||||||
() => _connectionSettings,
|
() => _connectionSettings,
|
||||||
_sendEvent,
|
_sendEvent,
|
||||||
getNegotiatorById,
|
getNegotiatorById,
|
||||||
@ -280,19 +267,12 @@ class XmppConnection {
|
|||||||
() => _connectionSettings.jid.withResource(_resource),
|
() => _connectionSettings.jid.withResource(_resource),
|
||||||
() => _socket,
|
() => _socket,
|
||||||
() => _isAuthenticated,
|
() => _isAuthenticated,
|
||||||
_setAuthenticated,
|
|
||||||
setResource,
|
|
||||||
_removeNegotiatingFeature,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_featureNegotiators[negotiator.id] = negotiator;
|
_featureNegotiators[negotiator.id] = negotiator;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.finest('Negotiators registered');
|
_log.finest('Negotiators registered');
|
||||||
|
|
||||||
for (final negotiator in _featureNegotiators.values) {
|
|
||||||
await negotiator.postRegisterCallback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset all registered negotiators.
|
/// Reset all registered negotiators.
|
||||||
@ -316,8 +296,13 @@ 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() {
|
||||||
return getManagerById(presenceManager);
|
assert(
|
||||||
|
_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.
|
||||||
@ -469,10 +454,10 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an [XMLNode] without any further processing to the server.
|
/// Sends an [XMLNode] without any further processing to the server.
|
||||||
void sendRawXML(XMLNode node) {
|
void sendRawXML(XMLNode node, {String? redact}) {
|
||||||
final string = node.toXml();
|
final string = node.toXml();
|
||||||
_log.finest('==> $string');
|
_log.finest('==> $string');
|
||||||
_socket.write(string);
|
_socket.write(string, redact: redact);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends [raw] to the server.
|
/// Sends [raw] to the server.
|
||||||
@ -672,14 +657,9 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the resource of the connection
|
/// Sets the resource of the connection
|
||||||
@visibleForTesting
|
void setResource(String resource) {
|
||||||
void setResource(String resource, {bool triggerEvent = true}) {
|
|
||||||
_log.finest('Updating _resource to $resource');
|
_log.finest('Updating _resource to $resource');
|
||||||
_resource = resource;
|
_resource = resource;
|
||||||
|
|
||||||
if (triggerEvent) {
|
|
||||||
_sendEvent(ResourceBoundEvent(resource));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the connection's events as a stream.
|
/// Returns the connection's events as a stream.
|
||||||
@ -859,6 +839,9 @@ class XmppConnection {
|
|||||||
// Tell consumers of the event stream that we're done with stream feature
|
// Tell consumers of the event stream that we're done with stream feature
|
||||||
// negotiations
|
// negotiations
|
||||||
await _sendEvent(StreamNegotiationsDoneEvent());
|
await _sendEvent(StreamNegotiationsDoneEvent());
|
||||||
|
|
||||||
|
// Send out initial presence
|
||||||
|
await getPresenceManager().sendInitialPresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _executeCurrentNegotiator(XMLNode nonza) async {
|
Future<void> _executeCurrentNegotiator(XMLNode nonza) async {
|
||||||
@ -912,7 +895,10 @@ class XmppConnection {
|
|||||||
_streamFeatures.clear();
|
_streamFeatures.clear();
|
||||||
_sendStreamHeader();
|
_sendStreamHeader();
|
||||||
} else {
|
} else {
|
||||||
_removeNegotiatingFeature(_currentNegotiator!.negotiatingXmlns);
|
_streamFeatures.removeWhere((node) {
|
||||||
|
return node.attributes['xmlns'] ==
|
||||||
|
_currentNegotiator!.negotiatingXmlns;
|
||||||
|
});
|
||||||
_currentNegotiator = null;
|
_currentNegotiator = null;
|
||||||
|
|
||||||
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||||
@ -1024,12 +1010,25 @@ class XmppConnection {
|
|||||||
Future<void> _sendEvent(XmppEvent event) async {
|
Future<void> _sendEvent(XmppEvent event) async {
|
||||||
_log.finest('Event: ${event.toString()}');
|
_log.finest('Event: ${event.toString()}');
|
||||||
|
|
||||||
|
// Specific event handling
|
||||||
|
if (event is ResourceBindingSuccessEvent) {
|
||||||
|
_log.finest(
|
||||||
|
'Received ResourceBindingSuccessEvent. Setting _resource to ${event.resource}',
|
||||||
|
);
|
||||||
|
setResource(event.resource);
|
||||||
|
|
||||||
|
_log.finest('Resetting _serverFeatures');
|
||||||
|
_serverFeatures.clear();
|
||||||
|
} else if (event is AuthenticationSuccessEvent) {
|
||||||
|
_log.finest(
|
||||||
|
'Received AuthenticationSuccessEvent. Setting _isAuthenticated to true',
|
||||||
|
);
|
||||||
|
_isAuthenticated = true;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -1039,11 +1038,11 @@ class XmppConnection {
|
|||||||
_socket.write(
|
_socket.write(
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'xml',
|
tag: 'xml',
|
||||||
attributes: {'version': '1.0'},
|
attributes: <String, String>{'version': '1.0'},
|
||||||
closeTag: false,
|
closeTag: false,
|
||||||
isDeclaration: true,
|
isDeclaration: true,
|
||||||
children: [
|
children: [
|
||||||
StreamHeaderNonza(_connectionSettings.jid),
|
StreamHeaderNonza(_connectionSettings.jid.domain),
|
||||||
],
|
],
|
||||||
).toXml(),
|
).toXml(),
|
||||||
);
|
);
|
||||||
@ -1068,7 +1067,7 @@ class XmppConnection {
|
|||||||
await _reconnectionPolicy.setShouldReconnect(false);
|
await _reconnectionPolicy.setShouldReconnect(false);
|
||||||
|
|
||||||
if (triggeredByUser) {
|
if (triggeredByUser) {
|
||||||
getPresenceManager()?.sendUnavailablePresence();
|
getPresenceManager().sendUnavailablePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
_socket.prepareDisconnect();
|
_socket.prepareDisconnect();
|
||||||
@ -1088,6 +1087,10 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Make sure that all required managers are registered
|
/// Make sure that all required managers are registered
|
||||||
void _runPreConnectionAssertions() {
|
void _runPreConnectionAssertions() {
|
||||||
|
assert(
|
||||||
|
_xmppManagers.containsKey(presenceManager),
|
||||||
|
'A PresenceManager is mandatory',
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
_xmppManagers.containsKey(rosterManager),
|
_xmppManagers.containsKey(rosterManager),
|
||||||
'A RosterManager is mandatory',
|
'A RosterManager is mandatory',
|
||||||
@ -1135,9 +1138,7 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lastResource != null) {
|
if (lastResource != null) {
|
||||||
setResource(lastResource, triggerEvent: false);
|
setResource(lastResource);
|
||||||
} else {
|
|
||||||
setResource('', triggerEvent: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
_enableReconnectOnSuccess = enableReconnectOnSuccess;
|
||||||
@ -1158,16 +1159,13 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final smManager = getStreamManagementManager();
|
final smManager = getStreamManagementManager();
|
||||||
var host = _connectionSettings.host;
|
String? host;
|
||||||
var port = _connectionSettings.port;
|
int? port;
|
||||||
if (smManager?.state.streamResumptionLocation != null) {
|
if (smManager?.state.streamResumptionLocation != null) {
|
||||||
// TODO(Unknown): Maybe wrap this in a try catch?
|
// TODO(Unknown): Maybe wrap this in a try catch?
|
||||||
final parsed = Uri.parse(smManager!.state.streamResumptionLocation!);
|
final parsed = Uri.parse(smManager!.state.streamResumptionLocation!);
|
||||||
host = parsed.host;
|
host = parsed.host;
|
||||||
port = parsed.port;
|
port = parsed.port;
|
||||||
} else {
|
|
||||||
host = _connectionSettings.host;
|
|
||||||
port = _connectionSettings.port;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await _socket.connect(
|
final result = await _socket.connect(
|
||||||
|
@ -160,10 +160,8 @@ class StreamManagementEnabledEvent extends XmppEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we bound a resource
|
/// Triggered when we bound a resource
|
||||||
class ResourceBoundEvent extends XmppEvent {
|
class ResourceBindingSuccessEvent extends XmppEvent {
|
||||||
ResourceBoundEvent(this.resource);
|
ResourceBindingSuccessEvent({required this.resource});
|
||||||
|
|
||||||
/// The resource that was just bound.
|
|
||||||
final String resource;
|
final String resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ class XmppManagerAttributes {
|
|||||||
required this.getManagerById,
|
required this.getManagerById,
|
||||||
required this.sendEvent,
|
required this.sendEvent,
|
||||||
required this.getConnectionSettings,
|
required this.getConnectionSettings,
|
||||||
|
required this.isFeatureSupported,
|
||||||
required this.getFullJID,
|
required this.getFullJID,
|
||||||
required this.getSocket,
|
required this.getSocket,
|
||||||
required this.getConnection,
|
required this.getConnection,
|
||||||
@ -44,6 +45,9 @@ class XmppManagerAttributes {
|
|||||||
/// (Maybe) Get a Manager attached to the connection by its Id.
|
/// (Maybe) Get a Manager attached to the connection by its Id.
|
||||||
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
||||||
|
|
||||||
|
/// Returns true if a server feature is supported
|
||||||
|
final bool Function(String) isFeatureSupported;
|
||||||
|
|
||||||
/// Returns the full JID of the current account
|
/// Returns the full JID of the current account
|
||||||
final JID Function() getFullJID;
|
final JID Function() getFullJID;
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import 'package:moxxmpp/src/managers/data.dart';
|
|||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
@ -32,29 +31,6 @@ abstract class XmppManagerBase {
|
|||||||
return _managerAttributes;
|
return _managerAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves to true when the server supports the disco feature [xmlns]. Resolves
|
|
||||||
/// to false when either the disco request fails or the server does not
|
|
||||||
/// support [xmlns].
|
|
||||||
/// Note that this function requires a registered DiscoManager.
|
|
||||||
@protected
|
|
||||||
Future<bool> isFeatureSupported(String xmlns) async {
|
|
||||||
final dm = _managerAttributes.getManagerById<DiscoManager>(discoManager);
|
|
||||||
assert(
|
|
||||||
dm != null,
|
|
||||||
'The DiscoManager must be registered for isFeatureSupported to work',
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await dm!.discoInfoQuery(
|
|
||||||
_managerAttributes.getConnectionSettings().jid.domain,
|
|
||||||
shouldEncrypt: false,
|
|
||||||
);
|
|
||||||
if (result.isType<DiscoError>()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.get<DiscoInfo>().features.contains(xmlns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
||||||
/// send. These are run before the stanza is sent. The higher the value of the
|
/// send. These are run before the stanza is sent. The higher the value of the
|
||||||
/// handler's priority, the earlier it is run.
|
/// handler's priority, the earlier it is run.
|
||||||
|
@ -116,12 +116,6 @@ const omemoBundlesXmlns = 'urn:xmpp:omemo:2:bundles';
|
|||||||
// XEP-0385
|
// XEP-0385
|
||||||
const simsXmlns = 'urn:xmpp:sims:1';
|
const simsXmlns = 'urn:xmpp:sims:1';
|
||||||
|
|
||||||
// XEP-0386
|
|
||||||
const bind2Xmlns = 'urn:xmpp:bind:0';
|
|
||||||
|
|
||||||
// XEP-0388
|
|
||||||
const sasl2Xmlns = 'urn:xmpp:sasl:2';
|
|
||||||
|
|
||||||
// XEP-0420
|
// XEP-0420
|
||||||
const sceXmlns = 'urn:xmpp:sce:1';
|
const sceXmlns = 'urn:xmpp:sce:1';
|
||||||
|
|
||||||
@ -160,6 +154,3 @@ 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';
|
|
||||||
|
@ -7,7 +7,3 @@ const rosterNegotiator = 'im.moxxmpp.core.roster';
|
|||||||
const resourceBindingNegotiator = 'im.moxxmpp.core.resource';
|
const resourceBindingNegotiator = 'im.moxxmpp.core.resource';
|
||||||
const streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
|
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 bind2Negotiator = 'org.moxxmpp.bind2';
|
|
||||||
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
|
||||||
const carbonsNegotiator = 'org.moxxmpp.bind2.carbons';
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
|
||||||
import 'package:moxxmpp/src/errors.dart';
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
@ -29,7 +27,6 @@ abstract class NegotiatorError extends XmppError {}
|
|||||||
class NegotiatorAttributes {
|
class NegotiatorAttributes {
|
||||||
const NegotiatorAttributes(
|
const NegotiatorAttributes(
|
||||||
this.sendNonza,
|
this.sendNonza,
|
||||||
this.getConnection,
|
|
||||||
this.getConnectionSettings,
|
this.getConnectionSettings,
|
||||||
this.sendEvent,
|
this.sendEvent,
|
||||||
this.getNegotiatorById,
|
this.getNegotiatorById,
|
||||||
@ -37,21 +34,15 @@ class NegotiatorAttributes {
|
|||||||
this.getFullJID,
|
this.getFullJID,
|
||||||
this.getSocket,
|
this.getSocket,
|
||||||
this.isAuthenticated,
|
this.isAuthenticated,
|
||||||
this.setAuthenticated,
|
|
||||||
this.setResource,
|
|
||||||
this.removeNegotiatingFeature,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Sends the nonza nonza and optionally redacts it in logs if redact is not null.
|
/// Sends the nonza nonza and optionally redacts it in logs if redact is not null.
|
||||||
final void Function(XMLNode nonza) sendNonza;
|
final void Function(XMLNode nonza, {String? redact}) sendNonza;
|
||||||
|
|
||||||
/// Returns the connection settings.
|
/// Returns the connection settings.
|
||||||
final ConnectionSettings Function() getConnectionSettings;
|
final ConnectionSettings Function() getConnectionSettings;
|
||||||
|
|
||||||
/// Returns the connection object.
|
/// Send an event event to the connection's event bus
|
||||||
final XmppConnection Function() getConnection;
|
|
||||||
|
|
||||||
/// Send an event event to the connection's event bus.
|
|
||||||
final Future<void> Function(XmppEvent event) sendEvent;
|
final Future<void> Function(XmppEvent event) sendEvent;
|
||||||
|
|
||||||
/// Returns the negotiator with id id of the connection or null.
|
/// Returns the negotiator with id id of the connection or null.
|
||||||
@ -69,17 +60,6 @@ class NegotiatorAttributes {
|
|||||||
|
|
||||||
/// Returns true if the stream is authenticated. Returns false if not.
|
/// Returns true if the stream is authenticated. Returns false if not.
|
||||||
final bool Function() isAuthenticated;
|
final bool Function() isAuthenticated;
|
||||||
|
|
||||||
/// Sets the resource of the connection. If triggerEvent is true, then a
|
|
||||||
/// [ResourceBoundEvent] is triggered.
|
|
||||||
final void Function(String, {bool triggerEvent}) setResource;
|
|
||||||
|
|
||||||
/// Sets the authentication state of the connection to true.
|
|
||||||
final void Function() setAuthenticated;
|
|
||||||
|
|
||||||
/// Remove a stream feature from our internal cache. This is useful for when you
|
|
||||||
/// negotiated a feature for another negotiator, like SASL2.
|
|
||||||
final void Function(String) removeNegotiatingFeature;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class XmppFeatureNegotiatorBase {
|
abstract class XmppFeatureNegotiatorBase {
|
||||||
@ -124,9 +104,6 @@ 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.
|
||||||
@ -143,10 +120,5 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
state = NegotiatorState.ready;
|
state = NegotiatorState.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
NegotiatorAttributes get attributes => _attributes;
|
NegotiatorAttributes get attributes => _attributes;
|
||||||
|
|
||||||
/// Run after all negotiators are registered. Useful for registering callbacks against
|
|
||||||
/// other negotiators. By default this function does nothing.
|
|
||||||
Future<void> postRegisterCallback() async {}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxxmpp/src/jid.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';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
@ -30,13 +30,10 @@ class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
if (sm != null) {
|
if (sm != null) {
|
||||||
return super.matchesFeature(features) &&
|
return super.matchesFeature(features) &&
|
||||||
!sm.streamResumed &&
|
!sm.streamResumed &&
|
||||||
attributes.isAuthenticated() &&
|
attributes.isAuthenticated();
|
||||||
attributes.getConnection().resource.isEmpty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.matchesFeature(features) &&
|
return super.matchesFeature(features) && attributes.isAuthenticated();
|
||||||
attributes.isAuthenticated() &&
|
|
||||||
attributes.getConnection().resource.isEmpty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -68,9 +65,11 @@ class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final bind = nonza.firstTag('bind')!;
|
final bind = nonza.firstTag('bind')!;
|
||||||
final rawJid = bind.firstTag('jid')!.innerText();
|
final jid = bind.firstTag('jid')!;
|
||||||
final resource = JID.fromString(rawJid).resource;
|
final resource = jid.innerText().split('/')[1];
|
||||||
attributes.setResource(resource);
|
|
||||||
|
await attributes
|
||||||
|
.sendEvent(ResourceBindingSuccessEvent(resource: resource));
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,23 +3,21 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.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/rfcs/rfc_6120/sasl/errors.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl/nonza.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';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
|
||||||
import 'package:saslprep/saslprep.dart';
|
|
||||||
|
|
||||||
class SaslPlainAuthNonza extends SaslAuthNonza {
|
class SaslPlainAuthNonza extends SaslAuthNonza {
|
||||||
SaslPlainAuthNonza(String data)
|
SaslPlainAuthNonza(String username, String password)
|
||||||
: super(
|
: super(
|
||||||
'PLAIN',
|
'PLAIN',
|
||||||
data,
|
base64.encode(utf8.encode('\u0000$username\u0000$password')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
class SaslPlainNegotiator extends SaslNegotiator {
|
||||||
SaslPlainNegotiator()
|
SaslPlainNegotiator()
|
||||||
: _authSent = false,
|
: _authSent = false,
|
||||||
_log = Logger('SaslPlainNegotiator'),
|
_log = Logger('SaslPlainNegotiator'),
|
||||||
@ -49,16 +47,17 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
XMLNode nonza,
|
XMLNode nonza,
|
||||||
) async {
|
) async {
|
||||||
if (!_authSent) {
|
if (!_authSent) {
|
||||||
final data = await getRawStep('');
|
final settings = attributes.getConnectionSettings();
|
||||||
attributes.sendNonza(
|
attributes.sendNonza(
|
||||||
SaslPlainAuthNonza(data),
|
SaslPlainAuthNonza(settings.jid.local, settings.password),
|
||||||
|
redact: SaslPlainAuthNonza('******', '******').toXml(),
|
||||||
);
|
);
|
||||||
_authSent = true;
|
_authSent = true;
|
||||||
return const Result(NegotiatorState.ready);
|
return const Result(NegotiatorState.ready);
|
||||||
} else {
|
} else {
|
||||||
final tag = nonza.tag;
|
final tag = nonza.tag;
|
||||||
if (tag == 'success') {
|
if (tag == 'success') {
|
||||||
attributes.setAuthenticated();
|
await attributes.sendEvent(AuthenticationSuccessEvent());
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
} else {
|
} else {
|
||||||
// We assume it's a <failure/>
|
// We assume it's a <failure/>
|
||||||
@ -77,34 +76,4 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
|
||||||
?.registerSaslNegotiator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> getRawStep(String input) async {
|
|
||||||
final settings = attributes.getConnectionSettings();
|
|
||||||
final prep = Saslprep.saslprep(settings.password);
|
|
||||||
return base64.encode(
|
|
||||||
utf8.encode('\u0000${settings.jid.local}\u0000$prep'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onSasl2Failure(XMLNode response) async {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -6,28 +6,15 @@ import 'package:moxxmpp/src/events.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/rfcs/rfc_6120/sasl/errors.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/kv.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl/nonza.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';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
import 'package:saslprep/saslprep.dart';
|
import 'package:saslprep/saslprep.dart';
|
||||||
|
|
||||||
abstract class SaslScramError extends NegotiatorError {}
|
|
||||||
|
|
||||||
class NoAdditionalDataError extends SaslScramError {
|
|
||||||
@override
|
|
||||||
bool isRecoverable() => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InvalidServerSignatureError extends SaslScramError {
|
|
||||||
@override
|
|
||||||
bool isRecoverable() => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Inspired by https://github.com/vukoye/xmpp_dart/blob/3b1a0588562b9e591488c99d834088391840911d/lib/src/features/sasl/ScramSaslHandler.dart
|
// NOTE: Inspired by https://github.com/vukoye/xmpp_dart/blob/3b1a0588562b9e591488c99d834088391840911d/lib/src/features/sasl/ScramSaslHandler.dart
|
||||||
|
|
||||||
enum ScramHashType { sha1, sha256, sha512 }
|
enum ScramHashType { sha1, sha256, sha512 }
|
||||||
@ -108,7 +95,7 @@ enum ScramState { preSent, initialMessageSent, challengeResponseSent, error }
|
|||||||
|
|
||||||
const gs2Header = 'n,,';
|
const gs2Header = 'n,,';
|
||||||
|
|
||||||
class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
class SaslScramNegotiator extends SaslNegotiator {
|
||||||
// NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing
|
// NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing
|
||||||
SaslScramNegotiator(
|
SaslScramNegotiator(
|
||||||
int priority,
|
int priority,
|
||||||
@ -243,23 +230,29 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _checkSignature(String base64Signature) {
|
|
||||||
final signature =
|
|
||||||
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
|
||||||
return signature['v']! == _serverSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
XMLNode nonza,
|
XMLNode nonza,
|
||||||
) async {
|
) async {
|
||||||
switch (_scramState) {
|
switch (_scramState) {
|
||||||
case ScramState.preSent:
|
case ScramState.preSent:
|
||||||
|
if (clientNonce == null || clientNonce == '') {
|
||||||
|
clientNonce = randomAlphaNumeric(
|
||||||
|
40,
|
||||||
|
provider: CoreRandomProvider.from(Random.secure()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialMessageNoGS2 =
|
||||||
|
'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce';
|
||||||
|
|
||||||
|
_scramState = ScramState.initialMessageSent;
|
||||||
attributes.sendNonza(
|
attributes.sendNonza(
|
||||||
SaslScramAuthNonza(
|
SaslScramAuthNonza(
|
||||||
body: await getRawStep(''),
|
body: base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)),
|
||||||
type: hashType,
|
type: hashType,
|
||||||
),
|
),
|
||||||
|
redact: SaslScramAuthNonza(body: '******', type: hashType).toXml(),
|
||||||
);
|
);
|
||||||
return const Result(NegotiatorState.ready);
|
return const Result(NegotiatorState.ready);
|
||||||
case ScramState.initialMessageSent:
|
case ScramState.initialMessageSent:
|
||||||
@ -273,8 +266,13 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final challengeBase64 = nonza.innerText();
|
||||||
|
final response = await calculateChallengeResponse(challengeBase64);
|
||||||
|
final responseBase64 = base64.encode(utf8.encode(response));
|
||||||
|
_scramState = ScramState.challengeResponseSent;
|
||||||
attributes.sendNonza(
|
attributes.sendNonza(
|
||||||
SaslScramResponseNonza(body: await getRawStep(nonza.innerText())),
|
SaslScramResponseNonza(body: responseBase64),
|
||||||
|
redact: SaslScramResponseNonza(body: '******').toXml(),
|
||||||
);
|
);
|
||||||
return const Result(NegotiatorState.ready);
|
return const Result(NegotiatorState.ready);
|
||||||
case ScramState.challengeResponseSent:
|
case ScramState.challengeResponseSent:
|
||||||
@ -288,7 +286,10 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_checkSignature(nonza.innerText())) {
|
// NOTE: This assumes that the string is always "v=..." and contains no other parameters
|
||||||
|
final signature =
|
||||||
|
parseKeyValue(utf8.decode(base64.decode(nonza.innerText())));
|
||||||
|
if (signature['v']! != _serverSignature) {
|
||||||
// TODO(Unknown): Notify of a signature mismatch
|
// TODO(Unknown): Notify of a signature mismatch
|
||||||
//final error = nonza.children.first.tag;
|
//final error = nonza.children.first.tag;
|
||||||
//attributes.sendEvent(AuthenticationFailedEvent(error));
|
//attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||||
@ -298,7 +299,7 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.setAuthenticated();
|
await attributes.sendEvent(AuthenticationSuccessEvent());
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
case ScramState.error:
|
case ScramState.error:
|
||||||
return Result(
|
return Result(
|
||||||
@ -313,65 +314,4 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> getRawStep(String input) async {
|
|
||||||
switch (_scramState) {
|
|
||||||
case ScramState.preSent:
|
|
||||||
if (clientNonce == null || clientNonce == '') {
|
|
||||||
clientNonce = randomAlphaNumeric(
|
|
||||||
40,
|
|
||||||
provider: CoreRandomProvider.from(Random.secure()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialMessageNoGS2 =
|
|
||||||
'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce';
|
|
||||||
|
|
||||||
_scramState = ScramState.initialMessageSent;
|
|
||||||
return base64.encode(utf8.encode(gs2Header + initialMessageNoGS2));
|
|
||||||
case ScramState.initialMessageSent:
|
|
||||||
final challengeBase64 = input;
|
|
||||||
final response = await calculateChallengeResponse(challengeBase64);
|
|
||||||
final responseBase64 = base64.encode(utf8.encode(response));
|
|
||||||
_scramState = ScramState.challengeResponseSent;
|
|
||||||
|
|
||||||
return responseBase64;
|
|
||||||
case ScramState.challengeResponseSent:
|
|
||||||
case ScramState.error:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
|
||||||
?.registerSaslNegotiator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
|
||||||
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
|
|
||||||
// signature.
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
final additionalData = response.firstTag('additional-data');
|
|
||||||
if (additionalData == null) {
|
|
||||||
return Result(NoAdditionalDataError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_checkSignature(additionalData.innerText())) {
|
|
||||||
return Result(InvalidServerSignatureError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
@ -7,10 +6,8 @@ import 'package:moxxmpp/src/managers/data.dart';
|
|||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.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';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart';
|
|
||||||
|
|
||||||
/// A function that will be called when presence, outside of subscription request
|
/// A function that will be called when presence, outside of subscription request
|
||||||
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
@ -45,20 +42,6 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
_presenceCallbacks.add(callback);
|
_presenceCallbacks.add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {
|
|
||||||
if (event is StreamNegotiationsDoneEvent) {
|
|
||||||
// Send initial presence only when we have not resumed the stream
|
|
||||||
final sm = getAttributes().getNegotiatorById<StreamManagementNegotiator>(
|
|
||||||
streamManagementNegotiator,
|
|
||||||
);
|
|
||||||
final isResumed = sm?.isResumed ?? false;
|
|
||||||
if (!isResumed) {
|
|
||||||
unawaited(sendInitialPresence());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onPresence(
|
Future<StanzaHandlerData> _onPresence(
|
||||||
Stanza presence,
|
Stanza presence,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
|
@ -5,13 +5,8 @@ class ConnectionSettings {
|
|||||||
required this.jid,
|
required this.jid,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.useDirectTLS,
|
required this.useDirectTLS,
|
||||||
this.host,
|
|
||||||
this.port,
|
|
||||||
});
|
});
|
||||||
final JID jid;
|
final JID jid;
|
||||||
final String password;
|
final String password;
|
||||||
final bool useDirectTLS;
|
final bool useDirectTLS;
|
||||||
|
|
||||||
final String? host;
|
|
||||||
final int? port;
|
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,9 @@ abstract class BaseSocketWrapper {
|
|||||||
/// reused by calling [this.connect] again.
|
/// reused by calling [this.connect] again.
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
/// Write [data] into the socket.
|
/// Write [data] into the socket. If [redact] is not null, then [redact] will be
|
||||||
void write(String data);
|
/// logged instead of [data].
|
||||||
|
void write(String data, {String? redact});
|
||||||
|
|
||||||
/// This must connect to [host]:[port] and initialize the streams accordingly.
|
/// This must connect to [host]:[port] and initialize the streams accordingly.
|
||||||
/// [domain] is the domain that TLS should be validated against, in case the Socket
|
/// [domain] is the domain that TLS should be validated against, in case the Socket
|
||||||
|
@ -146,6 +146,4 @@ class XMLNode {
|
|||||||
String innerText() {
|
String innerText() {
|
||||||
return text ?? '';
|
return text ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get xmlns => attributes['xmlns'] as String?;
|
|
||||||
}
|
}
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
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/stringxml.dart';
|
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.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
|
|
||||||
bool shouldRetrySasl() => true;
|
|
||||||
|
|
||||||
@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,6 +1,4 @@
|
|||||||
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';
|
||||||
@ -12,9 +10,6 @@ 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';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
|
||||||
|
|
||||||
enum _StreamManagementNegotiatorState {
|
enum _StreamManagementNegotiatorState {
|
||||||
// We have not done anything yet
|
// We have not done anything yet
|
||||||
@ -28,71 +23,26 @@ 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 XmppFeatureNegotiatorBase {
|
||||||
implements Bind2FeatureNegotiatorInterface {
|
|
||||||
StreamManagementNegotiator()
|
StreamManagementNegotiator()
|
||||||
: super(10, false, smXmlns, streamManagementNegotiator);
|
: _state = _StreamManagementNegotiatorState.ready,
|
||||||
|
_supported = false,
|
||||||
|
_resumeFailed = false,
|
||||||
|
_isResumed = false,
|
||||||
|
_log = Logger('StreamManagementNegotiator'),
|
||||||
|
super(10, false, smXmlns, streamManagementNegotiator);
|
||||||
|
_StreamManagementNegotiatorState _state;
|
||||||
|
bool _resumeFailed;
|
||||||
|
bool _isResumed;
|
||||||
|
|
||||||
/// Stream Management negotiation state.
|
final Logger _log;
|
||||||
_StreamManagementNegotiatorState _state =
|
|
||||||
_StreamManagementNegotiatorState.ready;
|
|
||||||
|
|
||||||
/// Flag indicating whether the resume failed (true) or succeeded (false).
|
|
||||||
bool _resumeFailed = false;
|
|
||||||
bool get resumeFailed => _resumeFailed;
|
|
||||||
|
|
||||||
/// Flag indicating whether the current stream is resumed (true) or not (false).
|
|
||||||
bool _isResumed = false;
|
|
||||||
bool get isResumed => _isResumed;
|
|
||||||
|
|
||||||
/// Flag indicating that stream enablement failed
|
|
||||||
bool _streamEnablementFailed = false;
|
|
||||||
bool get streamEnablementFailed => _streamEnablementFailed;
|
|
||||||
|
|
||||||
/// Logger
|
|
||||||
final Logger _log = Logger('StreamManagementNegotiator');
|
|
||||||
|
|
||||||
/// True if Stream Management is supported on this stream.
|
/// True if Stream Management is supported on this stream.
|
||||||
bool _supported = false;
|
bool _supported;
|
||||||
bool get isSupported => _supported;
|
bool get isSupported => _supported;
|
||||||
|
|
||||||
/// True if we requested stream enablement inline
|
/// True if the current stream is resumed. False if not.
|
||||||
bool _inlineStreamEnablementRequested = false;
|
bool get isResumed => _isResumed;
|
||||||
|
|
||||||
/// 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)!;
|
|
||||||
|
|
||||||
// We do not check here for authentication as enabling/resuming happens inline
|
|
||||||
// with the authentication.
|
|
||||||
if (sm.state.streamResumptionId != null && !_resumeFailed) {
|
|
||||||
// We can try to resume the stream or enable the stream
|
|
||||||
return features.firstWhereOrNull(
|
|
||||||
(child) => child.xmlns == smXmlns,
|
|
||||||
) !=
|
|
||||||
null;
|
|
||||||
} else {
|
|
||||||
// We can try to enable SM
|
|
||||||
return features.firstWhereOrNull(
|
|
||||||
(child) => child.tag == 'enable' && child.xmlns == smXmlns,
|
|
||||||
) !=
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
||||||
@ -103,66 +53,13 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
return super.matchesFeature(features) && attributes.isAuthenticated();
|
return super.matchesFeature(features) && attributes.isAuthenticated();
|
||||||
} else {
|
} else {
|
||||||
// We cannot do a stream resumption
|
// We cannot do a stream resumption
|
||||||
|
final br = attributes.getNegotiatorById(resourceBindingNegotiator);
|
||||||
return super.matchesFeature(features) &&
|
return super.matchesFeature(features) &&
|
||||||
attributes.getConnection().resource.isNotEmpty &&
|
br?.state == NegotiatorState.done &&
|
||||||
attributes.isAuthenticated();
|
attributes.isAuthenticated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onStreamResumptionFailed() async {
|
|
||||||
await attributes.sendEvent(StreamResumeFailedEvent());
|
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
|
||||||
|
|
||||||
// We have to do this because we otherwise get a stanza stuck in the queue,
|
|
||||||
// thus spamming the server on every <a /> nonza we receive.
|
|
||||||
// ignore: cascade_invocations
|
|
||||||
await sm.setState(StreamManagementState(0, 0));
|
|
||||||
await sm.commitState();
|
|
||||||
|
|
||||||
_resumeFailed = true;
|
|
||||||
_isResumed = false;
|
|
||||||
_state = _StreamManagementNegotiatorState.ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onStreamResumptionSuccessful(XMLNode resumed) async {
|
|
||||||
assert(resumed.tag == 'resumed', 'The correct element must be passed');
|
|
||||||
|
|
||||||
final h = int.parse(resumed.attributes['h']! as String);
|
|
||||||
await attributes.sendEvent(StreamResumedEvent(h: h));
|
|
||||||
|
|
||||||
_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 {
|
|
||||||
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,
|
||||||
@ -206,26 +103,54 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
csi.restoreCSIState();
|
csi.restoreCSIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _onStreamResumptionSuccessful(nonza);
|
final h = int.parse(nonza.attributes['h']! as String);
|
||||||
|
await attributes.sendEvent(StreamResumedEvent(h: h));
|
||||||
|
|
||||||
|
_resumeFailed = false;
|
||||||
|
_isResumed = true;
|
||||||
return const Result(NegotiatorState.skipRest);
|
return const Result(NegotiatorState.skipRest);
|
||||||
} else {
|
} else {
|
||||||
// We assume it is <failed />
|
// We assume it is <failed />
|
||||||
_log.info(
|
_log.info(
|
||||||
'Stream resumption failed. Expected <resumed />, got ${nonza.tag}, Proceeding with new stream...',
|
'Stream resumption failed. Expected <resumed />, got ${nonza.tag}, Proceeding with new stream...',
|
||||||
);
|
);
|
||||||
await _onStreamResumptionFailed();
|
await attributes.sendEvent(StreamResumeFailedEvent());
|
||||||
|
final sm =
|
||||||
|
attributes.getManagerById<StreamManagementManager>(smManager)!;
|
||||||
|
|
||||||
|
// We have to do this because we otherwise get a stanza stuck in the queue,
|
||||||
|
// thus spamming the server on every <a /> nonza we receive.
|
||||||
|
// ignore: cascade_invocations
|
||||||
|
await sm.setState(StreamManagementState(0, 0));
|
||||||
|
await sm.commitState();
|
||||||
|
|
||||||
|
_resumeFailed = true;
|
||||||
|
_isResumed = false;
|
||||||
|
_state = _StreamManagementNegotiatorState.ready;
|
||||||
return const Result(NegotiatorState.retryLater);
|
return const Result(NegotiatorState.retryLater);
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,97 +162,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
_supported = false;
|
_supported = false;
|
||||||
_resumeFailed = false;
|
_resumeFailed = false;
|
||||||
_isResumed = false;
|
_isResumed = false;
|
||||||
_inlineStreamEnablementRequested = false;
|
|
||||||
_streamEnablementFailed = false;
|
|
||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onBind2FeaturesReceived(
|
|
||||||
List<String> bind2Features,
|
|
||||||
) async {
|
|
||||||
if (!bind2Features.contains(smXmlns)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
_inlineStreamEnablementRequested = true;
|
|
||||||
return [
|
|
||||||
StreamManagementEnableNonza(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onBind2Success(XMLNode response) async {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
|
||||||
final inline = sasl2Features.firstTag('inline')!;
|
|
||||||
final resume = inline.firstTag('resume', xmlns: smXmlns);
|
|
||||||
|
|
||||||
if (resume == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager)!;
|
|
||||||
final srid = sm.state.streamResumptionId;
|
|
||||||
final h = sm.state.s2c;
|
|
||||||
if (srid == null) {
|
|
||||||
_log.finest('No srid');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
StreamManagementResumeNonza(
|
|
||||||
srid,
|
|
||||||
h,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
|
||||||
final enabled = response
|
|
||||||
.firstTag('bound', xmlns: bind2Xmlns)
|
|
||||||
?.firstTag('enabled', 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) {
|
|
||||||
_log.warning('Inline stream resumption failed');
|
|
||||||
await _onStreamResumptionFailed();
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.finest('Inline stream resumption successful');
|
|
||||||
await _onStreamResumptionSuccessful(resumed);
|
|
||||||
state = NegotiatorState.skipRest;
|
|
||||||
|
|
||||||
attributes.removeNegotiatingFeature(smXmlns);
|
|
||||||
attributes.removeNegotiatingFeature(bindXmlns);
|
|
||||||
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
|
||||||
?.registerNegotiator(this);
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Bind2Negotiator>(bind2Negotiator)
|
|
||||||
?.registerNegotiator(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
@ -8,12 +7,10 @@ import 'package:moxxmpp/src/managers/data.dart';
|
|||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.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';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0297.dart';
|
import 'package:moxxmpp/src/xeps/xep_0297.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0386.dart';
|
|
||||||
|
|
||||||
/// This manager class implements support for XEP-0280.
|
/// This manager class implements support for XEP-0280.
|
||||||
class CarbonsManager extends XmppManagerBase {
|
class CarbonsManager extends XmppManagerBase {
|
||||||
@ -176,16 +173,6 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
_isEnabled = true;
|
_isEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@internal
|
|
||||||
void setEnabled() {
|
|
||||||
_isEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@internal
|
|
||||||
void setDisabled() {
|
|
||||||
_isEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a carbon sent by [senderJid] is valid to prevent vulnerabilities like
|
/// Checks if a carbon sent by [senderJid] is valid to prevent vulnerabilities like
|
||||||
/// the ones listed at https://xmpp.org/extensions/xep-0280.html#security.
|
/// the ones listed at https://xmpp.org/extensions/xep-0280.html#security.
|
||||||
///
|
///
|
||||||
@ -198,55 +185,3 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CarbonsNegotiator extends Bind2FeatureNegotiator {
|
|
||||||
CarbonsNegotiator() : super(0, carbonsXmlns, carbonsNegotiator);
|
|
||||||
|
|
||||||
/// Flag indicating whether we requested to enable carbons inline (true) or not
|
|
||||||
/// (false).
|
|
||||||
bool _requestedEnablement = false;
|
|
||||||
|
|
||||||
/// Logger
|
|
||||||
final Logger _log = Logger('CarbonsNegotiator');
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onBind2Success(XMLNode response) async {
|
|
||||||
if (!_requestedEnablement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final enabled = response.firstTag('enabled', xmlns: carbonsXmlns);
|
|
||||||
final cm = attributes.getManagerById<CarbonsManager>(carbonsManager)!;
|
|
||||||
if (enabled != null) {
|
|
||||||
_log.finest('Successfully enabled Message Carbons inline');
|
|
||||||
cm.setEnabled();
|
|
||||||
} else {
|
|
||||||
_log.warning('Failed to enable Message Carbons inline');
|
|
||||||
cm.setDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onBind2FeaturesReceived(
|
|
||||||
List<String> bind2Features,
|
|
||||||
) async {
|
|
||||||
if (!bind2Features.contains(carbonsXmlns)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestedEnablement = true;
|
|
||||||
return [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'enable',
|
|
||||||
xmlns: carbonsXmlns,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_requestedEnablement = false;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,6 @@ 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/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0386.dart';
|
|
||||||
|
|
||||||
class CSIActiveNonza extends XMLNode {
|
class CSIActiveNonza extends XMLNode {
|
||||||
CSIActiveNonza()
|
CSIActiveNonza()
|
||||||
@ -24,8 +23,7 @@ class CSIInactiveNonza extends XMLNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A Stub negotiator that is just for "intercepting" the stream feature.
|
/// A Stub negotiator that is just for "intercepting" the stream feature.
|
||||||
class CSINegotiator extends XmppFeatureNegotiatorBase
|
class CSINegotiator extends XmppFeatureNegotiatorBase {
|
||||||
implements Bind2FeatureNegotiatorInterface {
|
|
||||||
CSINegotiator() : super(11, false, csiXmlns, csiNegotiator);
|
CSINegotiator() : super(11, false, csiXmlns, csiNegotiator);
|
||||||
|
|
||||||
/// True if CSI is supported. False otherwise.
|
/// True if CSI is supported. False otherwise.
|
||||||
@ -42,47 +40,19 @@ class CSINegotiator extends XmppFeatureNegotiatorBase
|
|||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onBind2FeaturesReceived(
|
|
||||||
List<String> bind2Features,
|
|
||||||
) async {
|
|
||||||
if (!bind2Features.contains(csiXmlns)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
_supported = true;
|
|
||||||
final active = attributes.getManagerById<CSIManager>(csiManager)!.isActive;
|
|
||||||
return [
|
|
||||||
if (active) CSIActiveNonza() else CSIInactiveNonza(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onBind2Success(XMLNode response) async {}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void reset() {
|
void reset() {
|
||||||
_supported = false;
|
_supported = false;
|
||||||
|
|
||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Bind2Negotiator>(bind2Negotiator)
|
|
||||||
?.registerNegotiator(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The manager requires a CSINegotiator to be registered as a feature negotiator.
|
/// The manager requires a CSINegotiator to be registered as a feature negotiator.
|
||||||
class CSIManager extends XmppManagerBase {
|
class CSIManager extends XmppManagerBase {
|
||||||
CSIManager() : super(csiManager);
|
CSIManager() : super(csiManager);
|
||||||
|
|
||||||
/// Flag indicating whether the application is currently active and the CSI
|
|
||||||
/// traffic optimisation should be disabled (true).
|
|
||||||
bool _isActive = true;
|
bool _isActive = true;
|
||||||
bool get isActive => _isActive;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async {
|
Future<bool> isSupported() async {
|
||||||
@ -101,31 +71,23 @@ class CSIManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tells the server to stop optimizing traffic.
|
/// Tells the server to top optimizing traffic
|
||||||
/// If [sendNonza] is false, then no nonza is sent. This is useful
|
Future<void> setActive() async {
|
||||||
/// for setting up the CSI manager for Bind2.
|
|
||||||
Future<void> setActive({bool sendNonza = true}) async {
|
|
||||||
_isActive = true;
|
_isActive = true;
|
||||||
|
|
||||||
if (sendNonza) {
|
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
if (await isSupported()) {
|
if (await isSupported()) {
|
||||||
attrs.sendNonza(CSIActiveNonza());
|
attrs.sendNonza(CSIActiveNonza());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Tells the server to optimize traffic following XEP-0352
|
/// Tells the server to optimize traffic following XEP-0352
|
||||||
/// If [sendNonza] is false, then no nonza is sent. This is useful
|
Future<void> setInactive() async {
|
||||||
/// for setting up the CSI manager for Bind2.
|
|
||||||
Future<void> setInactive({bool sendNonza = true}) async {
|
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
|
|
||||||
if (sendNonza) {
|
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
if (await isSupported()) {
|
if (await isSupported()) {
|
||||||
attrs.sendNonza(CSIInactiveNonza());
|
attrs.sendNonza(CSIInactiveNonza());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:meta/meta.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/stringxml.dart';
|
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.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 Bind2FeatureNegotiatorInterface {
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
/// Called by the Bind2 negotiator when Bind2 results are received.
|
|
||||||
Future<void> onBind2Success(XMLNode result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A class that allows for simple negotiators that only registers itself against
|
|
||||||
/// the Bind2 negotiator. You only have to implement the functions required by
|
|
||||||
/// [Bind2FeatureNegotiatorInterface].
|
|
||||||
abstract class Bind2FeatureNegotiator extends XmppFeatureNegotiatorBase
|
|
||||||
implements Bind2FeatureNegotiatorInterface {
|
|
||||||
Bind2FeatureNegotiator(
|
|
||||||
int priority,
|
|
||||||
String negotiatingXmlns,
|
|
||||||
String id,
|
|
||||||
) : super(priority, false, negotiatingXmlns, id);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFeature(List<XMLNode> features) => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
|
||||||
XMLNode nonza,
|
|
||||||
) async {
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mustCallSuper
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Bind2Negotiator>(bind2Negotiator)!
|
|
||||||
.registerNegotiator(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A negotiator implementing XEP-0386. This negotiator is useless on its own
|
|
||||||
/// and requires a [Sasl2Negotiator] to be registered.
|
|
||||||
class Bind2Negotiator extends Sasl2FeatureNegotiator {
|
|
||||||
Bind2Negotiator() : super(0, false, bind2Xmlns, bind2Negotiator);
|
|
||||||
|
|
||||||
/// A list of negotiators that can work with Bind2.
|
|
||||||
final List<Bind2FeatureNegotiatorInterface> _negotiators =
|
|
||||||
List<Bind2FeatureNegotiatorInterface>.empty(growable: true);
|
|
||||||
|
|
||||||
/// A tag to sent to the server when requesting Bind2.
|
|
||||||
String? tag;
|
|
||||||
|
|
||||||
/// Register [negotiator] against the Bind2 negotiator to append data to the Bind2
|
|
||||||
/// negotiation.
|
|
||||||
void registerNegotiator(Bind2FeatureNegotiatorInterface negotiator) {
|
|
||||||
_negotiators.add(negotiator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
|
||||||
XMLNode nonza,
|
|
||||||
) async {
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
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 [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'bind',
|
|
||||||
xmlns: bind2Xmlns,
|
|
||||||
children: [
|
|
||||||
if (tag != null)
|
|
||||||
XMLNode(
|
|
||||||
tag: 'tag',
|
|
||||||
text: tag,
|
|
||||||
),
|
|
||||||
...children,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
|
||||||
return features.firstWhereOrNull(
|
|
||||||
(child) => child.tag == 'bind' && child.xmlns == bind2Xmlns,
|
|
||||||
) !=
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
|
||||||
final bound = response.firstTag('bound', xmlns: bind2Xmlns);
|
|
||||||
if (bound != null) {
|
|
||||||
for (final negotiator in _negotiators) {
|
|
||||||
await negotiator.onBind2Success(bound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attributes.removeNegotiatingFeature(bindXmlns);
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!
|
|
||||||
.registerNegotiator(this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
|
||||||
|
|
||||||
/// Triggered by the SASL2 negotiator when no SASL mechanism was chosen during
|
|
||||||
/// negotiation.
|
|
||||||
class NoSASLMechanismSelectedError extends NegotiatorError {
|
|
||||||
@override
|
|
||||||
bool isRecoverable() => false;
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
|
|
||||||
/// A special type of [XmppFeatureNegotiatorBase] that is aware of SASL2.
|
|
||||||
abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
|
||||||
Sasl2FeatureNegotiator(
|
|
||||||
super.priority,
|
|
||||||
super.sendStreamHeaderWhenDone,
|
|
||||||
super.negotiatingXmlns,
|
|
||||||
super.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Called by the SASL2 negotiator when we received the SASL2 stream features
|
|
||||||
/// [sasl2Features]. The return value is a list of XML elements that should be
|
|
||||||
/// added to the SASL2 <authenticate /> nonza.
|
|
||||||
/// This method is only called when the <inline /> element contains an item with
|
|
||||||
/// xmlns equal to [negotiatingXmlns].
|
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features);
|
|
||||||
|
|
||||||
/// Called by the SASL2 negotiator when the SASL2 negotiations are done. [response]
|
|
||||||
/// is the entire response nonza.
|
|
||||||
/// This method is only called when the previous <inline /> element contains an
|
|
||||||
/// 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.
|
|
||||||
bool canInlineFeature(List<XMLNode> features);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A special type of [SaslNegotiator] that is aware of SASL2.
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
|
|
||||||
/// A data class describing the user agent. See https://dyn.eightysoft.de/final/xep-0388.html#initiation
|
|
||||||
class UserAgent {
|
|
||||||
const UserAgent({
|
|
||||||
this.id,
|
|
||||||
this.software,
|
|
||||||
this.device,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The identifier of the software/device combo connecting. SHOULD be a UUIDv4.
|
|
||||||
final String? id;
|
|
||||||
|
|
||||||
/// The software's name that's connecting at the moment.
|
|
||||||
final String? software;
|
|
||||||
|
|
||||||
/// The name of the device.
|
|
||||||
final String? device;
|
|
||||||
|
|
||||||
XMLNode toXml() {
|
|
||||||
assert(
|
|
||||||
id != null || software != null || device != null,
|
|
||||||
'A completely empty user agent makes no sense',
|
|
||||||
);
|
|
||||||
return XMLNode(
|
|
||||||
tag: 'user-agent',
|
|
||||||
attributes: id != null
|
|
||||||
? {
|
|
||||||
'id': id,
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
children: [
|
|
||||||
if (software != null)
|
|
||||||
XMLNode(
|
|
||||||
tag: 'software',
|
|
||||||
text: software,
|
|
||||||
),
|
|
||||||
if (device != null)
|
|
||||||
XMLNode(
|
|
||||||
tag: 'device',
|
|
||||||
text: device,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
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/rfcs/rfc_6120/sasl/errors.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
|
||||||
|
|
||||||
/// The state of the SASL2 negotiation
|
|
||||||
enum Sasl2State {
|
|
||||||
// No request has been sent yet.
|
|
||||||
idle,
|
|
||||||
// We have sent the <authenticate /> nonza.
|
|
||||||
authenticateSent,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A negotiator that implements XEP-0388 SASL2. Alone, it does nothing. Has to be
|
|
||||||
/// registered with other negotiators that register themselves against this one.
|
|
||||||
class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|
||||||
Sasl2Negotiator({
|
|
||||||
this.userAgent,
|
|
||||||
}) : super(100, false, sasl2Xmlns, sasl2Negotiator);
|
|
||||||
|
|
||||||
/// The user agent data that will be sent to the server when authenticating.
|
|
||||||
final UserAgent? userAgent;
|
|
||||||
|
|
||||||
/// List of callbacks that are registered against us. Will be called once we get
|
|
||||||
/// SASL2 features.
|
|
||||||
final List<Sasl2FeatureNegotiator> _featureNegotiators =
|
|
||||||
List<Sasl2FeatureNegotiator>.empty(growable: true);
|
|
||||||
|
|
||||||
/// List of SASL negotiators, sorted by their priority. The higher the priority, the
|
|
||||||
/// lower its index.
|
|
||||||
final List<Sasl2AuthenticationNegotiator> _saslNegotiators =
|
|
||||||
List<Sasl2AuthenticationNegotiator>.empty(growable: true);
|
|
||||||
|
|
||||||
/// The state the SASL2 negotiator is currently in.
|
|
||||||
Sasl2State _sasl2State = Sasl2State.idle;
|
|
||||||
|
|
||||||
/// The SASL negotiator that will negotiate authentication.
|
|
||||||
Sasl2AuthenticationNegotiator? _currentSaslNegotiator;
|
|
||||||
|
|
||||||
/// The SASL2 <authentication /> element we received with the stream features.
|
|
||||||
XMLNode? _sasl2Data;
|
|
||||||
final List<String> _activeSasl2Negotiators =
|
|
||||||
List<String>.empty(growable: true);
|
|
||||||
|
|
||||||
/// Register a SASL negotiator so that we can use that SASL implementation during
|
|
||||||
/// SASL2.
|
|
||||||
void registerSaslNegotiator(Sasl2AuthenticationNegotiator negotiator) {
|
|
||||||
_featureNegotiators.add(negotiator);
|
|
||||||
_saslNegotiators
|
|
||||||
..add(negotiator)
|
|
||||||
..sort((a, b) => b.priority.compareTo(a.priority));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a feature negotiator so that we can negotitate that feature inline with
|
|
||||||
/// the SASL authentication.
|
|
||||||
void registerNegotiator(Sasl2FeatureNegotiator negotiator) {
|
|
||||||
_featureNegotiators.add(negotiator);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
|
||||||
// Only do SASL2 when the socket is secure
|
|
||||||
return attributes.getSocket().isSecure() && super.matchesFeature(features);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
|
||||||
XMLNode nonza,
|
|
||||||
) async {
|
|
||||||
switch (_sasl2State) {
|
|
||||||
case Sasl2State.idle:
|
|
||||||
_sasl2Data = nonza.firstTag('authentication', xmlns: sasl2Xmlns);
|
|
||||||
final mechanisms = XMLNode.xmlns(
|
|
||||||
tag: 'mechanisms',
|
|
||||||
xmlns: saslXmlns,
|
|
||||||
children:
|
|
||||||
_sasl2Data!.children.where((c) => c.tag == 'mechanism').toList(),
|
|
||||||
);
|
|
||||||
for (final negotiator in _saslNegotiators) {
|
|
||||||
if (negotiator.matchesFeature([mechanisms])) {
|
|
||||||
_currentSaslNegotiator = negotiator;
|
|
||||||
_currentSaslNegotiator!.pickForSasl2();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We must have a SASL negotiator by now
|
|
||||||
if (_currentSaslNegotiator == null) {
|
|
||||||
return Result(NoSASLMechanismSelectedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect additional data by interested negotiators
|
|
||||||
final inline = _sasl2Data!.firstTag('inline');
|
|
||||||
final children = List<XMLNode>.empty(growable: true);
|
|
||||||
if (inline != null && inline.children.isNotEmpty) {
|
|
||||||
for (final negotiator in _featureNegotiators) {
|
|
||||||
if (negotiator.canInlineFeature(inline.children)) {
|
|
||||||
_activeSasl2Negotiators.add(negotiator.id);
|
|
||||||
children.addAll(
|
|
||||||
await negotiator.onSasl2FeaturesReceived(_sasl2Data!),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the authenticate nonza
|
|
||||||
final authenticate = XMLNode.xmlns(
|
|
||||||
tag: 'authenticate',
|
|
||||||
xmlns: sasl2Xmlns,
|
|
||||||
attributes: {
|
|
||||||
'mechanism': _currentSaslNegotiator!.mechanismName,
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode(
|
|
||||||
tag: 'initial-response',
|
|
||||||
text: await _currentSaslNegotiator!.getRawStep(''),
|
|
||||||
),
|
|
||||||
if (userAgent != null) userAgent!.toXml(),
|
|
||||||
...children,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
_sasl2State = Sasl2State.authenticateSent;
|
|
||||||
attributes.sendNonza(authenticate);
|
|
||||||
return const Result(NegotiatorState.ready);
|
|
||||||
case Sasl2State.authenticateSent:
|
|
||||||
if (nonza.tag == 'success') {
|
|
||||||
// Tell the dependent negotiators about the result
|
|
||||||
final negotiators = _featureNegotiators
|
|
||||||
.where(
|
|
||||||
(negotiator) => _activeSasl2Negotiators.contains(negotiator.id),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
..add(_currentSaslNegotiator!);
|
|
||||||
for (final negotiator in negotiators) {
|
|
||||||
final result = await negotiator.onSasl2Success(nonza);
|
|
||||||
if (!result.isType<bool>()) {
|
|
||||||
return Result(result.get<NegotiatorError>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're done
|
|
||||||
attributes.setAuthenticated();
|
|
||||||
attributes.removeNegotiatingFeature(saslXmlns);
|
|
||||||
|
|
||||||
// Check if we also received a resource with the SASL2 success
|
|
||||||
final jid = JID.fromString(
|
|
||||||
nonza.firstTag('authorization-identifier')!.innerText(),
|
|
||||||
);
|
|
||||||
if (!jid.isBare()) {
|
|
||||||
attributes.setResource(jid.resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
} else if (nonza.tag == 'challenge') {
|
|
||||||
// We still have to negotiate
|
|
||||||
final challenge = nonza.innerText();
|
|
||||||
final response = XMLNode.xmlns(
|
|
||||||
tag: 'response',
|
|
||||||
xmlns: sasl2Xmlns,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return const Result(NegotiatorState.ready);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_currentSaslNegotiator = null;
|
|
||||||
_sasl2State = Sasl2State.idle;
|
|
||||||
_sasl2Data = null;
|
|
||||||
_activeSasl2Negotiators.clear();
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,6 +56,7 @@ class TestingManagerHolder {
|
|||||||
sendNonza: (_) {},
|
sendNonza: (_) {},
|
||||||
sendEvent: (_) {},
|
sendEvent: (_) {},
|
||||||
getSocket: () => _socket,
|
getSocket: () => _socket,
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
getFullJID: () => jid,
|
getFullJID: () => jid,
|
||||||
getManagerById: _getManagerById,
|
getManagerById: _getManagerById,
|
||||||
|
@ -58,7 +58,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
|||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare().toString()}' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -77,7 +77,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare().toString()}' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
|
@ -46,7 +46,7 @@ void main() {
|
|||||||
final stubSocket = StubTCPSocket(
|
final stubSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='user@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/kv.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -46,11 +46,6 @@ void main() {
|
|||||||
)..register(
|
)..register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
(XMLNode _, {String? redact}) {},
|
(XMLNode _, {String? redact}) {},
|
||||||
() => XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
),
|
|
||||||
() => ConnectionSettings(
|
() => ConnectionSettings(
|
||||||
jid: JID.fromString('user@server'),
|
jid: JID.fromString('user@server'),
|
||||||
password: 'pencil',
|
password: 'pencil',
|
||||||
@ -62,9 +57,6 @@ void main() {
|
|||||||
() => JID.fromString('user@server'),
|
() => JID.fromString('user@server'),
|
||||||
() => fakeSocket,
|
() => fakeSocket,
|
||||||
() => false,
|
() => false,
|
||||||
() {},
|
|
||||||
(_, {bool triggerEvent = true}) {},
|
|
||||||
(_) {},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -147,11 +139,6 @@ void main() {
|
|||||||
)..register(
|
)..register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
(XMLNode n, {String? redact}) => lastMessage = n.innerText(),
|
(XMLNode n, {String? redact}) => lastMessage = n.innerText(),
|
||||||
() => XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
StubTCPSocket([]),
|
|
||||||
),
|
|
||||||
() => ConnectionSettings(
|
() => ConnectionSettings(
|
||||||
jid: JID.fromString('user@server'),
|
jid: JID.fromString('user@server'),
|
||||||
password: 'pencil',
|
password: 'pencil',
|
||||||
@ -163,9 +150,6 @@ void main() {
|
|||||||
() => JID.fromString('user@server'),
|
() => JID.fromString('user@server'),
|
||||||
() => fakeSocket,
|
() => fakeSocket,
|
||||||
() => false,
|
() => false,
|
||||||
() {},
|
|
||||||
(_, {bool triggerEvent = true}) {},
|
|
||||||
(_) {},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -203,11 +187,6 @@ void main() {
|
|||||||
)..register(
|
)..register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
(XMLNode _, {String? redact}) {},
|
(XMLNode _, {String? redact}) {},
|
||||||
() => XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
StubTCPSocket([]),
|
|
||||||
),
|
|
||||||
() => ConnectionSettings(
|
() => ConnectionSettings(
|
||||||
jid: JID.fromString('user@server'),
|
jid: JID.fromString('user@server'),
|
||||||
password: 'pencil',
|
password: 'pencil',
|
||||||
@ -219,9 +198,6 @@ void main() {
|
|||||||
() => JID.fromString('user@server'),
|
() => JID.fromString('user@server'),
|
||||||
() => fakeSocket,
|
() => fakeSocket,
|
||||||
() => false,
|
() => false,
|
||||||
() {},
|
|
||||||
(_, {bool triggerEvent = true}) {},
|
|
||||||
(_) {},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -249,11 +225,6 @@ void main() {
|
|||||||
)..register(
|
)..register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
(XMLNode _, {String? redact}) {},
|
(XMLNode _, {String? redact}) {},
|
||||||
() => XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
StubTCPSocket([]),
|
|
||||||
),
|
|
||||||
() => ConnectionSettings(
|
() => ConnectionSettings(
|
||||||
jid: JID.fromString('user@server'),
|
jid: JID.fromString('user@server'),
|
||||||
password: 'pencil',
|
password: 'pencil',
|
||||||
@ -265,9 +236,6 @@ void main() {
|
|||||||
() => JID.fromString('user@server'),
|
() => JID.fromString('user@server'),
|
||||||
() => fakeSocket,
|
() => fakeSocket,
|
||||||
() => false,
|
() => false,
|
||||||
() {},
|
|
||||||
(_, {bool triggerEvent = true}) {},
|
|
||||||
(_) {},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -298,11 +266,6 @@ void main() {
|
|||||||
)..register(
|
)..register(
|
||||||
NegotiatorAttributes(
|
NegotiatorAttributes(
|
||||||
(XMLNode _, {String? redact}) {},
|
(XMLNode _, {String? redact}) {},
|
||||||
() => XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
StubTCPSocket([]),
|
|
||||||
),
|
|
||||||
() => ConnectionSettings(
|
() => ConnectionSettings(
|
||||||
jid: JID.fromString('user@server'),
|
jid: JID.fromString('user@server'),
|
||||||
password: 'pencil',
|
password: 'pencil',
|
||||||
@ -314,9 +277,6 @@ void main() {
|
|||||||
() => JID.fromString('user@server'),
|
() => JID.fromString('user@server'),
|
||||||
() => fakeSocket,
|
() => fakeSocket,
|
||||||
() => false,
|
() => false,
|
||||||
() {},
|
|
||||||
(_, {bool triggerEvent = true}) {},
|
|
||||||
(_) {},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
StreamHeaderNonza(JID.fromString('user@uwu.server')).toXml(),
|
StreamHeaderNonza('uwu.server').toXml(),
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='uwu.server' from='user@uwu.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='uwu.server' xml:lang='en'>",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -12,7 +12,7 @@ void main() {
|
|||||||
final fakeSocket = StubTCPSocket(
|
final fakeSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -31,7 +31,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -84,7 +84,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
EntityCapabilitiesManager('http://moxxmpp.example'),
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
|
@ -169,11 +169,12 @@ void main() {
|
|||||||
MessageManager(),
|
MessageManager(),
|
||||||
RosterManager(TestingRosterStateManager(null, [])),
|
RosterManager(TestingRosterStateManager(null, [])),
|
||||||
]);
|
]);
|
||||||
await connection.registerFeatureNegotiators([
|
connection
|
||||||
|
..registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
]);
|
])
|
||||||
connection.setConnectionSettings(TestingManagerHolder.settings);
|
..setConnectionSettings(TestingManagerHolder.settings);
|
||||||
await connection.connect(
|
await connection.connect(
|
||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
);
|
);
|
||||||
|
@ -63,6 +63,7 @@ XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
|
|||||||
password: 'password',
|
password: 'password',
|
||||||
useDirectTLS: true,
|
useDirectTLS: true,
|
||||||
),
|
),
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('hallo@example.server/uwu'),
|
getFullJID: () => JID.fromString('hallo@example.server/uwu'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -227,7 +228,7 @@ void main() {
|
|||||||
() async {
|
() async {
|
||||||
final fakeSocket = StubTCPSocket([
|
final fakeSocket = StubTCPSocket([
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -246,7 +247,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -297,7 +298,7 @@ void main() {
|
|||||||
CarbonsManager()..forceEnable(),
|
CarbonsManager()..forceEnable(),
|
||||||
EntityCapabilitiesManager('http://moxxmpp.example'),
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator(),
|
||||||
@ -342,7 +343,7 @@ void main() {
|
|||||||
test('Test counting incoming stanzas that are awaited', () async {
|
test('Test counting incoming stanzas that are awaited', () async {
|
||||||
final fakeSocket = StubTCPSocket([
|
final fakeSocket = StubTCPSocket([
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -361,7 +362,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -390,10 +391,10 @@ void main() {
|
|||||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||||
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
|
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
// StringExpectation(
|
||||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
|
// "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
|
||||||
'<iq type="result" />',
|
// '<iq type="result" />',
|
||||||
),
|
// ),
|
||||||
StanzaExpectation(
|
StanzaExpectation(
|
||||||
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
|
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
|
||||||
"<iq from='user@example.com' type='result' id='a' />",
|
"<iq from='user@example.com' type='result' id='a' />",
|
||||||
@ -422,7 +423,7 @@ void main() {
|
|||||||
CarbonsManager()..forceEnable(),
|
CarbonsManager()..forceEnable(),
|
||||||
//EntityCapabilitiesManager('http://moxxmpp.example'),
|
//EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator(),
|
||||||
@ -431,7 +432,7 @@ void main() {
|
|||||||
await conn.connect(
|
await conn.connect(
|
||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
);
|
);
|
||||||
expect(fakeSocket.getState(), 6);
|
expect(fakeSocket.getState(), 5);
|
||||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||||
expect(
|
expect(
|
||||||
conn
|
conn
|
||||||
@ -449,7 +450,7 @@ void main() {
|
|||||||
addFrom: StanzaFromType.none,
|
addFrom: StanzaFromType.none,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(sm.state.s2c, 2);
|
expect(sm.state.s2c, /*2*/ 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -512,7 +513,7 @@ void main() {
|
|||||||
test('Test successful stream enablement', () async {
|
test('Test successful stream enablement', () async {
|
||||||
final fakeSocket = StubTCPSocket([
|
final fakeSocket = StubTCPSocket([
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -531,7 +532,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -579,7 +580,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator(),
|
||||||
@ -589,7 +590,7 @@ void main() {
|
|||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fakeSocket.getState(), 6);
|
expect(fakeSocket.getState(), 5);
|
||||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||||
expect(
|
expect(
|
||||||
conn
|
conn
|
||||||
@ -602,7 +603,7 @@ void main() {
|
|||||||
test('Test a failed stream resumption', () async {
|
test('Test a failed stream resumption', () async {
|
||||||
final fakeSocket = StubTCPSocket([
|
final fakeSocket = StubTCPSocket([
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -621,7 +622,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -673,7 +674,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator(),
|
||||||
@ -689,7 +690,7 @@ void main() {
|
|||||||
await conn.connect(
|
await conn.connect(
|
||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
);
|
);
|
||||||
expect(fakeSocket.getState(), 7);
|
expect(fakeSocket.getState(), 6);
|
||||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||||
expect(
|
expect(
|
||||||
conn
|
conn
|
||||||
@ -702,7 +703,7 @@ void main() {
|
|||||||
test('Test a successful stream resumption', () async {
|
test('Test a successful stream resumption', () async {
|
||||||
final fakeSocket = StubTCPSocket([
|
final fakeSocket = StubTCPSocket([
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -721,7 +722,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -764,7 +765,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
StreamManagementNegotiator(),
|
StreamManagementNegotiator(),
|
||||||
@ -788,298 +789,4 @@ void main() {
|
|||||||
expect(sm.streamResumed, true);
|
expect(sm.streamResumed, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test SASL2 inline stream resumption', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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" />
|
|
||||||
</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><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()..setResource('test-resource'),
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
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' from='polynomdivision@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</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()..setResource('test-resource'),
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
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' from='polynomdivision@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:bind:0'>
|
|
||||||
<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');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import '../helpers/logging.dart';
|
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
initLogger();
|
|
||||||
|
|
||||||
test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities",
|
test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities",
|
||||||
() async {
|
() async {
|
||||||
final attributes = XmppManagerAttributes(
|
final attributes = XmppManagerAttributes(
|
||||||
@ -30,6 +27,7 @@ void main() {
|
|||||||
password: 'password',
|
password: 'password',
|
||||||
useDirectTLS: true,
|
useDirectTLS: true,
|
||||||
),
|
),
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
|
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -55,94 +53,4 @@ void main() {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test enabling message carbons inline 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' from='polynomdivision@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" />
|
|
||||||
<feature var="urn:xmpp:carbons:2" />
|
|
||||||
</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:carbons:2' /></bind></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server/test-resource</authorization-identifier>
|
|
||||||
<bound xmlns='urn:xmpp:bind:0'>
|
|
||||||
<enabled xmlns='urn:xmpp:carbons:2' />
|
|
||||||
</bound>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
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([]),
|
|
||||||
CarbonsManager(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
CarbonsNegotiator(),
|
|
||||||
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(conn.resource, 'test-resource');
|
|
||||||
expect(
|
|
||||||
conn.getManagerById<CarbonsManager>(carbonsManager)!.isEnabled,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import '../helpers/logging.dart';
|
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
class MockedCSINegotiator extends CSINegotiator {
|
class MockedCSINegotiator extends CSINegotiator {
|
||||||
@ -29,8 +28,6 @@ T? getUnsupportedCSINegotiator<T extends XmppFeatureNegotiatorBase>(String id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
initLogger();
|
|
||||||
|
|
||||||
group('Test the XEP-0352 implementation', () {
|
group('Test the XEP-0352 implementation', () {
|
||||||
test('Test setting the CSI state when CSI is unsupported', () {
|
test('Test setting the CSI state when CSI is unsupported', () {
|
||||||
var nonzaSent = false;
|
var nonzaSent = false;
|
||||||
@ -58,6 +55,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
getManagerById: getManagerNullStub,
|
getManagerById: getManagerNullStub,
|
||||||
getNegotiatorById: getUnsupportedCSINegotiator,
|
getNegotiatorById: getUnsupportedCSINegotiator,
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -101,6 +99,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
getManagerById: getManagerNullStub,
|
getManagerById: getManagerNullStub,
|
||||||
getNegotiatorById: getSupportedCSINegotiator,
|
getNegotiatorById: getSupportedCSINegotiator,
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -114,99 +113,4 @@ void main() {
|
|||||||
..setInactive();
|
..setInactive();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test CSI 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' from='polynomdivision@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>
|
|
||||||
<bind xmlns="urn:xmpp:bind:0">
|
|
||||||
<inline>
|
|
||||||
<feature var="urn:xmpp:csi:0" />
|
|
||||||
</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'>
|
|
||||||
<inactive xmlns='urn:xmpp:csi:0' />
|
|
||||||
</bind>
|
|
||||||
</authenticate>''',
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server/test-resource</authorization-identifier>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
|
||||||
password: 'aaaa',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final csi = CSIManager();
|
|
||||||
await csi.setInactive(sendNonza: false);
|
|
||||||
await conn.registerManagers([
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
csi,
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
FASTSaslNegotiator(),
|
|
||||||
Bind2Negotiator(),
|
|
||||||
CSINegotiator(),
|
|
||||||
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(fakeSocket.getState(), 2);
|
|
||||||
expect(
|
|
||||||
conn.getNegotiatorById<CSINegotiator>(csiNegotiator)!.isSupported,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -1,153 +0,0 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import '../helpers/logging.dart';
|
|
||||||
import '../helpers/xmpp.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
initLogger();
|
|
||||||
|
|
||||||
test('Test simple Bind2 negotiation', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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>
|
|
||||||
<bind xmlns="urn:xmpp:bind:0" />
|
|
||||||
</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' /></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server/random.resource</authorization-identifier>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
|
||||||
password: 'aaaa',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
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(conn.resource, 'random.resource');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test simple Bind2 negotiation with a provided tag', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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>
|
|
||||||
<bind xmlns="urn:xmpp:bind:0" />
|
|
||||||
</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'><tag>moxxmpp</tag></bind></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server/moxxmpp.random.resource</authorization-identifier>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
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(),
|
|
||||||
Bind2Negotiator()..tag = 'moxxmpp',
|
|
||||||
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(conn.resource, 'moxxmpp.random.resource');
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,473 +0,0 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import '../helpers/logging.dart';
|
|
||||||
import '../helpers/xmpp.dart';
|
|
||||||
|
|
||||||
class ExampleNegotiator extends Sasl2FeatureNegotiator {
|
|
||||||
ExampleNegotiator()
|
|
||||||
: super(0, false, 'invalid:example:dont:use', 'testNegotiator');
|
|
||||||
|
|
||||||
String? value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
|
||||||
XMLNode nonza,
|
|
||||||
) async {
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool canInlineFeature(List<XMLNode> features) {
|
|
||||||
return features.firstWhereOrNull(
|
|
||||||
(child) => child.xmlns == 'invalid:example:dont:use',
|
|
||||||
) !=
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> postRegisterCallback() async {
|
|
||||||
attributes
|
|
||||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!
|
|
||||||
.registerNegotiator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode nonza) async {
|
|
||||||
return [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'test-data-request',
|
|
||||||
xmlns: 'invalid:example:dont:use',
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode nonza) async {
|
|
||||||
final child =
|
|
||||||
nonza.firstTag('test-data', xmlns: 'invalid:example:dont:use');
|
|
||||||
if (child == null) {
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
value = child.innerText();
|
|
||||||
return const Result(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
initLogger();
|
|
||||||
|
|
||||||
test('Test simple SASL2 negotiation', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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>
|
|
||||||
</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></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<iq xmlns='jabber:client' type='set' id='aaaa'><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>',
|
|
||||||
''',
|
|
||||||
adjustId: true,
|
|
||||||
ignoreId: true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
|
||||||
password: 'aaaa',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test SCRAM-SHA-1 SASL2 negotiation with a valid signature', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='server' from='user@server' xml:lang='en'>",
|
|
||||||
'''
|
|
||||||
<stream:stream
|
|
||||||
xmlns="jabber:client"
|
|
||||||
version="1.0"
|
|
||||||
xmlns:stream="http://etherx.jabber.org/streams"
|
|
||||||
from="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>SCRAM-SHA-256</mechanism>
|
|
||||||
</mechanisms>
|
|
||||||
<authentication xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<mechanism>PLAIN</mechanism>
|
|
||||||
<mechanism>SCRAM-SHA-256</mechanism>
|
|
||||||
</authentication>
|
|
||||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
|
||||||
<required/>
|
|
||||||
</bind>
|
|
||||||
</stream:features>''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='SCRAM-SHA-256'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8=</initial-response></authenticate>",
|
|
||||||
'''
|
|
||||||
<challenge xmlns='urn:xmpp:sasl:2'>cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY=</challenge>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
'<response xmlns="urn:xmpp:sasl:2">Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ==</response>',
|
|
||||||
'<success xmlns="urn:xmpp:sasl:2"><additional-data>dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ==</additional-data><authorization-identifier>user@server</authorization-identifier></success>',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<iq xmlns='jabber:client' type='set' id='aaaa'><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>',
|
|
||||||
''',
|
|
||||||
adjustId: true,
|
|
||||||
ignoreId: true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('user@server'),
|
|
||||||
password: 'pencil',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
SaslScramNegotiator(
|
|
||||||
10,
|
|
||||||
'n=user,r=rOprNGfwEbeRWgbNEkqO',
|
|
||||||
'rOprNGfwEbeRWgbNEkqO',
|
|
||||||
ScramHashType.sha256,
|
|
||||||
),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
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<XmppError>(), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test SCRAM-SHA-1 SASL2 negotiation with an invalid signature',
|
|
||||||
() async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='server' from='user@server' xml:lang='en'>",
|
|
||||||
'''
|
|
||||||
<stream:stream
|
|
||||||
xmlns="jabber:client"
|
|
||||||
version="1.0"
|
|
||||||
xmlns:stream="http://etherx.jabber.org/streams"
|
|
||||||
from="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>SCRAM-SHA-256</mechanism>
|
|
||||||
</mechanisms>
|
|
||||||
<authentication xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<mechanism>PLAIN</mechanism>
|
|
||||||
<mechanism>SCRAM-SHA-256</mechanism>
|
|
||||||
</authentication>
|
|
||||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
|
||||||
<required/>
|
|
||||||
</bind>
|
|
||||||
</stream:features>''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='SCRAM-SHA-256'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8=</initial-response></authenticate>",
|
|
||||||
'''
|
|
||||||
<challenge xmlns='urn:xmpp:sasl:2'>cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY=</challenge>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
'<response xmlns="urn:xmpp:sasl:2">Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ==</response>',
|
|
||||||
'<success xmlns="urn:xmpp:sasl:2"><additional-data>dj1zbUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</additional-data><authorization-identifier>user@server</authorization-identifier></success>',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('user@server'),
|
|
||||||
password: 'pencil',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
SaslScramNegotiator(
|
|
||||||
10,
|
|
||||||
'n=user,r=rOprNGfwEbeRWgbNEkqO',
|
|
||||||
'rOprNGfwEbeRWgbNEkqO',
|
|
||||||
ScramHashType.sha256,
|
|
||||||
),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
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>(), true);
|
|
||||||
expect(result.get<NegotiatorError>() is InvalidServerSignatureError, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test simple SASL2 inlining', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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>
|
|
||||||
<test-feature xmlns="invalid:example:dont:use" />
|
|
||||||
</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><test-data-request xmlns='invalid:example:dont:use' /></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
|
||||||
<test-data xmlns='invalid:example:dont:use'>Hello World</test-data>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<iq xmlns='jabber:client' type='set' id='aaaa'><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>',
|
|
||||||
''',
|
|
||||||
adjustId: true,
|
|
||||||
ignoreId: true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
|
||||||
password: 'aaaa',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
ExampleNegotiator(),
|
|
||||||
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(
|
|
||||||
conn.getNegotiatorById<ExampleNegotiator>('testNegotiator')!.value,
|
|
||||||
'Hello World',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test simple SASL2 inlining 2', () async {
|
|
||||||
final fakeSocket = StubTCPSocket([
|
|
||||||
StringExpectation(
|
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@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" />
|
|
||||||
</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></authenticate>",
|
|
||||||
'''
|
|
||||||
<success xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<authorization-identifier>polynomdivision@test.server</authorization-identifier>
|
|
||||||
</success>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
StanzaExpectation(
|
|
||||||
"<iq xmlns='jabber:client' type='set' id='aaaa'><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>',
|
|
||||||
''',
|
|
||||||
adjustId: true,
|
|
||||||
ignoreId: true,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
final conn = XmppConnection(
|
|
||||||
TestingReconnectionPolicy(),
|
|
||||||
AlwaysConnectedConnectivityManager(),
|
|
||||||
fakeSocket,
|
|
||||||
)..setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
|
||||||
password: 'aaaa',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await conn.registerManagers([
|
|
||||||
PresenceManager(),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
DiscoManager([]),
|
|
||||||
]);
|
|
||||||
await conn.registerFeatureNegotiators([
|
|
||||||
SaslPlainNegotiator(),
|
|
||||||
ResourceBindingNegotiator(),
|
|
||||||
ExampleNegotiator(),
|
|
||||||
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(
|
|
||||||
conn.getNegotiatorById<ExampleNegotiator>('testNegotiator')!.value,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,269 +0,0 @@
|
|||||||
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' from='polynomdivision@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' from='polynomdivision@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);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Test failed FAST authentication with 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' from='polynomdivision@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>",
|
|
||||||
'''
|
|
||||||
<failure xmlns='urn:xmpp:sasl:2'>
|
|
||||||
<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
|
|
||||||
</failure>
|
|
||||||
''',
|
|
||||||
),
|
|
||||||
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='ed00e36cb42449a365a306a413f51ffd5ea8' />
|
|
||||||
</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()
|
|
||||||
..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<NegotiatorError>(), false);
|
|
||||||
expect(conn.resource, 'MU29eEZn');
|
|
||||||
expect(fakeSocket.getState(), 4);
|
|
||||||
|
|
||||||
final token = conn
|
|
||||||
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
|
|
||||||
.fastToken;
|
|
||||||
expect(token != null, true);
|
|
||||||
expect(token!.token, 'ed00e36cb42449a365a306a413f51ffd5ea8');
|
|
||||||
});
|
|
||||||
}
|
|
@ -35,6 +35,7 @@ Future<bool> testRosterManager(
|
|||||||
),
|
),
|
||||||
getManagerById: getManagerNullStub,
|
getManagerById: getManagerNullStub,
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('$bareJid/$resource'),
|
getFullJID: () => JID.fromString('$bareJid/$resource'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -70,7 +71,7 @@ void main() {
|
|||||||
final fakeSocket = StubTCPSocket(
|
final fakeSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -89,7 +90,7 @@ void main() {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -139,7 +140,7 @@ void main() {
|
|||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
EntityCapabilitiesManager('http://moxxmpp.example'),
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
@ -150,14 +151,13 @@ void main() {
|
|||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
);
|
);
|
||||||
expect(fakeSocket.getState(), /*6*/ 5);
|
expect(fakeSocket.getState(), /*6*/ 5);
|
||||||
expect(conn.resource, 'MU29eEZn');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test a failed SASL auth', () async {
|
test('Test a failed SASL auth', () async {
|
||||||
final fakeSocket = StubTCPSocket(
|
final fakeSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -195,7 +195,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
EntityCapabilitiesManager('http://moxxmpp.example'),
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ void main() {
|
|||||||
final fakeSocket = StubTCPSocket(
|
final fakeSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -254,7 +254,7 @@ void main() {
|
|||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
EntityCapabilitiesManager('http://moxxmpp.example'),
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([SaslPlainNegotiator()]);
|
conn.registerFeatureNegotiators([SaslPlainNegotiator()]);
|
||||||
|
|
||||||
conn.asBroadcastStream().listen((event) {
|
conn.asBroadcastStream().listen((event) {
|
||||||
if (event is AuthenticationFailedEvent &&
|
if (event is AuthenticationFailedEvent &&
|
||||||
@ -296,6 +296,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
getManagerById: getManagerNullStub,
|
getManagerById: getManagerNullStub,
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket([]),
|
getSocket: () => StubTCPSocket([]),
|
||||||
getConnection: () => XmppConnection(
|
getConnection: () => XmppConnection(
|
||||||
@ -379,7 +380,7 @@ void main() {
|
|||||||
final fakeSocket = StubTCPSocket(
|
final fakeSocket = StubTCPSocket(
|
||||||
[
|
[
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='example.org' from='testuser@example.org' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='example.org' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@ -406,11 +407,12 @@ void main() {
|
|||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
conn
|
||||||
|
..registerFeatureNegotiators([
|
||||||
// SaslPlainNegotiator(),
|
// SaslPlainNegotiator(),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
]);
|
])
|
||||||
conn.setConnectionSettings(
|
..setConnectionSettings(
|
||||||
ConnectionSettings(
|
ConnectionSettings(
|
||||||
jid: JID.fromString('testuser@example.org'),
|
jid: JID.fromString('testuser@example.org'),
|
||||||
password: 'abc123',
|
password: 'abc123',
|
||||||
|
@ -18,8 +18,7 @@ Future<void> _runTest(String domain) async {
|
|||||||
TestingReconnectionPolicy(),
|
TestingReconnectionPolicy(),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
socket,
|
socket,
|
||||||
);
|
)..registerFeatureNegotiators([
|
||||||
await connection.registerFeatureNegotiators([
|
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
await connection.registerManagers([
|
||||||
|
@ -19,8 +19,7 @@ void main() {
|
|||||||
TestingSleepReconnectionPolicy(10),
|
TestingSleepReconnectionPolicy(10),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
TCPSocketWrapper(),
|
TCPSocketWrapper(),
|
||||||
);
|
)..registerFeatureNegotiators([
|
||||||
await connection.registerFeatureNegotiators([
|
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
await connection.registerManagers([
|
||||||
@ -69,8 +68,7 @@ void main() {
|
|||||||
TestingReconnectionPolicy(),
|
TestingReconnectionPolicy(),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
TCPSocketWrapper(),
|
TCPSocketWrapper(),
|
||||||
);
|
)..registerFeatureNegotiators([
|
||||||
await connection.registerFeatureNegotiators([
|
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
await connection.registerManagers([
|
||||||
|
@ -243,8 +243,6 @@ class TCPSocketWrapper extends BaseSocketWrapper {
|
|||||||
if (await _hostPortConnect(host, port)) {
|
if (await _hostPortConnect(host, port)) {
|
||||||
_setupStreams();
|
_setupStreams();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,13 +289,17 @@ class TCPSocketWrapper extends BaseSocketWrapper {
|
|||||||
_eventStream.stream.asBroadcastStream();
|
_eventStream.stream.asBroadcastStream();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(String data) {
|
void write(String data, {String? redact}) {
|
||||||
if (_socket == null) {
|
if (_socket == null) {
|
||||||
_log.severe('Failed to write to socket as _socket is null');
|
_log.severe('Failed to write to socket as _socket is null');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (redact != null) {
|
||||||
|
_log.finest('**> $redact');
|
||||||
|
} else {
|
||||||
_log.finest('==> $data');
|
_log.finest('==> $data');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_socket!.write(data);
|
_socket!.write(data);
|
||||||
|
Loading…
Reference in New Issue
Block a user