Merge pull request 'OMEMO Improvements' (#47) from omemo-changes into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/47
This commit is contained in:
commit
4f9a0605c7
@ -30,11 +30,12 @@ class EchoMessageManager extends XmppManagerBase {
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final body = stanza.firstTag('body');
|
||||
if (body == null) return state.copyWith(done: true);
|
||||
if (body == null) return state..done = true;
|
||||
|
||||
final bodyText = body.innerText();
|
||||
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.message(
|
||||
to: stanza.from,
|
||||
children: [
|
||||
@ -45,9 +46,10 @@ class EchoMessageManager extends XmppManagerBase {
|
||||
],
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
116
examples_dart/bin/omemo_client.dart
Normal file
116
examples_dart/bin/omemo_client.dart
Normal file
@ -0,0 +1,116 @@
|
||||
import 'package:chalkdart/chalk.dart';
|
||||
import 'package:cli_repl/cli_repl.dart';
|
||||
import 'package:example_dart/arguments.dart';
|
||||
import 'package:example_dart/socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:omemo_dart/omemo_dart.dart' as omemo;
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Set up logging
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
final parser = ArgumentParser()
|
||||
..parser.addOption('to', help: 'The JID to send messages to');
|
||||
final options = parser.handleArguments(args);
|
||||
if (options == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect
|
||||
final jid = parser.jid;
|
||||
final to = JID.fromString(options['to']! as String).toBare();
|
||||
final connection = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
ExampleTCPSocketWrapper(parser.srvRecord),
|
||||
)..connectionSettings = parser.connectionSettings;
|
||||
|
||||
// Generate OMEMO data
|
||||
omemo.OmemoManager? oom;
|
||||
final moxxmppOmemo = OmemoManager(
|
||||
() async => oom!,
|
||||
(toJid, _) async => toJid == to,
|
||||
);
|
||||
oom = omemo.OmemoManager(
|
||||
await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5),
|
||||
omemo.BlindTrustBeforeVerificationTrustManager(),
|
||||
moxxmppOmemo.sendEmptyMessageImpl,
|
||||
moxxmppOmemo.fetchDeviceList,
|
||||
moxxmppOmemo.fetchDeviceBundle,
|
||||
moxxmppOmemo.subscribeToDeviceListImpl,
|
||||
moxxmppOmemo.publishDeviceImpl,
|
||||
);
|
||||
final deviceId = await oom.getDeviceId();
|
||||
Logger.root.info('Our device id: $deviceId');
|
||||
|
||||
// Register the managers and negotiators
|
||||
await connection.registerManagers([
|
||||
PresenceManager(),
|
||||
DiscoManager([]),
|
||||
PubSubManager(),
|
||||
MessageManager(),
|
||||
moxxmppOmemo,
|
||||
]);
|
||||
await connection.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StartTlsNegotiator(),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
|
||||
]);
|
||||
|
||||
// Set up event handlers
|
||||
connection.asBroadcastStream().listen((event) {
|
||||
if (event is MessageEvent) {
|
||||
Logger.root.info(event.id);
|
||||
Logger.root.info(event.extensions.keys.toList());
|
||||
|
||||
final body = event.encryptionError != null
|
||||
? chalk.red('Failed to decrypt message: ${event.encryptionError}')
|
||||
: chalk.green(event.get<MessageBodyData>()?.body ?? '');
|
||||
print('[${event.from.toString()}] $body');
|
||||
}
|
||||
});
|
||||
|
||||
// Connect
|
||||
Logger.root.info('Connecting...');
|
||||
final result =
|
||||
await connection.connect(shouldReconnect: false, waitUntilLogin: true);
|
||||
if (!result.isType<bool>()) {
|
||||
Logger.root.severe('Authentication failed!');
|
||||
return;
|
||||
}
|
||||
Logger.root.info('Connected.');
|
||||
|
||||
// Publish our bundle
|
||||
Logger.root.info('Publishing bundle');
|
||||
final device = await oom.getDevice();
|
||||
final omemoResult = await moxxmppOmemo.publishBundle(await device.toBundle());
|
||||
if (!omemoResult.isType<bool>()) {
|
||||
Logger.root.severe(
|
||||
'Failed to publish OMEMO bundle: ${omemoResult.get<OmemoError>()}');
|
||||
return;
|
||||
}
|
||||
|
||||
final repl = Repl(prompt: '> ');
|
||||
await for (final line in repl.runAsync()) {
|
||||
await connection
|
||||
.getManagerById<MessageManager>(messageManager)!
|
||||
.sendMessage(
|
||||
to,
|
||||
TypedMap<StanzaHandlerExtension>.fromList([
|
||||
MessageBodyData(line),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
// Disconnect
|
||||
await connection.disconnect();
|
||||
}
|
84
examples_dart/lib/arguments.dart
Normal file
84
examples_dart/lib/arguments.dart
Normal file
@ -0,0 +1,84 @@
|
||||
import 'package:args/args.dart';
|
||||
import 'package:chalkdart/chalk.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
extension StringToInt on String {
|
||||
int toInt() => int.parse(this);
|
||||
}
|
||||
|
||||
/// A wrapper around [ArgParser] for providing convenience functions and standard parameters
|
||||
/// to the examples.
|
||||
class ArgumentParser {
|
||||
ArgumentParser() {
|
||||
parser
|
||||
..addOption('jid', help: 'The JID to connect as')
|
||||
..addOption('password', help: 'The password to use for authenticating')
|
||||
..addOption('host',
|
||||
help:
|
||||
'The host address to connect to (By default uses the domain part of the JID)')
|
||||
..addOption('port', help: 'The port to connect to')
|
||||
..addOption('xmpps-srv',
|
||||
help:
|
||||
'Inject a SRV record for _xmpps-client._tcp. Format: <priority>,<weight>,<target>,<port>')
|
||||
..addFlag('help',
|
||||
abbr: 'h',
|
||||
negatable: false,
|
||||
defaultsTo: false,
|
||||
help: 'Show this help text');
|
||||
}
|
||||
|
||||
/// The [ArgParser] that handles parsing the arguments.
|
||||
final ArgParser parser = ArgParser();
|
||||
|
||||
/// The parsed options. Only valid after calling [handleArguments].
|
||||
late ArgResults options;
|
||||
|
||||
ArgResults? handleArguments(List<String> args) {
|
||||
options = parser.parse(args);
|
||||
if (options['help']!) {
|
||||
print(parser.usage);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options['jid'] == null) {
|
||||
print(chalk.red('No JID specified'));
|
||||
print(parser.usage);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options['password'] == null) {
|
||||
print(chalk.red('No password specified'));
|
||||
print(parser.usage);
|
||||
return null;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// The JID to connect as.
|
||||
JID get jid => JID.fromString(options['jid']!).toBare();
|
||||
|
||||
/// Construct connection settings from the parsed options.
|
||||
ConnectionSettings get connectionSettings => ConnectionSettings(
|
||||
jid: jid,
|
||||
password: options['password']!,
|
||||
host: options['host'],
|
||||
port: (options['port'] as String?)?.toInt(),
|
||||
);
|
||||
|
||||
/// Construct an xmpps-client SRV record for injection, if specified.
|
||||
MoxSrvRecord? get srvRecord {
|
||||
if (options['xmpps-srv'] == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final parts = options['xmpps-srv']!.split(',');
|
||||
return MoxSrvRecord(
|
||||
int.parse(parts[0]),
|
||||
int.parse(parts[1]),
|
||||
parts[2],
|
||||
int.parse(parts[3]),
|
||||
);
|
||||
}
|
||||
}
|
22
examples_dart/lib/socket.dart
Normal file
22
examples_dart/lib/socket.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
/// A simple socket for examples that allows injection of SRV records (since
|
||||
/// we cannot use moxdns here).
|
||||
class ExampleTCPSocketWrapper extends TCPSocketWrapper {
|
||||
ExampleTCPSocketWrapper(this.srvRecord);
|
||||
|
||||
/// A potential SRV record to inject for testing.
|
||||
final MoxSrvRecord? srvRecord;
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
||||
return [
|
||||
if (srvRecord != null) srvRecord!,
|
||||
];
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@ environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
args: 2.4.1
|
||||
chalkdart: 2.0.9
|
||||
cli_repl: 0.2.3
|
||||
logging: ^1.0.2
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
@ -14,6 +17,9 @@ dependencies:
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.3.1
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: ^0.5.0
|
||||
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
|
2
integration_tests/create_users.sh
Normal file
2
integration_tests/create_users.sh
Normal file
@ -0,0 +1,2 @@
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123
|
@ -16,6 +16,7 @@
|
||||
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
|
||||
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
|
||||
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
|
||||
- **BREAKING**: Overriding `BaseOmemoManager` is no longer required. `OmemoManager` now takes callback methods instead.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
|
@ -38,7 +38,6 @@ export 'package:moxxmpp/src/settings.dart';
|
||||
export 'package:moxxmpp/src/socket.dart';
|
||||
export 'package:moxxmpp/src/stanza.dart';
|
||||
export 'package:moxxmpp/src/stringxml.dart';
|
||||
export 'package:moxxmpp/src/types/result.dart';
|
||||
export 'package:moxxmpp/src/util/typed_map.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||
|
@ -25,7 +25,6 @@ import 'package:moxxmpp/src/settings.dart';
|
||||
import 'package:moxxmpp/src/socket.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/util/queue.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
@ -479,6 +478,7 @@ class XmppConnection {
|
||||
newStanza,
|
||||
TypedMap(),
|
||||
encrypted: details.encrypted,
|
||||
shouldEncrypt: details.shouldEncrypt,
|
||||
forceEncryption: details.forceEncryption,
|
||||
),
|
||||
);
|
||||
@ -736,6 +736,13 @@ class XmppConnection {
|
||||
: '';
|
||||
_log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}');
|
||||
|
||||
if (incomingPreHandlers.skip) {
|
||||
_log.fine(
|
||||
'Not processing stanza (${incomingPreHandlers.stanza.tag}, ${incomingPreHandlers.stanza.id}) due to skip=true.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final awaited = await _stanzaAwaiter.onData(
|
||||
incomingPreHandlers.stanza,
|
||||
connectionSettings.jid.toBare(),
|
||||
@ -753,6 +760,7 @@ class XmppConnection {
|
||||
incomingPreHandlers.stanza,
|
||||
incomingPreHandlers.extensions,
|
||||
encrypted: incomingPreHandlers.encrypted,
|
||||
encryptionError: incomingPreHandlers.encryptionError,
|
||||
cancelReason: incomingPreHandlers.cancelReason,
|
||||
),
|
||||
);
|
||||
|
@ -70,9 +70,9 @@ class MessageEvent extends XmppEvent {
|
||||
MessageEvent(
|
||||
this.from,
|
||||
this.to,
|
||||
this.id,
|
||||
this.encrypted,
|
||||
this.extensions, {
|
||||
this.id,
|
||||
this.type,
|
||||
this.error,
|
||||
this.encryptionError,
|
||||
@ -85,7 +85,7 @@ class MessageEvent extends XmppEvent {
|
||||
final JID to;
|
||||
|
||||
/// The id attribute of the message.
|
||||
final String id;
|
||||
final String? id;
|
||||
|
||||
/// The type attribute of the message.
|
||||
final String? type;
|
||||
|
@ -47,7 +47,6 @@ abstract class XmppManagerBase {
|
||||
|
||||
final result = await dm!.discoInfoQuery(
|
||||
_managerAttributes.getConnectionSettings().jid.toDomain(),
|
||||
shouldEncrypt: false,
|
||||
);
|
||||
if (result.isType<DiscoError>()) {
|
||||
return false;
|
||||
|
@ -13,12 +13,20 @@ class StanzaHandlerData {
|
||||
this.encryptionError,
|
||||
this.encrypted = false,
|
||||
this.forceEncryption = false,
|
||||
this.shouldEncrypt = true,
|
||||
this.skip = false,
|
||||
});
|
||||
|
||||
/// Indicates to the runner that processing is now done. This means that all
|
||||
/// pre-processing is done and no other handlers should be consulted.
|
||||
bool done;
|
||||
|
||||
/// Only useful in combination with [done] = true: When [skip] is set to true and
|
||||
/// this [StanzaHandlerData] object is returned from a IncomingPreStanzaHandler, then
|
||||
/// moxxmpp will skip checking whether the stanza was awaited and will not run any actual
|
||||
/// IncomingStanzaHandler callbacks.
|
||||
bool skip;
|
||||
|
||||
/// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||
/// should run. The stanza also will not be sent.
|
||||
bool cancel;
|
||||
@ -33,7 +41,7 @@ class StanzaHandlerData {
|
||||
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
||||
Stanza stanza;
|
||||
|
||||
/// Whether the stanza was received encrypted
|
||||
/// Whether the stanza is already encrypted
|
||||
bool encrypted;
|
||||
|
||||
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
@ -42,6 +50,10 @@ class StanzaHandlerData {
|
||||
// to the JID anyway.
|
||||
bool forceEncryption;
|
||||
|
||||
/// Flag indicating whether a E2EE implementation should encrypt the stanza (true)
|
||||
/// or not (false).
|
||||
bool shouldEncrypt;
|
||||
|
||||
/// Additional data from other managers.
|
||||
final TypedMap<StanzaHandlerExtension> extensions;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
@ -100,10 +100,10 @@ class StanzaHandler extends Handler {
|
||||
matches &= firstTag?.xmlns == tagXmlns;
|
||||
}
|
||||
} else if (tagXmlns != null) {
|
||||
matches &= listContains(
|
||||
node.children,
|
||||
matches &= node.children.firstWhereOrNull(
|
||||
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||
);
|
||||
) !=
|
||||
null;
|
||||
}
|
||||
|
||||
return matches;
|
||||
|
@ -73,16 +73,23 @@ class MessageManager extends XmppManagerBase {
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza _,
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final body = stanza.firstTag('body');
|
||||
if (body != null) {
|
||||
state.extensions.set(
|
||||
MessageBodyData(body.innerText()),
|
||||
);
|
||||
}
|
||||
|
||||
getAttributes().sendEvent(
|
||||
MessageEvent(
|
||||
JID.fromString(state.stanza.attributes['from']! as String),
|
||||
JID.fromString(state.stanza.attributes['to']! as String),
|
||||
state.stanza.attributes['id']! as String,
|
||||
state.encrypted,
|
||||
state.extensions,
|
||||
id: state.stanza.attributes['id'] as String?,
|
||||
type: state.stanza.attributes['type'] as String?,
|
||||
error: StanzaError.fromStanza(state.stanza),
|
||||
encryptionError: state.encryptionError,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/connection.dart';
|
||||
@ -8,7 +9,6 @@ import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/settings.dart';
|
||||
import 'package:moxxmpp/src/socket.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
/// The state a negotiator is currently in
|
||||
enum NegotiatorState {
|
||||
@ -117,8 +117,7 @@ abstract class XmppFeatureNegotiatorBase {
|
||||
/// Returns true if a feature in [features], which are the children of the
|
||||
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
return firstWhereOrNull(
|
||||
features,
|
||||
return features.firstWhereOrNull(
|
||||
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
||||
) !=
|
||||
null;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -10,7 +11,6 @@ import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
/// 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
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.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_0198/xep_0198.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
@ -13,15 +13,13 @@ abstract class SaslNegotiator extends XmppFeatureNegotiatorBase {
|
||||
@override
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
// Is SASL advertised?
|
||||
final mechanisms = firstWhereOrNull(
|
||||
features,
|
||||
final mechanisms = features.firstWhereOrNull(
|
||||
(XMLNode feature) => feature.attributes['xmlns'] == saslXmlns,
|
||||
);
|
||||
if (mechanisms == null) return false;
|
||||
|
||||
// Is SASL PLAIN advertised?
|
||||
return firstWhereOrNull(
|
||||
mechanisms.children,
|
||||
return mechanisms.children.firstWhereOrNull(
|
||||
(XMLNode mechanism) => mechanism.text == mechanismName,
|
||||
) !=
|
||||
null;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.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/rfcs/rfc_6120/sasl/nonza.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';
|
||||
import 'package:saslprep/saslprep.dart';
|
||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
@ -10,7 +11,6 @@ import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.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';
|
||||
import 'package:random_string/random_string.dart';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.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';
|
||||
|
||||
enum _StartTlsState { ready, requested }
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -14,7 +15,6 @@ import 'package:moxxmpp/src/roster/errors.dart';
|
||||
import 'package:moxxmpp/src/roster/state.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
@immutable
|
||||
class XmppRosterItem {
|
||||
|
@ -7,6 +7,7 @@ class StanzaDetails {
|
||||
this.stanza, {
|
||||
this.addId = true,
|
||||
this.awaitable = true,
|
||||
this.shouldEncrypt = true,
|
||||
this.encrypted = false,
|
||||
this.forceEncryption = false,
|
||||
this.bypassQueue = false,
|
||||
@ -22,9 +23,16 @@ class StanzaDetails {
|
||||
/// Track the stanza to allow awaiting its response.
|
||||
final bool awaitable;
|
||||
|
||||
final bool forceEncryption;
|
||||
|
||||
/// Flag indicating whether the stanza that is sent is already encrypted (true)
|
||||
/// or not (false). This is only useful for E2EE implementations that have to
|
||||
/// send heartbeats that must bypass themselves.
|
||||
final bool encrypted;
|
||||
|
||||
final bool forceEncryption;
|
||||
/// Tells an E2EE implementation, if available, to encrypt the stanza (true) or
|
||||
/// ignore the stanza (false).
|
||||
final bool shouldEncrypt;
|
||||
|
||||
/// Bypasses being put into the queue. Useful for sending stanzas that must go out
|
||||
/// now, where it's okay if it does not get sent.
|
||||
|
@ -1,16 +0,0 @@
|
||||
class Result<T, V> {
|
||||
const Result(this._data)
|
||||
: assert(
|
||||
_data is T || _data is V,
|
||||
'Invalid data type: Must be either $T or $V',
|
||||
);
|
||||
final dynamic _data;
|
||||
|
||||
bool isType<S>() => _data is S;
|
||||
|
||||
S get<S>() {
|
||||
assert(_data is S, 'Data is not $S');
|
||||
|
||||
return _data as S;
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ class AsyncStanzaQueue {
|
||||
this._canSendCallback,
|
||||
);
|
||||
|
||||
/// The lock for accessing [AsyncStanzaQueue._lock] and [AsyncStanzaQueue._running].
|
||||
/// The lock for accessing [AsyncStanzaQueue._queue].
|
||||
final Lock _lock = Lock();
|
||||
|
||||
/// The actual job queue.
|
||||
@ -44,22 +44,15 @@ class AsyncStanzaQueue {
|
||||
|
||||
final CanSendCallback _canSendCallback;
|
||||
|
||||
/// Indicates whether we are currently executing a job.
|
||||
bool _running = false;
|
||||
|
||||
@visibleForTesting
|
||||
Queue<StanzaQueueEntry> get queue => _queue;
|
||||
|
||||
@visibleForTesting
|
||||
bool get isRunning => _running;
|
||||
|
||||
/// Adds a job [entry] to the queue.
|
||||
Future<void> enqueueStanza(StanzaQueueEntry entry) async {
|
||||
await _lock.synchronized(() async {
|
||||
_queue.add(entry);
|
||||
|
||||
if (!_running && _queue.isNotEmpty && await _canSendCallback()) {
|
||||
_running = true;
|
||||
if (_queue.isNotEmpty && await _canSendCallback()) {
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
@ -79,8 +72,6 @@ class AsyncStanzaQueue {
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
} else {
|
||||
_running = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -90,7 +81,6 @@ class AsyncStanzaQueue {
|
||||
|
||||
await _lock.synchronized(() {
|
||||
if (_queue.isNotEmpty) {
|
||||
_running = true;
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
|
@ -20,4 +20,6 @@ class TypedMap<B> {
|
||||
|
||||
/// Return the object of type [T] from the map, if it has been stored.
|
||||
T? get<T>() => _data[T] as T?;
|
||||
|
||||
Iterable<Object> get keys => _data.keys;
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.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';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
@ -80,7 +80,7 @@ class DataForm {
|
||||
final List<List<DataFormField>> items;
|
||||
|
||||
DataFormField? getFieldByVar(String varAttr) {
|
||||
return firstWhereOrNull(fields, (field) => field.varAttr == varAttr);
|
||||
return fields.firstWhereOrNull((field) => field.varAttr == varAttr);
|
||||
}
|
||||
|
||||
XMLNode toXml() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -9,7 +10,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/util/wait.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/cache.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
@ -255,7 +255,7 @@ class DiscoManager extends XmppManagerBase {
|
||||
Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(
|
||||
JID entity, {
|
||||
String? node,
|
||||
bool shouldEncrypt = true,
|
||||
bool shouldEncrypt = false,
|
||||
bool shouldCache = true,
|
||||
}) async {
|
||||
DiscoInfo? info;
|
||||
@ -294,7 +294,7 @@ class DiscoManager extends XmppManagerBase {
|
||||
final stanza = (await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
buildDiscoInfoQueryStanza(entity, node),
|
||||
encrypted: !shouldEncrypt,
|
||||
shouldEncrypt: shouldEncrypt,
|
||||
),
|
||||
))!;
|
||||
final query = stanza.firstTag('query');
|
||||
@ -325,7 +325,7 @@ class DiscoManager extends XmppManagerBase {
|
||||
Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(
|
||||
JID entity, {
|
||||
String? node,
|
||||
bool shouldEncrypt = true,
|
||||
bool shouldEncrypt = false,
|
||||
}) async {
|
||||
final key = DiscoCacheKey(entity, node);
|
||||
final future = await _discoItemsTracker.waitFor(key);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -7,7 +8,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
abstract class VCardError {}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -9,7 +10,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
@ -202,6 +202,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
@ -245,6 +246,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
@ -329,6 +331,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
)
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
if (result.attributes['type'] != 'result') {
|
||||
@ -419,6 +422,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
)
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
@ -471,6 +475,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
@ -521,6 +526,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
if (form.attributes['type'] != 'result') {
|
||||
@ -550,6 +556,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
if (submit.attributes['type'] != 'result') {
|
||||
@ -580,6 +587,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
@ -624,6 +632,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
),
|
||||
))!;
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'dart:convert';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.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/xep_0030.dart';
|
||||
@ -225,7 +225,6 @@ class UserAvatarManager extends XmppManagerBase {
|
||||
final response = await disco.discoItemsQuery(
|
||||
jid,
|
||||
node: userAvatarDataXmlns,
|
||||
shouldEncrypt: false,
|
||||
);
|
||||
if (response.isType<DiscoError>()) return Result(UnknownAvatarError());
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.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_0198/nonzas.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.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_0386.dart';
|
||||
|
||||
class CSIActiveNonza extends XMLNode {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
@ -7,7 +8,6 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.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/xep_0030.dart';
|
||||
@ -58,10 +58,10 @@ class HttpFileUploadManager extends XmppManagerBase {
|
||||
/// Returns whether the entity provided an identity that tells us that we can ask it
|
||||
/// for an HTTP upload slot.
|
||||
bool _containsFileUploadIdentity(DiscoInfo info) {
|
||||
return listContains(
|
||||
info.identities,
|
||||
return info.identities.firstWhereOrNull(
|
||||
(Identity id) => id.category == 'store' && id.type == 'file',
|
||||
);
|
||||
) !=
|
||||
null;
|
||||
}
|
||||
|
||||
/// Extract the maximum filesize in octets from the disco response. Returns null
|
||||
|
@ -13,9 +13,8 @@ class DoNotEncrypt {
|
||||
|
||||
/// An encryption error caused by OMEMO.
|
||||
class OmemoEncryptionError {
|
||||
const OmemoEncryptionError(this.jids, this.devices);
|
||||
const OmemoEncryptionError(this.deviceEncryptionErrors);
|
||||
|
||||
/// See omemo_dart's EncryptionResult for info on these fields.
|
||||
final Map<String, OmemoException> jids;
|
||||
final Map<RatchetMapKey, OmemoException> devices;
|
||||
/// See omemo_dart's EncryptionResult for info on this field.
|
||||
final Map<String, List<EncryptToJidError>> deviceEncryptionErrors;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@ -10,13 +11,11 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.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/xep_0030.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0203.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0280.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
||||
@ -24,9 +23,21 @@ import 'package:moxxmpp/src/xeps/xep_0384/crypto.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0384/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
||||
import 'package:omemo_dart/omemo_dart.dart';
|
||||
import 'package:omemo_dart/omemo_dart.dart' as omemo;
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
/// A callback that is executed whenever we need to acquire the OmemoManager backing
|
||||
/// the manager.
|
||||
typedef GetOmemoManagerCallback = Future<omemo.OmemoManager> Function();
|
||||
|
||||
/// A callback for figuring out whether a stanza should be encrypted or not. Note that
|
||||
/// returning true here does not necessarily mean that a stanza gets encrypted because
|
||||
/// handlers can indicate that a stanza should not be encrypted, e.g. PubSub.
|
||||
typedef ShouldEncryptStanzaCallback = Future<bool> Function(
|
||||
JID toJid,
|
||||
Stanza stanza,
|
||||
);
|
||||
|
||||
const _doNotEncryptList = [
|
||||
// XEP-0033
|
||||
DoNotEncrypt('addresses', extendedAddressingXmlns),
|
||||
@ -43,8 +54,15 @@ const _doNotEncryptList = [
|
||||
DoNotEncrypt('stanza-id', stableIdXmlns),
|
||||
];
|
||||
|
||||
abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
BaseOmemoManager() : super(omemoManager);
|
||||
class OmemoManager extends XmppManagerBase {
|
||||
OmemoManager(this._getOmemoManager, this._shouldEncryptStanza)
|
||||
: super(omemoManager);
|
||||
|
||||
/// Callback for getting the [omemo.OmemoManager].
|
||||
final GetOmemoManagerCallback _getOmemoManager;
|
||||
|
||||
/// Callback for checking whether a stanza should be encrypted or not.
|
||||
final ShouldEncryptStanzaCallback _shouldEncryptStanza;
|
||||
|
||||
// TODO(Unknown): Technically, this is not always true
|
||||
@override
|
||||
@ -113,22 +131,19 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
// Tell the OmemoManager
|
||||
(await getOmemoManager()).onDeviceListUpdate(jid.toString(), ids);
|
||||
await (await _getOmemoManager()).onDeviceListUpdate(jid.toString(), ids);
|
||||
|
||||
// Generate an event
|
||||
getAttributes().sendEvent(OmemoDeviceListUpdatedEvent(jid, ids));
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForOverriding
|
||||
Future<OmemoManager> getOmemoManager();
|
||||
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
||||
Future<int> _getDeviceId() async => (await _getOmemoManager()).getDeviceId();
|
||||
|
||||
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
||||
Future<int> _getDeviceId() async => (await getOmemoManager()).getDeviceId();
|
||||
|
||||
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
||||
Future<OmemoBundle> _getDeviceBundle() async {
|
||||
final om = await getOmemoManager();
|
||||
Future<omemo.OmemoBundle> _getDeviceBundle() async {
|
||||
final om = await _getOmemoManager();
|
||||
final device = await om.getDevice();
|
||||
return device.toBundle();
|
||||
}
|
||||
@ -199,53 +214,45 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
XMLNode _buildEncryptedElement(
|
||||
EncryptionResult result,
|
||||
omemo.EncryptionResult result,
|
||||
String recipientJid,
|
||||
int deviceId,
|
||||
) {
|
||||
final keyElements = <String, List<XMLNode>>{};
|
||||
for (final key in result.encryptedKeys) {
|
||||
final keyElement = XMLNode(
|
||||
for (final keys in result.encryptedKeys.entries) {
|
||||
keyElements[keys.key] = keys.value
|
||||
.map(
|
||||
(ek) => XMLNode(
|
||||
tag: 'key',
|
||||
attributes: <String, String>{
|
||||
'rid': '${key.rid}',
|
||||
'kex': key.kex ? 'true' : 'false',
|
||||
attributes: {
|
||||
'rid': ek.rid.toString(),
|
||||
if (ek.kex) 'kex': 'true',
|
||||
},
|
||||
text: key.value,
|
||||
);
|
||||
|
||||
if (keyElements.containsKey(key.jid)) {
|
||||
keyElements[key.jid]!.add(keyElement);
|
||||
} else {
|
||||
keyElements[key.jid] = [keyElement];
|
||||
}
|
||||
text: ek.value,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
final keysElements = keyElements.entries.map((entry) {
|
||||
return XMLNode(
|
||||
tag: 'keys',
|
||||
attributes: <String, String>{
|
||||
attributes: {
|
||||
'jid': entry.key,
|
||||
},
|
||||
children: entry.value,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
var payloadElement = <XMLNode>[];
|
||||
if (result.ciphertext != null) {
|
||||
payloadElement = [
|
||||
XMLNode(
|
||||
tag: 'payload',
|
||||
text: base64.encode(result.ciphertext!),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return XMLNode.xmlns(
|
||||
tag: 'encrypted',
|
||||
xmlns: omemoXmlns,
|
||||
children: [
|
||||
...payloadElement,
|
||||
if (result.ciphertext != null)
|
||||
XMLNode(
|
||||
tag: 'payload',
|
||||
text: base64Encode(result.ciphertext!),
|
||||
),
|
||||
XMLNode(
|
||||
tag: 'header',
|
||||
attributes: <String, String>{
|
||||
@ -259,7 +266,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
|
||||
/// For usage with omemo_dart's OmemoManager.
|
||||
Future<void> sendEmptyMessageImpl(
|
||||
EncryptionResult result,
|
||||
omemo.EncryptionResult result,
|
||||
String toJid,
|
||||
) async {
|
||||
await getAttributes().sendStanza(
|
||||
@ -288,7 +295,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
|
||||
/// Send a heartbeat message to [jid].
|
||||
Future<void> sendOmemoHeartbeat(String jid) async {
|
||||
final om = await getOmemoManager();
|
||||
final om = await _getOmemoManager();
|
||||
await om.sendOmemoHeartbeat(jid);
|
||||
}
|
||||
|
||||
@ -301,17 +308,22 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
/// For usage with omemo_dart's OmemoManager
|
||||
Future<OmemoBundle?> fetchDeviceBundle(String jid, int id) async {
|
||||
Future<omemo.OmemoBundle?> fetchDeviceBundle(String jid, int id) async {
|
||||
final result = await retrieveDeviceBundle(JID.fromString(jid), id);
|
||||
if (result.isType<OmemoError>()) return null;
|
||||
|
||||
return result.get<OmemoBundle>();
|
||||
return result.get<omemo.OmemoBundle>();
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onOutgoingStanza(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
if (!state.shouldEncrypt) {
|
||||
logger.finest('Not encrypting since state.shouldEncrypt is false');
|
||||
return state;
|
||||
}
|
||||
|
||||
if (state.encrypted) {
|
||||
logger.finest('Not encrypting since state.encrypted is true');
|
||||
return state;
|
||||
@ -324,7 +336,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final toJid = JID.fromString(stanza.to!).toBare();
|
||||
final shouldEncryptResult = await shouldEncryptStanza(toJid, stanza);
|
||||
final shouldEncryptResult = await _shouldEncryptStanza(toJid, stanza);
|
||||
if (!shouldEncryptResult && !state.forceEncryption) {
|
||||
logger.finest(
|
||||
'Not encrypting stanza for $toJid: Both shouldEncryptStanza and forceEncryption are false.',
|
||||
@ -351,29 +363,29 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
.getManagerById<CarbonsManager>(carbonsManager)
|
||||
?.isEnabled ??
|
||||
false;
|
||||
final om = await getOmemoManager();
|
||||
final result = await om.onOutgoingStanza(
|
||||
OmemoOutgoingStanza(
|
||||
[
|
||||
final om = await _getOmemoManager();
|
||||
final encryptToJids = [
|
||||
toJid.toString(),
|
||||
if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(),
|
||||
],
|
||||
];
|
||||
final result = await om.onOutgoingStanza(
|
||||
omemo.OmemoOutgoingStanza(
|
||||
encryptToJids,
|
||||
_buildEnvelope(toEncrypt, toJid.toString()),
|
||||
),
|
||||
);
|
||||
logger.finest('Encryption done');
|
||||
|
||||
if (!result.isSuccess(2)) {
|
||||
if (!result.canSend) {
|
||||
return state
|
||||
..cancel = true
|
||||
// If we have no device list for toJid, then the contact most likely does not
|
||||
// support OMEMO:2
|
||||
..cancelReason = result.jidEncryptionErrors[toJid.toString()]
|
||||
is NoKeyMaterialAvailableException
|
||||
..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first
|
||||
.error is omemo.NoKeyMaterialAvailableError
|
||||
? OmemoNotSupportedForContactException()
|
||||
: UnknownOmemoError()
|
||||
..encryptionError = OmemoEncryptionError(
|
||||
result.jidEncryptionErrors,
|
||||
result.deviceEncryptionErrors,
|
||||
);
|
||||
}
|
||||
@ -401,53 +413,45 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
..encrypted = true;
|
||||
}
|
||||
|
||||
/// This function is called whenever a message is to be encrypted. If it returns true,
|
||||
/// then the message will be encrypted. If it returns false, the message won't be
|
||||
/// encrypted.
|
||||
@visibleForOverriding
|
||||
Future<bool> shouldEncryptStanza(JID toJid, Stanza stanza);
|
||||
|
||||
Future<StanzaHandlerData> _onIncomingStanza(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns);
|
||||
if (encrypted == null) return state;
|
||||
if (stanza.from == null) return state;
|
||||
|
||||
final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns)!;
|
||||
final fromJid = JID.fromString(stanza.from!).toBare();
|
||||
final header = encrypted.firstTag('header')!;
|
||||
final payloadElement = encrypted.firstTag('payload');
|
||||
final keys = List<EncryptedKey>.empty(growable: true);
|
||||
final ourJid = getAttributes().getFullJID();
|
||||
final ourJidString = ourJid.toBare().toString();
|
||||
final keys = List<omemo.EncryptedKey>.empty(growable: true);
|
||||
for (final keysElement in header.findTags('keys')) {
|
||||
// We only care about our own JID
|
||||
final jid = keysElement.attributes['jid']! as String;
|
||||
for (final key in keysElement.findTags('key')) {
|
||||
keys.add(
|
||||
EncryptedKey(
|
||||
jid,
|
||||
if (jid != ourJidString) {
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.addAll(
|
||||
keysElement.findTags('key').map(
|
||||
(key) => omemo.EncryptedKey(
|
||||
int.parse(key.attributes['rid']! as String),
|
||||
key.innerText(),
|
||||
key.attributes['kex'] == 'true',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final ourJid = getAttributes().getFullJID();
|
||||
final sid = int.parse(header.attributes['sid']! as String);
|
||||
|
||||
final om = await getOmemoManager();
|
||||
final om = await _getOmemoManager();
|
||||
final result = await om.onIncomingStanza(
|
||||
OmemoIncomingStanza(
|
||||
omemo.OmemoIncomingStanza(
|
||||
fromJid.toString(),
|
||||
sid,
|
||||
state.extensions
|
||||
.get<DelayedDeliveryData>()
|
||||
?.timestamp
|
||||
.millisecondsSinceEpoch ??
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
keys,
|
||||
payloadElement?.innerText(),
|
||||
encrypted.firstTag('payload')?.innerText(),
|
||||
false,
|
||||
),
|
||||
);
|
||||
|
||||
@ -464,6 +468,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
.toList();
|
||||
}
|
||||
|
||||
logger.finest('Got payload: ${result.payload != null}');
|
||||
if (result.payload != null) {
|
||||
XMLNode envelope;
|
||||
try {
|
||||
@ -481,6 +486,8 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
// Do not add forbidden elements from the envelope
|
||||
envelopeChildren.where(shouldEncryptElement),
|
||||
);
|
||||
|
||||
logger.finest('Adding children: ${envelopeChildren.map((c) => c.tag)}');
|
||||
} else {
|
||||
logger.warning('Invalid envelope element: No <content /> element');
|
||||
}
|
||||
@ -490,6 +497,15 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore heartbeat messages
|
||||
if (stanza.tag == 'message' && encrypted.firstTag('payload') == null) {
|
||||
logger.finest('Received empty OMEMO message. Ending processing early.');
|
||||
return state
|
||||
..encrypted = true
|
||||
..skip = true
|
||||
..done = true;
|
||||
}
|
||||
|
||||
return state
|
||||
..encrypted = true
|
||||
..stanza = Stanza(
|
||||
@ -532,7 +548,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
/// Retrieve all device bundles for the JID [jid].
|
||||
///
|
||||
/// On success, returns a list of devices. On failure, returns am OmemoError.
|
||||
Future<Result<OmemoError, List<OmemoBundle>>> retrieveDeviceBundles(
|
||||
Future<Result<OmemoError, List<omemo.OmemoBundle>>> retrieveDeviceBundles(
|
||||
JID jid,
|
||||
) async {
|
||||
// TODO(Unknown): Should we query the device list first?
|
||||
@ -553,7 +569,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
/// Retrieves a bundle from entity [jid] with the device id [deviceId].
|
||||
///
|
||||
/// On success, returns the device bundle. On failure, returns an OmemoError.
|
||||
Future<Result<OmemoError, OmemoBundle>> retrieveDeviceBundle(
|
||||
Future<Result<OmemoError, omemo.OmemoBundle>> retrieveDeviceBundle(
|
||||
JID jid,
|
||||
int deviceId,
|
||||
) async {
|
||||
@ -569,7 +585,9 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
/// nodes.
|
||||
///
|
||||
/// On success, returns true. On failure, returns an OmemoError.
|
||||
Future<Result<OmemoError, bool>> publishBundle(OmemoBundle bundle) async {
|
||||
Future<Result<OmemoError, bool>> publishBundle(
|
||||
omemo.OmemoBundle bundle,
|
||||
) async {
|
||||
final attrs = getAttributes();
|
||||
final pm = attrs.getManagerById<PubSubManager>(pubsubManager)!;
|
||||
final bareJid = attrs.getFullJID().toBare();
|
||||
@ -636,6 +654,11 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
||||
await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns);
|
||||
}
|
||||
|
||||
/// Implementation for publishing our device [device].
|
||||
Future<void> publishDeviceImpl(omemo.OmemoDevice device) async {
|
||||
await publishBundle(await device.toBundle());
|
||||
}
|
||||
|
||||
/// Attempts to find out if [jid] supports omemo:2.
|
||||
///
|
||||
/// On success, returns whether [jid] has published a device list and device bundles.
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.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';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
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 {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
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';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
@ -114,8 +114,7 @@ class StatelessFileSharingData implements StanzaHandlerExtension {
|
||||
}
|
||||
|
||||
StatelessFileSharingUrlSource? getFirstUrlSource() {
|
||||
return firstWhereOrNull(
|
||||
sources,
|
||||
return sources.firstWhereOrNull(
|
||||
(StatelessFileSharingSource source) =>
|
||||
source is StatelessFileSharingUrlSource,
|
||||
) as StatelessFileSharingUrlSource?;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||
@ -54,8 +54,7 @@ class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource {
|
||||
final sources = element.firstTag('sources', xmlns: sfsXmlns)!.children;
|
||||
|
||||
// Find the first URL source
|
||||
final source = firstWhereOrNull(
|
||||
sources,
|
||||
final source = sources.firstWhereOrNull(
|
||||
(XMLNode child) =>
|
||||
child.tag == 'url-data' && child.attributes['xmlns'] == urlDataXmlns,
|
||||
)!;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
@ -9,7 +10,6 @@ import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||
|
@ -18,10 +18,10 @@ dependencies:
|
||||
meta: ^1.7.0
|
||||
moxlib:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.1.5
|
||||
version: ^0.2.0
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: ^0.4.3
|
||||
version: ^0.5.0
|
||||
random_string: ^2.3.1
|
||||
saslprep: ^1.0.2
|
||||
synchronized: ^3.0.0+2
|
||||
|
@ -30,7 +30,6 @@ void main() {
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
expect(queue.queue.length, 2);
|
||||
expect(queue.isRunning, false);
|
||||
});
|
||||
|
||||
test('Test sending', () async {
|
||||
@ -58,7 +57,6 @@ void main() {
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
expect(queue.queue.length, 0);
|
||||
expect(queue.isRunning, false);
|
||||
});
|
||||
|
||||
test('Test partial sending and resuming', () async {
|
||||
@ -89,12 +87,10 @@ void main() {
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
expect(queue.queue.length, 1);
|
||||
expect(queue.isRunning, false);
|
||||
|
||||
canRun = true;
|
||||
await queue.restart();
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
expect(queue.queue.length, 0);
|
||||
expect(queue.isRunning, false);
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp/src/parser.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'helpers/logging.dart';
|
||||
|
||||
const exampleXmlns1 = 'im:moxxmpp:example1';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../helpers/logging.dart';
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user