fix: Fix VCard and User Avatar queries being encrypted

This commit is contained in:
PapaTutuWawa 2022-12-25 13:05:16 +01:00
parent 298a8342b8
commit 09696c1c4d
4 changed files with 59 additions and 44 deletions

View File

@ -1,5 +1,4 @@
import 'dart:async'; 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';
@ -11,7 +10,6 @@ import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
class XmppManagerAttributes { class XmppManagerAttributes {
XmppManagerAttributes({ XmppManagerAttributes({
required this.sendStanza, required this.sendStanza,
required this.sendNonza, required this.sendNonza,

View File

@ -289,7 +289,7 @@ class DiscoManager extends XmppManagerBase {
} }
/// Sends a disco info query to the (full) jid [entity], optionally with node=[node]. /// Sends a disco info query to the (full) jid [entity], optionally with node=[node].
Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(String entity, { String? node}) async { Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(String entity, { String? node, bool shouldEncrypt = true }) async {
final cacheKey = DiscoCacheKey(entity, node); final cacheKey = DiscoCacheKey(entity, node);
DiscoInfo? info; DiscoInfo? info;
Completer<Result<DiscoError, DiscoInfo>>? completer; Completer<Result<DiscoError, DiscoInfo>>? completer;
@ -316,6 +316,7 @@ class DiscoManager extends XmppManagerBase {
final stanza = await getAttributes().sendStanza( final stanza = await getAttributes().sendStanza(
buildDiscoInfoQueryStanza(entity, node), buildDiscoInfoQueryStanza(entity, node),
encrypted: !shouldEncrypt,
); );
final query = stanza.firstTag('query'); final query = stanza.firstTag('query');
if (query == null) { if (query == null) {
@ -359,9 +360,12 @@ class DiscoManager extends XmppManagerBase {
} }
/// Sends a disco items query to the (full) jid [entity], optionally with node=[node]. /// Sends a disco items query to the (full) jid [entity], optionally with node=[node].
Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(String entity, { String? node }) async { Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(String entity, { String? node, bool shouldEncrypt = true }) async {
final stanza = await getAttributes() final stanza = await getAttributes()
.sendStanza(buildDiscoItemsQueryStanza(entity, node: node)) as Stanza; .sendStanza(
buildDiscoItemsQueryStanza(entity, node: node),
encrypted: !shouldEncrypt,
) as Stanza;
final query = stanza.firstTag('query'); final query = stanza.firstTag('query');
if (query == null) return Result(InvalidResponseDiscoError()); if (query == null) return Result(InvalidResponseDiscoError());

View File

@ -7,15 +7,20 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/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/types/result.dart';
abstract class VCardError {}
class UnknownVCardError extends VCardError {}
class InvalidVCardError extends VCardError {}
class VCardPhoto { class VCardPhoto {
const VCardPhoto({ this.binval }); const VCardPhoto({ this.binval });
final String? binval; final String? binval;
} }
class VCard { class VCard {
const VCard({ this.nickname, this.url, this.photo }); const VCard({ this.nickname, this.url, this.photo });
final String? nickname; final String? nickname;
final String? url; final String? url;
@ -23,7 +28,6 @@ class VCard {
} }
class VCardManager extends XmppManagerBase { class VCardManager extends XmppManagerBase {
VCardManager() : _lastHash = {}, super(); VCardManager() : _lastHash = {}, super();
final Map<String, String> _lastHash; final Map<String, String> _lastHash;
@ -59,12 +63,18 @@ class VCardManager extends XmppManagerBase {
final lastHash = _lastHash[from]; final lastHash = _lastHash[from];
if (lastHash != hash) { if (lastHash != hash) {
_lastHash[from] = hash; _lastHash[from] = hash;
final vcard = await requestVCard(from); final vcardResult = await requestVCard(from);
if (vcard != null) { if (vcardResult.isType<VCard>()) {
final binval = vcard.photo?.binval; final binval = vcardResult.get<VCard>().photo?.binval;
if (binval != null) { if (binval != null) {
getAttributes().sendEvent(AvatarUpdatedEvent(jid: from, base64: binval, hash: hash)); getAttributes().sendEvent(
AvatarUpdatedEvent(
jid: from,
base64: binval,
hash: hash,
),
);
} else { } else {
logger.warning('No avatar data found'); logger.warning('No avatar data found');
} }
@ -95,7 +105,7 @@ class VCardManager extends XmppManagerBase {
); );
} }
Future<VCard?> requestVCard(String jid) async { Future<Result<VCardError, VCard>> requestVCard(String jid) async {
final result = await getAttributes().sendStanza( final result = await getAttributes().sendStanza(
Stanza.iq( Stanza.iq(
to: jid, to: jid,
@ -107,12 +117,13 @@ class VCardManager extends XmppManagerBase {
) )
], ],
), ),
encrypted: true,
); );
if (result.attributes['type'] != 'result') return null; if (result.attributes['type'] != 'result') return Result(UnknownVCardError());
final vcard = result.firstTag('vCard', xmlns: vCardTempXmlns); final vcard = result.firstTag('vCard', xmlns: vCardTempXmlns);
if (vcard == null) return null; if (vcard == null) return Result(UnknownVCardError());
return _parseVCard(vcard); return Result(_parseVCard(vcard));
} }
} }

View File

@ -3,21 +3,24 @@ import 'package:moxxmpp/src/managers/base.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/stringxml.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/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_0060/errors.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_0060/xep_0060.dart';
class UserAvatar { abstract class AvatarError {}
class UnknownAvatarError extends AvatarError {}
class UserAvatar {
const UserAvatar({ required this.base64, required this.hash }); const UserAvatar({ required this.base64, required this.hash });
final String base64; final String base64;
final String hash; final String hash;
} }
class UserAvatarMetadata { class UserAvatarMetadata {
const UserAvatarMetadata( const UserAvatarMetadata(
this.id, this.id,
this.length, this.length,
@ -62,30 +65,30 @@ class UserAvatarManager extends XmppManagerBase {
// TODO(PapaTutuWawa): Check for PEP support // TODO(PapaTutuWawa): Check for PEP support
@override @override
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;
/// Requests the avatar from [jid]. Returns the avatar data if the request was /// Requests the avatar from [jid]. Returns the avatar data if the request was
/// successful. Null otherwise /// successful. Null otherwise
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, UserAvatar>> getUserAvatar(String jid) async {
Future<UserAvatar?> getUserAvatar(String jid) async {
final pubsub = _getPubSubManager(); final pubsub = _getPubSubManager();
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns); final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
if (resultsRaw.isType<PubSubError>()) return null; if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
final results = resultsRaw.get<List<PubSubItem>>(); final results = resultsRaw.get<List<PubSubItem>>();
if (results.isEmpty) return null; if (results.isEmpty) return Result(UnknownAvatarError());
final item = results[0]; final item = results[0];
return UserAvatar( return Result(
base64: item.payload.innerText(), UserAvatar(
hash: item.id, base64: item.payload.innerText(),
hash: item.id,
),
); );
} }
/// Publish the avatar data, [base64], on the pubsub node using [hash] as /// Publish the avatar data, [base64], on the pubsub node using [hash] as
/// the item id. [hash] must be the SHA-1 hash of the image data, while /// the item id. [hash] must be the SHA-1 hash of the image data, while
/// [base64] must be the base64-encoded version of the image data. /// [base64] must be the base64-encoded version of the image data.
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, bool>> publishUserAvatar(String base64, String hash, bool public) async {
Future<bool> publishUserAvatar(String base64, String hash, bool public) async {
final pubsub = _getPubSubManager(); final pubsub = _getPubSubManager();
final result = await pubsub.publish( final result = await pubsub.publish(
getAttributes().getFullJID().toBare().toString(), getAttributes().getFullJID().toBare().toString(),
@ -101,14 +104,15 @@ class UserAvatarManager extends XmppManagerBase {
), ),
); );
return !result.isType<PubSubError>(); if (result.isType<PubSubError>()) return Result(UnknownAvatarError());
return const Result(true);
} }
/// Publish avatar metadata [metadata] to the User Avatar's metadata node. If [public] /// Publish avatar metadata [metadata] to the User Avatar's metadata node. If [public]
/// is true, then the node will be set to an 'open' access model. If [public] is false, /// is true, then the node will be set to an 'open' access model. If [public] is false,
/// then the node will be set to an 'roster' access model. /// then the node will be set to an 'roster' access model.
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, bool>> publishUserAvatarMetadata(UserAvatarMetadata metadata, bool public) async {
Future<bool> publishUserAvatarMetadata(UserAvatarMetadata metadata, bool public) async {
final pubsub = _getPubSubManager(); final pubsub = _getPubSubManager();
final result = await pubsub.publish( final result = await pubsub.publish(
getAttributes().getFullJID().toBare().toString(), getAttributes().getFullJID().toBare().toString(),
@ -135,39 +139,37 @@ class UserAvatarManager extends XmppManagerBase {
), ),
); );
return result.isType<PubSubError>(); if (result.isType<PubSubError>()) return Result(UnknownAvatarError());
return const Result(true);
} }
/// Subscribe the data and metadata node of [jid]. /// Subscribe the data and metadata node of [jid].
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, bool>> subscribe(String jid) async {
Future<bool> subscribe(String jid) async {
await _getPubSubManager().subscribe(jid, userAvatarDataXmlns); await _getPubSubManager().subscribe(jid, userAvatarDataXmlns);
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns); await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
return true; return const Result(true);
} }
/// Unsubscribe the data and metadata node of [jid]. /// Unsubscribe the data and metadata node of [jid].
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, bool>> unsubscribe(String jid) async {
Future<bool> unsubscribe(String jid) async {
await _getPubSubManager().unsubscribe(jid, userAvatarDataXmlns); await _getPubSubManager().unsubscribe(jid, userAvatarDataXmlns);
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns); await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
return true; return const Result(true);
} }
/// Returns the PubSub Id of an avatar after doing a disco#items query. /// Returns the PubSub Id of an avatar after doing a disco#items query.
/// Note that this assumes that there is only one (1) item published on /// Note that this assumes that there is only one (1) item published on
/// the node. /// the node.
// TODO(Unknown): Migrate to Resultsv2 Future<Result<AvatarError, String>> getAvatarId(String jid) async {
Future<String?> getAvatarId(String jid) async {
final disco = getAttributes().getManagerById(discoManager)! as DiscoManager; final disco = getAttributes().getManagerById(discoManager)! as DiscoManager;
final response = await disco.discoItemsQuery(jid, node: userAvatarDataXmlns); final response = await disco.discoItemsQuery(jid, node: userAvatarDataXmlns, shouldEncrypt: false);
if (response.isType<DiscoError>()) return null; if (response.isType<DiscoError>()) return Result(UnknownAvatarError());
final items = response.get<List<DiscoItem>>(); final items = response.get<List<DiscoItem>>();
if (items.isEmpty) return null; if (items.isEmpty) return Result(UnknownAvatarError());
return items.first.name; return Result(items.first.name);
} }
} }