-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support cyrus server #3457
base: master
Are you sure you want to change the base?
Support cyrus server #3457
Conversation
This PR has been deployed to https://linagora.github.io/tmail-flutter/3457. |
291f97a
to
30f5323
Compare
the force push was to fix the 'unused import' warnings and prefix each commit message with |
regarding the way with the current changes, tmail-flutter works well with tmail-backend and with cyrus. this issue ( |
try { | ||
var principalsCapability = getCapabilityProperties<DefaultCapability>( | ||
AccountId(Id(username.value)), | ||
CapabilityIdentifier(Uri.parse('urn:ietf:params:jmap:principals'))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please define this capability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not find it in spec. It is Cyrus customization? Can you share session example of Cyrus here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here is one of the accountCapabilities
from the cyrus session:
"urn:ietf:params:jmap:principals": {
"currentUserPrincipalId": "bob",
"urn:ietf:params:jmap:calendars": {
"accountId": "bob",
"account": null,
"mayGetAvailability": true,
"sendTo": {
"imip": "mailto:[email protected]"
}
}
},
it is the only place where the email address appears in the session, so benoit suggested in this comment to fetch the address from there if necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hoangdat what do you think ? Indeed I dont think there is any doc about fetching the email from there ; the idea comes from @chibenwa 's comment
@@ -53,6 +52,28 @@ extension SessionExtension on Session { | |||
} | |||
} | |||
|
|||
String? getEmailAddress() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also should write unit test for this function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still prefer getUsername()
or getOwnEmailAddress()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unit tests done !
If we want to get the username, we should simply return session.username
. This method is used to retrieve the full email address (sometimes used as the username, sometimes not). Do you want me to change getEmailAddress()
to getOwnEmailAddress()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
term email address
is not specific for this. The reason for usename
is the current account use the app, we need to distinguish with other email addresses in delegate mode or multiple account. But ok with getOwnEmailAddress()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok done, i changed the name !
lib/features/composer/presentation/extensions/create_email_request_extension.dart
Outdated
Show resolved
Hide resolved
lib/features/identity_creator/presentation/identity_creator_controller.dart
Outdated
Show resolved
Hide resolved
lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
Outdated
Show resolved
Hide resolved
lib/features/composer/presentation/extensions/create_email_request_extension.dart
Outdated
Show resolved
Hide resolved
String getDownloadUrl(String jmapUrl) { | ||
final downloadUrlValid = downloadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a need to change it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because
- the
jmapUrl
attribute cannot be defaulted when using a cyrus server: it is not advertized in the session ; - it was already always provided anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the jmapUrl attribute cannot be defaulted when using a cyrus server: it is not advertized in the session
Not really understand it.
Moreover, we also support other back-end: Stalwart, Cyrus, James. Keep it optional will keep flexible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously the base URL jmapUrl
attribute was optional and the default value was the downloadUrl
provided in the session. For Tmail-backend, this is a full URL (so it's ok), but for Cyrus it is only the relative route:
"downloadUrl": "/jmap/download/{accountId}/{blobId}/{name}?accept={type}",
"uploadUrl": "/jmap/upload/{accountId}/",
but I also made changes to the toQualifiedUrl
method so that both cases are handled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking back about it, you are right that keeping it optional will keep it flexible, it is better.
On my latest commit you can see that I reverted it, but then I had to handle the case where the URL cannot be found at all by throwing an exception, is that ok ?
Uri getUploadUri(AccountId accountId, String jmapUrl) { | ||
final uploadUrlValid = uploadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
idem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also support other back-end: Stalwart, Cyrus, James. Keep it optional will keep flexible.
lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart
Outdated
Show resolved
Hide resolved
lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
Outdated
Show resolved
Hide resolved
lib/features/composer/presentation/extensions/create_email_request_extension.dart
Outdated
Show resolved
Hide resolved
lib/features/manage_account/presentation/menu/settings/settings_first_level_view.dart
Outdated
Show resolved
Hide resolved
@florentos17 Please confirm |
@@ -20,7 +20,7 @@ void main() { | |||
final baseUrl = Uri.parse('https://domain.com'); | |||
final sourceUrl = Uri.parse('/jmap/'); | |||
|
|||
final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap'); | |||
final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need this? In what cases?
IMO, it is our normalization, please keep it in uniform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cyrus advertizes the following in the session: "uploadUrl": "/jmap/upload/{accountId}/"
when querying https://localhost/jmap/upload/bob/
, the upload works, but with https://localhost/jmap/upload/bob
(without the trailing /
), I get a 404 Not Found
error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okie, in this case, make it uniform, keep /
for all qualified url, and update all the tests too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But tmail-backend works the other way around: no /
advertized, no /
needed, and it does not work if the /
is there...
So I feel like the solution is to keep the /
if it is advertized, and not add it if not advertized ? But I agree that it is not uniform.
log('SessionUtils::toQualifiedUrl():baseUrlValid: $baseUrlValid | sourceUrlValid: $sourceUrlValid'); | ||
final qualifiedUrl = baseUrlValid + sourceUrlValid; | ||
log('SessionUtils::toQualifiedUrl():qualifiedUrl: $qualifiedUrl'); | ||
return Uri.parse(qualifiedUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if parse
failed? Can we use tryParse
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any update? @florentos17
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What default behaviour would you expect if the parse fails and null
is returned ?
The methods that call toQualifiedUrl
(getQualifiedApiUrl
, getDownloadUrl
, getUploadUrl
) mostly cannot work with null
, so errors have to be thrown. But then it would be easier to just let toQualifiedUrl
throw the error ?
String getDownloadUrl(String jmapUrl) { | ||
final downloadUrlValid = downloadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the jmapUrl attribute cannot be defaulted when using a cyrus server: it is not advertized in the session
Not really understand it.
Moreover, we also support other back-end: Stalwart, Cyrus, James. Keep it optional will keep flexible.
Uri getUploadUri(AccountId accountId, String jmapUrl) { | ||
final uploadUrlValid = uploadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also support other back-end: Stalwart, Cyrus, James. Keep it optional will keep flexible.
try { | ||
var principalsCapability = getCapabilityProperties<DefaultCapability>( | ||
AccountId(Id(username.value)), | ||
CapabilityIdentifier(Uri.parse('urn:ietf:params:jmap:principals'))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not find it in spec. It is Cyrus customization? Can you share session example of Cyrus here?
@@ -53,6 +52,28 @@ extension SessionExtension on Session { | |||
} | |||
} | |||
|
|||
String? getEmailAddress() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still prefer getUsername()
or getOwnEmailAddress()
?
String wrappedAddress = ((((principalsCapability?.properties | ||
?.values.last) as Map<String, dynamic>) | ||
.values.last) as Map<String, dynamic>) | ||
.values.toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a fan of hard force casting and prefer strong typed for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something like that ?
final sendTo = principalsCapability?.properties?['urn:ietf:params:jmap:calendars']?['sendTo'];
if (sendTo is Map<String, dynamic>) {
final wrappedAddress = sendTo['imip'];
if (wrappedAddress is String && wrappedAddress.startsWith('mailto:')) {
String address = wrappedAddress.substring("mailto:".length);
return address.isEmail ? address : null;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that ok @hoangdat ?
lib/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart
Show resolved
Hide resolved
hi @florentos17, I rebased the branch from customer branch, so some files conflict. Please try to help us resolve it. This ticket is the first priority. please let us know when you are done in resolving conflict and comments. Thanks |
@hoangdat when you forced pushed on master, it "added" 19 commits to this PR: commits that were previously on the master branch but are not anymore since the force push. among these 19 commits:
do you want me to leave them on the current PR so that they are reinstated ? or maybe cherry-pick them back yourself onto master ? |
7bced46
to
99b53ac
Compare
@@ -2755,7 +2755,7 @@ class MailboxDashBoardController extends ReloadableController | |||
dispatchAction(SelectionAllEmailAction()); | |||
} | |||
|
|||
String get baseDownloadUrl => sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl) ?? ''; | |||
String get baseDownloadUrl => sessionCurrent?.getDownloadUrl(dynamicUrlInterceptors.jmapUrl!) ?? ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a big fan of !
without pre-check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, i reverted the change
lib/features/identity_creator/presentation/identity_creator_controller.dart
Outdated
Show resolved
Hide resolved
@@ -530,7 +530,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { | |||
))); | |||
} else { | |||
if (session != null && accountId != null) { | |||
final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); | |||
final baseDownloadUrl = session!.getDownloadUrl(dynamicUrlInterceptors.jmapUrl!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not safety: !
without pre-check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, i reverted the change
.values.last) as Map<String, dynamic>) | ||
.values.toString(); | ||
|
||
String address = wrappedAddress.substring("(mailto:".length, wrappedAddress.length - ")".length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still not find the docs said that this field can use as email address of current account. Can you more details?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i answered on one of your earlier comments: #3457 (comment)
CancelToken? cancelToken, | ||
}) async* { | ||
try { | ||
if (uploadUri == null) throw UnknownUriException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should not put the uploadUri == null
check in this interactor. It is against the interactor's functionality. You should check it before calling this interactor. uploadUri
is required in this interactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I did at first, but it means the interactor is not even called in case of a null
uploadUri
, so there is no UploadAttachmentFailure
, and the red toast notification "upload attachment failed" does not show on the app.
I thought it would be better for the UX to have the red toast notification show up, but I understand the concern. Should I change it to put the check before ? Maybe I can throw a GetUploadUriFailure
earlier to still have the toast notification ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you check uploadUri != null
before calling the interactor. And throw UploadAttachmentFailure(UnknownUriException())
exception as soon as you check it for null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done !
} else if (uploadUrl.hasOrigin) { | ||
uploadUrlValid = uploadUrl; | ||
} else { | ||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return null; | |
throw UnknownUriException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done !
lib/features/identity_creator/presentation/identity_creator_controller.dart
Outdated
Show resolved
Hide resolved
.map((identity) => identity.toEmailAddressNoName()) | ||
.toSet() | ||
.toList(); | ||
|
||
listEmailAddressDefault.add(EmailAddress(null, session?.getOwnEmailAddress())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you should check if listEmailAddressDefault.isEmpty
then add getOwnEmailAddress
. Avoid duplicate emails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it is already good, there is already no duplication.
i can add a listEmailAddressDefault.isEmpty
but it makes no difference
String wrappedAddress = ((((principalsCapability?.properties | ||
?.values.last) as Map<String, dynamic>) | ||
.values.last) as Map<String, dynamic>) | ||
.values.toString(); | ||
|
||
String address = wrappedAddress.substring("(mailto:".length, wrappedAddress.length - ")".length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, Getting the email this way is inefficient, because we don't know exactly which field contains the email, the order of the keys of the returned json will be different on the servers, so truncating the string of the last element is redundant. So we should skip this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the issue is that this is the only place where the full email address is advertized by cyrus: without this, the address wil be unknown.
final sendTo = principalsCapability?.properties?['urn:ietf:params:jmap:calendars']?['sendTo'];
if (sendTo is Map<String, dynamic>) {
final wrappedAddress = sendTo['imip'];
if (wrappedAddress is String && wrappedAddress.startsWith('mailto:')) {
String address = wrappedAddress.substring("mailto:".length);
return address.isEmail ? address : null;
}
}
would that be ok ?
tackling issue 2305
each commit has a self-explanatory message, but do not hesitate if you have any question !
i have tested the changes with both tmail-backend and cyrus to check that the expected behaviour happens, and it looks all good to me.