diff --git a/lib/features/mailbox/data/network/mailbox_api.dart b/lib/features/mailbox/data/network/mailbox_api.dart index e32f829c2..6866f4b72 100644 --- a/lib/features/mailbox/data/network/mailbox_api.dart +++ b/lib/features/mailbox/data/network/mailbox_api.dart @@ -138,9 +138,10 @@ class MailboxAPI { } Future deleteMultipleMailbox(Session session, AccountId accountId, List mailboxIds) async { - requireCapability(session, [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]); + requireCapability(session, accountId, [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]); - final coreCapability = (session.capabilities[CapabilityIdentifier.jmapCore] as CoreCapability); + final coreCapability = session.getCapabilityProperties( + accountId, CapabilityIdentifier.jmapCore); final maxMethodCount = coreCapability.maxCallsInRequest.value.toInt(); var finalResult = true; diff --git a/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart b/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart index 8dba970bf..f296f37ce 100644 --- a/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart +++ b/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart @@ -20,9 +20,11 @@ class DeleteMultipleMailboxInteractor { mailboxIdDeleted, currentMailboxState: currentMailboxState)); } else { + logError('DeleteMultipleMailboxInteractor::execute(): failed'); yield Left(DeleteMultipleMailboxFailure(null)); } } catch (e) { + logError('DeleteMultipleMailboxInteractor::execute(): exception: $e'); yield Left(DeleteMultipleMailboxFailure(e)); } } diff --git a/lib/main/error/capability_validator.dart b/lib/main/error/capability_validator.dart index f7ab46faa..51d5b7194 100644 --- a/lib/main/error/capability_validator.dart +++ b/lib/main/error/capability_validator.dart @@ -1,11 +1,17 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:tmail_ui_user/main/error/request_error.dart'; -void requireCapability(Session session, List requiredCapabilities) { - final matchedCapabilities = session.capabilities.keys +void requireCapability(Session session, AccountId accountId, List requiredCapabilities) { + final account = session.accounts[accountId]; + if (account == null) { + throw const InvalidCapability(); + } + + final matchedCapabilities = account.accountCapabilities.keys .fold>( - Set(), + {}, (previousValue, element) { if (requiredCapabilities.contains(element)) { previousValue.add(element); @@ -14,7 +20,7 @@ void requireCapability(Session session, List requiredCapab } ); final missingCapabilities = requiredCapabilities.toSet().difference(matchedCapabilities); - if (missingCapabilities.length > 0) { + if (missingCapabilities.isNotEmpty) { throw SessionMissingCapability(missingCapabilities); } } \ No newline at end of file diff --git a/lib/main/error/request_error.dart b/lib/main/error/request_error.dart index 1cbaaf500..c89511035 100644 --- a/lib/main/error/request_error.dart +++ b/lib/main/error/request_error.dart @@ -1,11 +1,18 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; -class SessionMissingCapability extends Equatable implements Exception { +class SessionMissingCapability extends InvalidCapability { final Set capabilityIdentifiers; - SessionMissingCapability(this.capabilityIdentifiers); + const SessionMissingCapability(this.capabilityIdentifiers) : super(); @override List get props => [capabilityIdentifiers]; +} + +class InvalidCapability extends Equatable implements Exception { + const InvalidCapability(); + + @override + List get props => []; } \ No newline at end of file diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index 9fdbd3934..9621a1770 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -1,6 +1,9 @@ import 'package:core/utils/app_logger.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; +import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart'; +import 'package:jmap_dart_client/jmap/core/capability/empty_capability.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:uri/uri.dart'; @@ -25,4 +28,15 @@ extension SessionExtension on Session { log('SessionExtension::getUploadUri(): uploadUri: $uploadUri'); return Uri.parse(uploadUri); } + + T getCapabilityProperties( + AccountId accountId, + CapabilityIdentifier identifier + ) { + var capability = accounts[accountId]!.accountCapabilities[identifier]; + if (capability is EmptyCapability) { + capability = capabilities[identifier] as T; + } + return (capability as T); + } } \ No newline at end of file diff --git a/test/main/error/capability_validator_test.dart b/test/main/error/capability_validator_test.dart index 570271289..7d088ac3d 100644 --- a/test/main/error/capability_validator_test.dart +++ b/test/main/error/capability_validator_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/account/account.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; +import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart'; import 'package:jmap_dart_client/jmap/core/capability/core_capability.dart'; import 'package:jmap_dart_client/jmap/core/capability/default_capability.dart'; import 'package:jmap_dart_client/jmap/core/capability/mail_capability.dart'; @@ -24,7 +25,7 @@ void main() { final session = Session( { CapabilityIdentifier.jmapSubmission: - SubmissionCapability(UnsignedInt(0), Set()), + SubmissionCapability(UnsignedInt(0), {}), CapabilityIdentifier.jmapMail: MailCapability( UnsignedInt(10000000), null, @@ -36,10 +37,10 @@ void main() { WebSocketCapability(true, Uri.parse('ws://domain.com/jmap/ws')), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:quota')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:shares')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier.jmapVacationResponse: VacationCapability(), CapabilityIdentifier.jmapMdn: MdnCapability() }, @@ -48,18 +49,9 @@ void main() { '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')): Account(AccountName('bob@domain.tld'), true, false, { CapabilityIdentifier.jmapSubmission: - SubmissionCapability(UnsignedInt(0), Set()), + SubmissionCapability(UnsignedInt(0), {}), CapabilityIdentifier.jmapWebSocket: WebSocketCapability( true, Uri.parse('ws://domain.com/jmap/ws')), - CapabilityIdentifier.jmapCore: CoreCapability( - UnsignedInt(20971520), - UnsignedInt(4), - UnsignedInt(10000000), - UnsignedInt(4), - UnsignedInt(16), - UnsignedInt(500), - UnsignedInt(500), - {CollationIdentifier("i;unicode-casemap")}), CapabilityIdentifier.jmapMail: MailCapability( UnsignedInt(10000000), null, @@ -69,10 +61,10 @@ void main() { true), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:quota')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:shares')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier.jmapVacationResponse: VacationCapability(), CapabilityIdentifier.jmapMdn: MdnCapability() }) @@ -108,15 +100,233 @@ void main() { 'http://domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}'), State('2c9f1b12-b35a-43e6-9af2-0106fb53a943')); expect( - () => requireCapability(session, [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), - throwsA(TypeMatcher())); + () => requireCapability( + session, + AccountId(Id('29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), + throwsA(const TypeMatcher())); }); - test('when session have all required capabilities exception should throw', () { + test('when account capabilities is empty exception should throw', () { final session = Session( { CapabilityIdentifier.jmapSubmission: - SubmissionCapability(UnsignedInt(0), Set()), + SubmissionCapability(UnsignedInt(0), {}), + CapabilityIdentifier.jmapMail: MailCapability( + UnsignedInt(10000000), + null, + UnsignedInt(200), + UnsignedInt(20000000), + {"receivedAt", "sentAt", "size", "from", "to", "subject"}, + true), + CapabilityIdentifier.jmapWebSocket: + WebSocketCapability(true, Uri.parse('ws://domain.com/jmap/ws')), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:quota')): + DefaultCapability({}), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:shares')): + DefaultCapability({}), + CapabilityIdentifier.jmapVacationResponse: VacationCapability(), + CapabilityIdentifier.jmapMdn: MdnCapability() + }, + { + AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')): + Account(AccountName('bob@domain.tld'), true, false, {}) + }, + { + CapabilityIdentifier.jmapSubmission: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapWebSocket: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapCore: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMail: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:quota')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:shares')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapVacationResponse: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMdn: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + }, + UserName('bob@domain.tld'), + Uri.parse('http://domain.com/jmap'), + Uri.parse( + 'http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}'), + Uri.parse('http://domain.com/upload/{accountId}'), + Uri.parse( + 'http://domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}'), + State('2c9f1b12-b35a-43e6-9af2-0106fb53a943')); + expect( + () => requireCapability( + session, + AccountId(Id('29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), + throwsA(const TypeMatcher())); + }); + + test('when accounts not have account with invalid accountId capability should throw', () { + final invalidId = Id("29883977c13473ae7cb7678ef767cbffabfc8a44a6e463d971d23a65c1dc4af6"); + final session = Session( + { + CapabilityIdentifier.jmapSubmission: + SubmissionCapability(UnsignedInt(0), {}), + CapabilityIdentifier.jmapMail: MailCapability( + UnsignedInt(10000000), + null, + UnsignedInt(200), + UnsignedInt(20000000), + {"receivedAt", "sentAt", "size", "from", "to", "subject"}, + true), + CapabilityIdentifier.jmapWebSocket: + WebSocketCapability(true, Uri.parse('ws://domain.com/jmap/ws')), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:quota')): + DefaultCapability({}), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:shares')): + DefaultCapability({}), + CapabilityIdentifier.jmapVacationResponse: VacationCapability(), + CapabilityIdentifier.jmapMdn: MdnCapability() + }, + { + AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')): + Account(AccountName('bob@domain.tld'), true, false, { + CapabilityIdentifier.jmapSubmission: + SubmissionCapability(UnsignedInt(0), {}), + CapabilityIdentifier.jmapWebSocket: WebSocketCapability( + true, Uri.parse('ws://domain.com/jmap/ws')), + CapabilityIdentifier.jmapMail: MailCapability( + UnsignedInt(10000000), + null, + UnsignedInt(200), + UnsignedInt(20000000), + {"receivedAt", "sentAt", "size", "from", "to", "subject"}, + true), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:quota')): + DefaultCapability({}), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:shares')): + DefaultCapability({}), + CapabilityIdentifier.jmapVacationResponse: VacationCapability(), + CapabilityIdentifier.jmapMdn: MdnCapability() + }) + }, + { + CapabilityIdentifier.jmapSubmission: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapWebSocket: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapCore: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMail: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:quota')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:shares')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapVacationResponse: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMdn: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + }, + UserName('bob@domain.tld'), + Uri.parse('http://domain.com/jmap'), + Uri.parse( + 'http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}'), + Uri.parse('http://domain.com/upload/{accountId}'), + Uri.parse( + 'http://domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}'), + State('2c9f1b12-b35a-43e6-9af2-0106fb53a943')); + expect( + () => requireCapability( + session, + AccountId(invalidId), + [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), + throwsA(const TypeMatcher())); + }); + + test('when accounts empty should throw exception', () { + final session = Session( + { + CapabilityIdentifier.jmapSubmission: + SubmissionCapability(UnsignedInt(0), {}), + CapabilityIdentifier.jmapMail: MailCapability( + UnsignedInt(10000000), + null, + UnsignedInt(200), + UnsignedInt(20000000), + {"receivedAt", "sentAt", "size", "from", "to", "subject"}, + true), + CapabilityIdentifier.jmapWebSocket: + WebSocketCapability(true, Uri.parse('ws://domain.com/jmap/ws')), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:quota')): + DefaultCapability({}), + CapabilityIdentifier( + Uri.parse('urn:apache:james:params:jmap:mail:shares')): + DefaultCapability({}), + CapabilityIdentifier.jmapVacationResponse: VacationCapability(), + CapabilityIdentifier.jmapMdn: MdnCapability() + }, + {}, + { + CapabilityIdentifier.jmapSubmission: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapWebSocket: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapCore: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMail: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:quota')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier( + Uri.parse( + 'urn:apache:james:params:jmap:mail:shares')): AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapVacationResponse: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + CapabilityIdentifier.jmapMdn: AccountId(Id( + '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + }, + UserName('bob@domain.tld'), + Uri.parse('http://domain.com/jmap'), + Uri.parse( + 'http://domain.com/download/{accountId}/{blobId}/?type={type}&name={name}'), + Uri.parse('http://domain.com/upload/{accountId}'), + Uri.parse( + 'http://domain.com/eventSource?types={types}&closeAfter={closeafter}&ping={ping}'), + State('2c9f1b12-b35a-43e6-9af2-0106fb53a943')); + expect( + () => requireCapability( + session, + AccountId(Id('29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), + throwsA(const TypeMatcher())); + }); + + test('when session have all required capabilities exception should not throw', () { + final session = Session( + { + CapabilityIdentifier.jmapSubmission: + SubmissionCapability(UnsignedInt(0), {}), CapabilityIdentifier.jmapCore: CoreCapability( UnsignedInt(20971520), UnsignedInt(4), @@ -138,10 +348,10 @@ void main() { WebSocketCapability(true, Uri.parse('ws://domain.com/jmap/ws')), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:quota')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:shares')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier.jmapVacationResponse: VacationCapability(), CapabilityIdentifier.jmapMdn: MdnCapability() }, @@ -150,7 +360,7 @@ void main() { '29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')): Account(AccountName('bob@domain.tld'), true, false, { CapabilityIdentifier.jmapSubmission: - SubmissionCapability(UnsignedInt(0), Set()), + SubmissionCapability(UnsignedInt(0), {}), CapabilityIdentifier.jmapWebSocket: WebSocketCapability( true, Uri.parse('ws://domain.com/jmap/ws')), CapabilityIdentifier.jmapCore: CoreCapability( @@ -171,10 +381,10 @@ void main() { true), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:quota')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier( Uri.parse('urn:apache:james:params:jmap:mail:shares')): - DefaultCapability(Map()), + DefaultCapability({}), CapabilityIdentifier.jmapVacationResponse: VacationCapability(), CapabilityIdentifier.jmapMdn: MdnCapability() }) @@ -212,7 +422,10 @@ void main() { ); expect( - () => requireCapability(session, [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), + () => requireCapability( + session, + AccountId(Id('29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6')), + [CapabilityIdentifier.jmapCore, CapabilityIdentifier.jmapMail]), returnsNormally); }); });