From c672b35b832046de085053b9f5b9dd68a6e50da3 Mon Sep 17 00:00:00 2001 From: Licaon_Kter Date: Wed, 7 Aug 2024 10:59:04 +0000 Subject: [PATCH 01/14] Quicksy - RO fastlane --- appstore_quicksy_metadata/ro/description.txt | 5 +++++ appstore_quicksy_metadata/ro/keywords.txt | 1 + appstore_quicksy_metadata/ro/marketing_url.txt | 1 + appstore_quicksy_metadata/ro/privacy_url.txt | 1 + appstore_quicksy_metadata/ro/support_url.txt | 1 + 5 files changed, 9 insertions(+) create mode 100644 appstore_quicksy_metadata/ro/description.txt create mode 100644 appstore_quicksy_metadata/ro/keywords.txt create mode 100644 appstore_quicksy_metadata/ro/marketing_url.txt create mode 100644 appstore_quicksy_metadata/ro/privacy_url.txt create mode 100644 appstore_quicksy_metadata/ro/support_url.txt diff --git a/appstore_quicksy_metadata/ro/description.txt b/appstore_quicksy_metadata/ro/description.txt new file mode 100644 index 000000000..f49b9b9c3 --- /dev/null +++ b/appstore_quicksy_metadata/ro/description.txt @@ -0,0 +1,5 @@ +Quicksy este un derivat al popularului client XMPP Monal cu descoperire automată a contactelor. + +Vă înscrieți cu numărul de telefon, iar Quicksy vă va sugera automat, pe baza numerelor de telefon din agenda dvs., posibile contacte. Sub capota Quicksy este un client XMPP complet care vă permite să comunicați cu orice utilizator de pe orice server public federat. De asemenea, utilizatorii de pe Quicksy pot fi contactați din exterior prin simpla adăugare a +numărdetelefon@quicksy.im la lista dvs. de contacte. + +În afară de sincronizarea contactelor, interfața utilizatorului este în mod deliberat cât mai apropiată de Monal. Acest lucru permite utilizatorilor să migreze în cele din urmă de la Quicksy la Monal fără a fi nevoiți să învețe din nou cum funcționează aplicația. \ No newline at end of file diff --git a/appstore_quicksy_metadata/ro/keywords.txt b/appstore_quicksy_metadata/ro/keywords.txt new file mode 100644 index 000000000..db666557f --- /dev/null +++ b/appstore_quicksy_metadata/ro/keywords.txt @@ -0,0 +1 @@ +xmpp, jabber, discutie, mesagerie instantanee, mesagerie, ejabberd, prosody, OMEMO diff --git a/appstore_quicksy_metadata/ro/marketing_url.txt b/appstore_quicksy_metadata/ro/marketing_url.txt new file mode 100644 index 000000000..b53627eba --- /dev/null +++ b/appstore_quicksy_metadata/ro/marketing_url.txt @@ -0,0 +1 @@ +https://quicksy.im/ \ No newline at end of file diff --git a/appstore_quicksy_metadata/ro/privacy_url.txt b/appstore_quicksy_metadata/ro/privacy_url.txt new file mode 100644 index 000000000..bb7d1c0a6 --- /dev/null +++ b/appstore_quicksy_metadata/ro/privacy_url.txt @@ -0,0 +1 @@ +https://quicksy.im/privacy.htm \ No newline at end of file diff --git a/appstore_quicksy_metadata/ro/support_url.txt b/appstore_quicksy_metadata/ro/support_url.txt new file mode 100644 index 000000000..b53627eba --- /dev/null +++ b/appstore_quicksy_metadata/ro/support_url.txt @@ -0,0 +1 @@ +https://quicksy.im/ \ No newline at end of file From 83f16d64234ac6b1944a3ad1ec531a97a13b3990 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Wed, 7 Aug 2024 18:47:28 +0200 Subject: [PATCH 02/14] Improve github workflows and mail2webhook script --- .github/workflows/beta.build-push.yml | 7 ++++++ .github/workflows/publish-quicksy-release.yml | 12 +++++----- .github/workflows/publish-stable-release.yml | 10 ++++---- .github/workflows/quicksy.build-push.yml | 11 +++++++-- .github/workflows/stable.build-push.yml | 7 ++++++ scripts/mail2webhook.py | 24 ++++++++++++++----- scripts/prepare-alpha-certs.sh | 0 7 files changed, 52 insertions(+), 19 deletions(-) mode change 100644 => 100755 scripts/mail2webhook.py mode change 100644 => 100755 scripts/prepare-alpha-certs.sh diff --git a/.github/workflows/beta.build-push.yml b/.github/workflows/beta.build-push.yml index 790700f5f..3ea31874e 100644 --- a/.github/workflows/beta.build-push.yml +++ b/.github/workflows/beta.build-push.yml @@ -39,6 +39,13 @@ jobs: lfs: true - name: Checkout submodules run: git submodule update -f --init --remote + - name: Check for proper semantic versioning + run: | + version="$(git log -n 1 --merges --pretty=format:%s | sed -E 's/^[\t\n ]*([^\n\t ]+)[\t\n ]+\(([^\n\t ]+)\)[\t\n ]*$/\1/g')" + if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then + echo "Invalid semver: '$version'!" + exit 1 + fi - name: Get last build tag and increment it run: | oldBuildNumber=$(git tag --sort="v:refname" | grep "Build_iOS" | grep -v "Quicksy_Build_iOS" | tail -n1 | sed 's/Build_iOS_//g') diff --git a/.github/workflows/publish-quicksy-release.yml b/.github/workflows/publish-quicksy-release.yml index 9b673afe5..5bfaaa0fa 100644 --- a/.github/workflows/publish-quicksy-release.yml +++ b/.github/workflows/publish-quicksy-release.yml @@ -13,18 +13,18 @@ jobs: release-notes: ${{ steps.releasenotes.outputs.notes }} release-notes_ios: ${{ steps.releasenotes.outputs.notes_ios }} # create release only if the ios app made it to the appstore and ignore the macos appstore state - if: github.event.client_payload.Platform == 'iOS' + if: github.event.client_payload.platform == 'iOS' steps: # - run: | - # echo ${{ github.event.client_payload.AppName }} - # echo ${{ github.event.client_payload.Platform }} - # echo ${{ github.event.client_payload.AppVersionNumber }} + # echo ${{ github.event.client_payload.appName }} + # echo ${{ github.event.client_payload.platform }} + # echo ${{ github.event.client_payload.appVersionNumber }} - name: Load release info id: releasenotes run: | - buildNumber="$(fastlane run app_store_build_number api_key_path:"/Users/ci/appstoreconnect/key.json" team_id:"S8D843U34Y" app_identifier:"G7YU7X7KRJ.SworIM" live:false version:"${{ github.event.client_payload.AppVersionNumber }}" 2>&1 | tee /dev/stderr | grep Result | sed -E 's/^.*Result: ([0-9]+).*$/\1/g')" + buildNumber="$(fastlane run app_store_build_number api_key_path:"/Users/ci/appstoreconnect/key.json" team_id:"S8D843U34Y" app_identifier:"G7YU7X7KRJ.SworIM" live:false version:"${{ github.event.client_payload.appVersionNumber }}" 2>&1 | tee /dev/stderr | grep Result | sed -E 's/^.*Result: ([0-9]+).*$/\1/g')" mkdir -p /Users/ci/releases - OUTPUT_FILE="/Users/ci/releases/$buildNumber.output" + OUTPUT_FILE="/Users/ci/quicksy_releases/$buildNumber.output" touch "$OUTPUT_FILE" cat "$OUTPUT_FILE" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/publish-stable-release.yml b/.github/workflows/publish-stable-release.yml index 09abd9ef9..1e4a17872 100644 --- a/.github/workflows/publish-stable-release.yml +++ b/.github/workflows/publish-stable-release.yml @@ -15,16 +15,16 @@ jobs: release-notes_macos: ${{ steps.releasenotes.outputs.notes_macos }} release-id: ${{ steps.releasenotes.outputs.releaseID }} # create release only if the ios app made it to the appstore and ignore the macos appstore state - if: github.event.client_payload.Platform == 'iOS' + if: github.event.client_payload.platform == 'iOS' steps: # - run: | - # echo ${{ github.event.client_payload.AppName }} - # echo ${{ github.event.client_payload.Platform }} - # echo ${{ github.event.client_payload.AppVersionNumber }} + # echo ${{ github.event.client_payload.appName }} + # echo ${{ github.event.client_payload.platform }} + # echo ${{ github.event.client_payload.appVersionNumber }} - name: Load release info id: releasenotes run: | - buildNumber="$(fastlane run app_store_build_number api_key_path:"/Users/ci/appstoreconnect/key.json" team_id:"S8D843U34Y" app_identifier:"G7YU7X7KRJ.SworIM" live:false version:"${{ github.event.client_payload.AppVersionNumber }}" 2>&1 | tee /dev/stderr | grep Result | sed -E 's/^.*Result: ([0-9]+).*$/\1/g')" + buildNumber="$(fastlane run app_store_build_number api_key_path:"/Users/ci/appstoreconnect/key.json" team_id:"S8D843U34Y" app_identifier:"G7YU7X7KRJ.SworIM" live:false version:"${{ github.event.client_payload.appVersionNumber }}" 2>&1 | tee /dev/stderr | grep Result | sed -E 's/^.*Result: ([0-9]+).*$/\1/g')" mkdir -p /Users/ci/releases OUTPUT_FILE="/Users/ci/releases/$buildNumber.output" touch "$OUTPUT_FILE" diff --git a/.github/workflows/quicksy.build-push.yml b/.github/workflows/quicksy.build-push.yml index e723bd6e5..59167208a 100644 --- a/.github/workflows/quicksy.build-push.yml +++ b/.github/workflows/quicksy.build-push.yml @@ -32,6 +32,13 @@ jobs: lfs: true - name: Checkout submodules run: git submodule update -f --init --remote + - name: Check for proper semantic versioning + run: | + version="$(git log -n 1 --merges --pretty=format:%s | sed -E 's/^[\t\n ]*([^\n\t ]+)[\t\n ]+\(([^\n\t ]+)\)[\t\n ]*$/\1/g')" + if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then + echo "Invalid semver: '$version'!" + exit 1 + fi - name: Get last build tag and increment it run: | oldBuildNumber=$(git tag --sort="v:refname" | grep "Quicksy_Build_iOS" | tail -n1 | sed 's/Quicksy_Build_iOS_//g') @@ -62,8 +69,8 @@ jobs: if [ "${{ github.ref }}" != "refs/heads/stable" ]; then version="1.$buildNumber" fi - mkdir -p /Users/ci/releases - OUTPUT_FILE="/Users/ci/releases/$buildNumber.output" + mkdir -p /Users/ci/quicksy_releases + OUTPUT_FILE="/Users/ci/quicksy_releases/$buildNumber.output" touch "$OUTPUT_FILE" echo "OUTPUT_FILE=$OUTPUT_FILE" | tee /dev/stderr >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/stable.build-push.yml b/.github/workflows/stable.build-push.yml index bd29023fb..93cb4603c 100644 --- a/.github/workflows/stable.build-push.yml +++ b/.github/workflows/stable.build-push.yml @@ -35,6 +35,13 @@ jobs: lfs: true - name: Checkout submodules run: git submodule update -f --init --remote + - name: Check for proper semantic versioning + run: | + version="$(git log -n 1 --merges --pretty=format:%s | sed -E 's/^[\t\n ]*([^\n\t ]+)[\t\n ]+\(([^\n\t ]+)\)[\t\n ]*$/\1/g')" + if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then + echo "Invalid semver: '$version'!" + exit 1 + fi - name: Get last build tag and increment it run: | oldBuildNumber=$(git tag --sort="v:refname" | grep "Build_iOS" | grep -v "Quicksy_Build_iOS" | tail -n1 | sed 's/Build_iOS_//g') diff --git a/scripts/mail2webhook.py b/scripts/mail2webhook.py old mode 100644 new mode 100755 index 69c984a58..88623891b --- a/scripts/mail2webhook.py +++ b/scripts/mail2webhook.py @@ -12,7 +12,7 @@ def to_camel_case(text): s = s.split() if len(text) == 0: return text - return s[0] + ''.join(i.capitalize() for i in s[1:]) + return s[0].lower() + ''.join(i.capitalize() for i in s[1:]) # parse commandline parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, description="Simple python script to trigger a github ") @@ -26,7 +26,7 @@ def to_camel_case(text): parser = email.parser.BytesParser() message = parser.parse(sys.stdin.buffer) -subject = message["subject"] +subject = re.sub(r'\s+', ' ', message["subject"]).strip() date = message["date"] # python > 3.9 variant @@ -45,16 +45,28 @@ def to_camel_case(text): else: body = message.get_payload(decode=True) -# transform body in an array of stripped strings +# transform body into an array of stripped strings body = [s.strip() for s in str(body, 'UTF-8').split("\n")] # parse app properties properties = {to_camel_case(k.strip()): v.strip() for k, v in [line.split(": ", 1) for line in body if len(line.split(": ", 1)) > 1]} -# sanity checks -if "The following app has been approved for distribution:" not in body: - print("Wrong state mentioned in mail", file=sys.stderr) +# sanity checks and state extraction +match = re.match(r"^The status of your \((?P.+)\) app, (?P.+), is now \"(?P.+)\"$", subject) +if match == None: + print(f"Mail subject does not contain proper state: '{subject}'", file=sys.stderr) sys.exit(0) +state = {"_"+to_camel_case(k.strip()): v.strip() for k, v in match.groupdict().items()} +state["_state"] = to_camel_case(state["_state"].strip()) +if state["_appName"] != properties["appName"]: + print(f"Mail subject states different app name than properties in mail body: stateAppName='{state['_appName']}', appName='{properties['appName']}'", file=sys.stderr) + sys.exit(0) + +# merge body properties and extracted state +properties = state | {"_datetime": date} | properties +#print(properties) + +# filter everything using the given commandline arguments for entry in args.filter: k, v = entry.split("=", 1) if k not in properties: diff --git a/scripts/prepare-alpha-certs.sh b/scripts/prepare-alpha-certs.sh old mode 100644 new mode 100755 From e1c5c1d77640b4b39d72d567eba9f3cffc6cd631 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Thu, 8 Aug 2024 03:37:57 +0200 Subject: [PATCH 03/14] Fix crash on incoming filetransfers without pending notification --- Monal/Classes/MLNotificationManager.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Monal/Classes/MLNotificationManager.m b/Monal/Classes/MLNotificationManager.m index b1d28917b..86d6586a1 100644 --- a/Monal/Classes/MLNotificationManager.m +++ b/Monal/Classes/MLNotificationManager.m @@ -234,8 +234,6 @@ -(void) handleFiletransferUpdate:(NSNotification*) notification DDLogDebug(@"Already displayed notification '%@', updating it...", idval); [self internalMessageHandlerWithMessage:message andAccount:xmppAccount showAlert:YES andSound:NO andLMCReplaced:NO]; } - else - unreachable(@"Unknown MLNotificationState!", @{@"state": @(state)}); }); } From 563fc688fec13fef2a5f18f9e23cbd0f9804dfca Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 11 Aug 2024 06:53:21 +0200 Subject: [PATCH 04/14] Fix opening of chats when other views are in foreground --- Monal/Classes/AVCallUI.swift | 9 +- Monal/Classes/ActiveChatsViewController.m | 106 ++++++++++------------ 2 files changed, 53 insertions(+), 62 deletions(-) diff --git a/Monal/Classes/AVCallUI.swift b/Monal/Classes/AVCallUI.swift index 76ba379ed..11fb3e181 100644 --- a/Monal/Classes/AVCallUI.swift +++ b/Monal/Classes/AVCallUI.swift @@ -284,9 +284,14 @@ struct AVCallUI: View { VStack { Spacer().frame(height: 8) Button(action: { - self.delegate.dismissWithoutAnimation() if let activeChats = self.appDelegate.obj.activeChats { - activeChats.presentChat(with:self.contact.obj) + //make sure we don't animate anything + activeChats.dismissCompleteViewChain(withAnimation: false) { + activeChats.presentChat(with:self.contact.obj) + } + } else { + //self.delegate.dismissWithoutAnimation() + unreachable("active chats should always be accessible from AVCallUI!") } }, label: { Image(systemName: "text.bubble") diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index d6d98979c..aea4b0328 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -598,22 +598,14 @@ -(void) showAddContactWithJid:(NSString*) jid preauthToken:(NSString* _Nullable) MLContact* checkContact = [MLContact createContactFromJid:jid andAccountNo:checkAccount.accountNo]; if(checkContact.isInRoster) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ - [self presentChatWithContact:checkContact]; - }]; - }); + [self presentChatWithContact:checkContact]; return; } } appendToViewQueue((^(PMKResolver resolve) { UIViewController* addContactMenuView = [[SwiftuiInterface new] makeAddContactViewForJid:jid preauthToken:preauthToken prefillAccount:account andOmemoFingerprints:fingerprints withDismisser:^(MLContact* _Nonnull newContact) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ - [self presentChatWithContact:newContact]; - }]; - }); + [self presentChatWithContact:newContact]; }]; addContactMenuView.ml_disposeCallback = ^{ [self sheetDismissed]; @@ -628,11 +620,7 @@ -(void) showAddContact { appendToViewQueue((^(PMKResolver resolve) { UIViewController* addContactMenuView = [[SwiftuiInterface new] makeAddContactViewWithDismisser:^(MLContact* _Nonnull newContact) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ - [self presentChatWithContact:newContact]; - }]; - }); + [self presentChatWithContact:newContact]; }]; addContactMenuView.ml_disposeCallback = ^{ [self sheetDismissed]; @@ -1014,50 +1002,52 @@ -(void) presentChatWithContact:(MLContact*) contact -(void) presentChatWithContact:(MLContact*) contact andCompletion:(monal_id_block_t _Nullable) completion { DDLogVerbose(@"presenting chat with contact: %@, stacktrace: %@", contact, [NSThread callStackSymbols]); - dispatch_async(dispatch_get_main_queue(), ^{ - // only open contact chat when it is not opened yet (needed for opening via notifications and for macOS) - if([contact isEqualToContact:[MLNotificationManager sharedInstance].currentContact]) - { - // make sure the already open chat is reloaded and return - [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; - if(completion != nil) - completion(@YES); - return; - } - - // clear old chat before opening a new one (but not for splitView == YES) - if(self.splitViewController.collapsed) - [self.navigationController popViewControllerAnimated:NO]; - - // show placeholder if contact is nil, open chat otherwise - if(contact == nil) - { - [self presentSplitPlaceholder]; - if(completion != nil) - completion(@NO); - return; - } + [HelperTools dispatchAsync:YES reentrantOnQueue:dispatch_get_main_queue() withBlock:^{ + [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ + // only open contact chat when it is not opened yet (needed for opening via notifications and for macOS) + if([contact isEqualToContact:[MLNotificationManager sharedInstance].currentContact]) + { + // make sure the already open chat is reloaded and return + [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; + if(completion != nil) + completion(@YES); + return; + } + + // clear old chat before opening a new one (but not for splitView == YES) + if(self.splitViewController.collapsed) + [self.navigationController popViewControllerAnimated:NO]; + + // show placeholder if contact is nil, open chat otherwise + if(contact == nil) + { + [self presentSplitPlaceholder]; + if(completion != nil) + completion(@NO); + return; + } - //open chat (make sure we have an active buddy for it and add it to our ui, if needed) - //but don't animate this if the contact is already present in our list - [[DataLayer sharedInstance] addActiveBuddies:contact.contactJid forAccount:contact.accountId]; - if([[self getChatArrayForSection:pinnedChats] containsObject:contact] || [[self getChatArrayForSection:unpinnedChats] containsObject:contact]) - { - [self scrollToContact:contact]; - [self performSegueWithIdentifier:@"showConversation" sender:contact]; - if(completion != nil) - completion(@YES); - } - else - { - [self insertOrMoveContact:contact completion:^(BOOL finished __unused) { + //open chat (make sure we have an active buddy for it and add it to our ui, if needed) + //but don't animate this if the contact is already present in our list + [[DataLayer sharedInstance] addActiveBuddies:contact.contactJid forAccount:contact.accountId]; + if([[self getChatArrayForSection:pinnedChats] containsObject:contact] || [[self getChatArrayForSection:unpinnedChats] containsObject:contact]) + { [self scrollToContact:contact]; [self performSegueWithIdentifier:@"showConversation" sender:contact]; if(completion != nil) completion(@YES); - }]; - } - }); + } + else + { + [self insertOrMoveContact:contact completion:^(BOOL finished __unused) { + [self scrollToContact:contact]; + [self performSegueWithIdentifier:@"showConversation" sender:contact]; + if(completion != nil) + completion(@YES); + }]; + } + }]; + }]; } /* @@ -1119,12 +1109,8 @@ -(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender UINavigationController* nav = segue.destinationViewController; ContactsViewController* contacts = (ContactsViewController*)nav.topViewController; contacts.selectContact = ^(MLContact* selectedContact) { - dispatch_async(dispatch_get_main_queue(), ^{ - DDLogVerbose(@"Got selected contact from contactlist ui: %@", selectedContact); - [self dismissCompleteViewChainWithAnimation:YES andCompletion:^{ - [self presentChatWithContact:selectedContact]; - }]; - }); + DDLogVerbose(@"Got selected contact from contactlist ui: %@", selectedContact); + [self presentChatWithContact:selectedContact]; }; } } From 31a90e58fd356e7f9dfe9c8374e9fa25736bf044 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 10:56:48 +0200 Subject: [PATCH 05/14] Improve SSDP handling Alert about ongoing MITM before sending out client-final-message to finish authentication. --- Monal/Classes/SCRAM.h | 4 +++- Monal/Classes/SCRAM.m | 11 +++++++++-- Monal/Classes/xmpp.m | 42 +++++++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Monal/Classes/SCRAM.h b/Monal/Classes/SCRAM.h index e2732daf3..a4fce0936 100644 --- a/Monal/Classes/SCRAM.h +++ b/Monal/Classes/SCRAM.h @@ -12,15 +12,16 @@ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, MLScramStatus) { - MLScramStatusOK, //server-first-message MLScramStatusNonceError, MLScramStatusUnsupportedMAttribute, MLScramStatusSSDPTriggered, MLScramStatusIterationCountInsecure, + MLScramStatusServerFirstOK, //server-final-message MLScramStatusWrongServerProof, MLScramStatusServerError, + MLScramStatusServerFinalOK, }; @interface SCRAM : NSObject @@ -35,6 +36,7 @@ typedef NS_ENUM(NSUInteger, MLScramStatus) { -(NSData*) hashPasswordWithSalt:(NSData*) salt andIterationCount:(uint32_t) iterationCount; @property (nonatomic, readonly) NSString* method; +@property (nonatomic, readonly) BOOL serverFirstMessageParsed; @property (nonatomic, readonly) BOOL finishedSuccessfully; @property (nonatomic, readonly) BOOL ssdpSupported; diff --git a/Monal/Classes/SCRAM.m b/Monal/Classes/SCRAM.m index e83eeb80a..79dbf7755 100644 --- a/Monal/Classes/SCRAM.m +++ b/Monal/Classes/SCRAM.m @@ -58,6 +58,7 @@ -(instancetype) initWithUsername:(NSString*) username password:(NSString*) passw _password = password; _nonce = [NSUUID UUID].UUIDString; _ssdpString = nil; + _serverFirstMessageParsed = NO; _finishedSuccessfully = NO; return self; } @@ -65,6 +66,7 @@ -(instancetype) initWithUsername:(NSString*) username password:(NSString*) passw -(void) setSSDPMechanisms:(NSArray*) mechanisms andChannelBindingTypes:(NSArray* _Nullable) cbTypes { MLAssert(!_finishedSuccessfully, @"SCRAM handler finished already!"); + MLAssert(!_serverFirstMessageParsed, @"SCRAM handler already parsed server-first-message!"); DDLogVerbose(@"Creating SDDP string: %@\n%@", mechanisms, cbTypes); NSMutableString* ssdpString = [NSMutableString new]; [ssdpString appendString:[[mechanisms sortedArrayUsingSelector:@selector(compare:)] componentsJoinedByString:@","]]; @@ -80,6 +82,7 @@ -(void) setSSDPMechanisms:(NSArray*) mechanisms andChannelBindingType -(NSString*) clientFirstMessageWithChannelBinding:(NSString* _Nullable) channelBindingType { MLAssert(!_finishedSuccessfully, @"SCRAM handler finished already!"); + MLAssert(!_serverFirstMessageParsed, @"SCRAM handler already parsed server-first-message!"); if(channelBindingType == nil) _gssHeader = @"n,,"; //not supported by us else if(!_usingChannelBinding) @@ -94,7 +97,9 @@ -(NSString*) clientFirstMessageWithChannelBinding:(NSString* _Nullable) channelB -(MLScramStatus) parseServerFirstMessage:(NSString*) str { MLAssert(!_finishedSuccessfully, @"SCRAM handler finished already!"); + MLAssert(!_serverFirstMessageParsed, @"SCRAM handler already parsed server-first-message!"); NSDictionary* msg = [self parseScramString:str]; + _serverFirstMessageParsed = YES; //server nonce MUST start with our client nonce if(![msg[@"r"] hasPrefix:_nonce]) return MLScramStatusNonceError; @@ -117,13 +122,14 @@ -(MLScramStatus) parseServerFirstMessage:(NSString*) str } if(_iterationCount < 4096) return MLScramStatusIterationCountInsecure; - return MLScramStatusOK; + return MLScramStatusServerFirstOK; } //see https://stackoverflow.com/a/29299946/3528174 -(NSString*) clientFinalMessageWithChannelBindingData:(NSData* _Nullable) channelBindingData { MLAssert(!_finishedSuccessfully, @"SCRAM handler finished already!"); + MLAssert(_serverFirstMessageParsed, @"SCRAM handler did not parsed server-first-message yet!"); //calculate gss header with optional channel binding data NSMutableData* gssHeaderWithChannelBindingData = [NSMutableData new]; [gssHeaderWithChannelBindingData appendData:[_gssHeader dataUsingEncoding:NSUTF8StringEncoding]]; @@ -162,6 +168,7 @@ -(NSString*) clientFinalMessageWithChannelBindingData:(NSData* _Nullable) channe -(MLScramStatus) parseServerFinalMessage:(NSString*) str { MLAssert(!_finishedSuccessfully, @"SCRAM handler finished already!"); + MLAssert(_serverFirstMessageParsed, @"SCRAM handler did not parsed server-first-message yet!"); NSDictionary* msg = [self parseScramString:str]; //wrong v-value if(![HelperTools constantTimeCompareAttackerString:msg[@"v"] withKnownString:_expectedServerSignature]) @@ -174,7 +181,7 @@ -(MLScramStatus) parseServerFinalMessage:(NSString*) str } //everything was successful _finishedSuccessfully = YES; - return MLScramStatusOK; + return MLScramStatusServerFinalOK; } -(NSData*) hashPasswordWithSalt:(NSData*) salt andIterationCount:(uint32_t) iterationCount diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 79f930b92..97e1e8341 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -2505,9 +2505,24 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR case MLScramStatusNonceError: deactivate_account = NO; message = NSLocalizedString(@"Error handling SASL challenge of server (nonce error), disconnecting!", @"parenthesis should be verbatim"); break; case MLScramStatusUnsupportedMAttribute: deactivate_account = NO; message = NSLocalizedString(@"Error handling SASL challenge of server (m-attr error), disconnecting!", @"parenthesis should be verbatim"); break; case MLScramStatusIterationCountInsecure: deactivate_account = NO; message = NSLocalizedString(@"Error handling SASL challenge of server (iteration count too low), disconnecting!", @"parenthesis should be verbatim"); break; - case MLScramStatusOK: deactivate_account = NO; message = nil; break; //everything is okay + case MLScramStatusServerFirstOK: deactivate_account = NO; message = nil; break; //everything is okay default: unreachable(@"wrong status for scram message!"); break; } + + //check for incomplete XEP-0440 support (not implementing mandatory tls-server-end-point channel-binding) not mitigated by SSDP + //(we allow either support for tls-server-end-point or SSDP signed non-support) + if([kServerDoesNotFollowXep0440Error isEqualToString:[self channelBindingToUse]]) + { + MLXMLNode* streamError = [[MLXMLNode alloc] initWithElement:@"stream:error" withAttributes:@{@"type": @"cancel"} andChildren:@[ + [[MLXMLNode alloc] initWithElement:@"undefined-condition" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[] andData:nil], + [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[] andData:kServerDoesNotFollowXep0440Error], + ] andData:nil]; + [self disconnectWithStreamError:streamError andExplicitLogout:YES]; + + //make sure this error is reported, even if there are other SRV records left (we disconnect here and won't try again) + [HelperTools postError:NSLocalizedString(@"Either this is a man-in-the-middle attack OR your server neither implements XEP-0474 nor does it fully implement XEP-0440 which mandates support for tls-server-end-point channel-binding. In either case you should inform your server admin! Account disabled now.", @"") withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; + } + if(message != nil) { DDLogError(@"SCRAM says this server-first message was wrong!"); @@ -2654,20 +2669,6 @@ -(void) processInput:(MLXMLNode*) parsedStanza withDelayedReplay:(BOOL) delayedR //record TLS version self.connectionProperties.tlsVersion = [((MLStream*)self->_oStream) isTLS13] ? @"1.3" : @"1.2"; - //check for incomplete XEP-0440 support (not implementing mandatory tls-server-end-point channel-binding) not mitigated by SSDP - //(we allow either support for tls-server-end-point or SSDP signed non-support) - if([kServerDoesNotFollowXep0440Error isEqualToString:[self channelBindingToUse]]) - { - MLXMLNode* streamError = [[MLXMLNode alloc] initWithElement:@"stream:error" withAttributes:@{@"type": @"cancel"} andChildren:@[ - [[MLXMLNode alloc] initWithElement:@"undefined-condition" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[] andData:nil], - [[MLXMLNode alloc] initWithElement:@"text" andNamespace:@"urn:ietf:params:xml:ns:xmpp-streams" withAttributes:@{} andChildren:@[] andData:kServerDoesNotFollowXep0440Error], - ] andData:nil]; - [self disconnectWithStreamError:streamError andExplicitLogout:YES]; - - //make sure this error is reported, even if there are other SRV records left (we disconnect here and won't try again) - [HelperTools postError:NSLocalizedString(@"Either this is a man-in-the-middle attack OR your server neither implements XEP-0474 nor does it fully implement XEP-0440 which mandates support for tls-server-end-point channel-binding. In either case you should inform your server admin! Account disabled now.", @"") withNode:nil andAccount:self andIsSevere:YES andDisableAccount:YES]; - } - self->_scramHandler = nil; self->_blockToCallOnTCPOpen = nil; //just to be sure but not strictly necessary self->_accountState = kStateLoggedIn; @@ -2951,7 +2952,10 @@ -(void) handleFeaturesBeforeAuth:(MLXMLNode*) parsedStanza withForceSasl2:(BOOL) //_supportedSaslMechanisms==nil indicates SASL1 support only else if([self->_supportedSaslMechanisms containsObject:@"PLAIN"] || self->_supportedSaslMechanisms == nil) { - clearPipelineCacheOrReportSevereError(NSLocalizedString(@"This server isn't additionally hardened against man-in-the-middle attacks on the TLS encryption layer by using authentication methods that are secure against such attacks! This indicates an ongoing attack if the server is supposed to support SASL2 and SCRAM and is harmless otherwise. Use the advanced account creation menu and turn on the PLAIN switch there if you still want to log in to this server.", @"")); + //leave that in for translators, we might use it at a later time + while(!NSLocalizedString(@"This server isn't additionally hardened against man-in-the-middle attacks on the TLS encryption layer by using authentication methods that are secure against such attacks! This indicates an ongoing attack if the server is supposed to support SASL2 and SCRAM and is harmless otherwise. Use the advanced account creation menu and turn on the PLAIN switch there if you still want to log in to this server.", @"")); + + clearPipelineCacheOrReportSevereError(NSLocalizedString(@"This server lacks support for SASL2 and SCRAM, additionally hardening authentication against man-in-the-middle attacks on the TLS encryption layer. Since this server is listed as supporting both at https://github.com/monal-im/SCRAM_PreloadList, an ongoing MITM attack is highly likely! You should try again once you are in a clean networking environment.", @"")); return; } } @@ -3180,7 +3184,7 @@ -(void) handleScramInSuccessOrContinue:(MLXMLNode*) parsedStanza switch([self->_scramHandler parseServerFinalMessage:innerSASLData]) { case MLScramStatusWrongServerProof: deactivate_account = YES; message = NSLocalizedString(@"SCRAM server proof wrong, ongoing MITM attack highly likely, aborting authentication and disabling account to limit damage. You should try to reenable your account once you are in a clean networking environment again.", @""); break; case MLScramStatusServerError: deactivate_account = NO; message = NSLocalizedString(@"Unexpected error authenticating server using SASL2 (does your server have a bug?), disconnecting!", @""); break; - case MLScramStatusOK: deactivate_account = NO; message = nil; break; //everything is okay + case MLScramStatusServerFinalOK: deactivate_account = NO; message = nil; break; //everything is okay default: unreachable(@"wrong status for scram message!"); break; } @@ -3221,12 +3225,12 @@ -(NSString* _Nullable) channelBindingToUse //if our scram handshake is not finished yet and no mutually supported channel-binding can be found --> ignore that for now (see below) //if our scram handshake finished without negotiating a mutually supported channel-binding and this was not backed by SSDP --> report error - if(self->_scramHandler.finishedSuccessfully && !self->_scramHandler.ssdpSupported) + if(self->_scramHandler.serverFirstMessageParsed && !self->_scramHandler.ssdpSupported) { DDLogWarn(@"Could not find any supported channel-binding type, this MUST be a mitm attack, because tls-server-end-point is mandatory via XEP-0440!"); return kServerDoesNotFollowXep0440Error; //this will trigger a disconnect } - if(!self->_scramHandler.finishedSuccessfully) + if(!self->_scramHandler.serverFirstMessageParsed) DDLogWarn(@"Could not find any supported channel-binding type, this COULD be a mitm attack (check via XEP-0474 pending)!"); return nil; } From f4d8e06d33dac7b21de80c31e55a6e043216b4da Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 11:26:07 +0200 Subject: [PATCH 06/14] Fix crash on macOS when removing account or clearing history --- Monal/Classes/XMPPEdit.m | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Monal/Classes/XMPPEdit.m b/Monal/Classes/XMPPEdit.m index 92b0b585e..9f7c66940 100644 --- a/Monal/Classes/XMPPEdit.m +++ b/Monal/Classes/XMPPEdit.m @@ -480,11 +480,8 @@ - (IBAction) removeAccountClicked: (id) sender [questionAlert addAction:yesAction]; UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; - if(@available(iOS 16.0, macCatalyst 16.0, *)) - popPresenter.sourceItem = sender; - else - popPresenter.barButtonItem = sender; - + popPresenter.sourceView = self.view; + [self presentViewController:questionAlert animated:YES completion:nil]; } @@ -542,11 +539,8 @@ -(IBAction) deleteAccountClicked:(id) sender [questionAlert addAction:yesAction]; UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; - if(@available(iOS 16.0, macCatalyst 16.0, *)) - popPresenter.sourceItem = sender; - else - popPresenter.barButtonItem = sender; - + popPresenter.sourceView = self.view; + [self presentViewController:questionAlert animated:YES completion:nil]; } @@ -577,11 +571,8 @@ - (IBAction) clearHistoryClicked: (id) sender [questionAlert addAction:yesAction]; UIPopoverPresentationController* popPresenter = [questionAlert popoverPresentationController]; - if(@available(iOS 16.0, macCatalyst 16.0, *)) - popPresenter.sourceItem = sender; - else - popPresenter.barButtonItem = sender; - + popPresenter.sourceView = self.view; + [self presentViewController:questionAlert animated:YES completion:nil]; } From 1cf132a5c9c01407c1c04132f49e7b131ca0c339 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 14:40:17 +0200 Subject: [PATCH 07/14] Fix semver check in quicksy workflow --- .github/workflows/quicksy.build-push.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/quicksy.build-push.yml b/.github/workflows/quicksy.build-push.yml index 59167208a..80301f89c 100644 --- a/.github/workflows/quicksy.build-push.yml +++ b/.github/workflows/quicksy.build-push.yml @@ -34,7 +34,12 @@ jobs: run: git submodule update -f --init --remote - name: Check for proper semantic versioning run: | + buildNumber="$(git tag --sort="v:refname" | grep "Quicksy_Build_iOS" | tail -n1 | sed 's/Quicksy_Build_iOS_//g')" version="$(git log -n 1 --merges --pretty=format:%s | sed -E 's/^[\t\n ]*([^\n\t ]+)[\t\n ]+\(([^\n\t ]+)\)[\t\n ]*$/\1/g')" + if [ "${{ github.ref }}" != "refs/heads/stable" ]; then + version="1.$buildNumber.0" + fi + if ! [[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$ ]]; then echo "Invalid semver: '$version'!" exit 1 From 3e2c1b8195a7ce2eb58dddb05b4daf9d49d3e24c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 16:36:35 +0200 Subject: [PATCH 08/14] Fix omemo devicelist fetch handling --- Monal/Classes/MLOMEMO.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 8c60dc3a0..35cb9a9f2 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -338,6 +338,7 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe { //fetch newest devicelist (this is needed even after a subscribe on at least prosody) [self.account.pubsub fetchNode:@"eu.siacs.conversations.axolotl.devicelist" from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleDevicelistFetch, handleDevicelistFetchInvalidation, $BOOL(subscribe))]; + [self.state.openDevicelistFetches addObject:jid]; } } From 7bbf42c957b67432ca9b8fbbc30d33f5886bd71c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 16:37:36 +0200 Subject: [PATCH 09/14] Add new kMonalOmemoFetchingStateUpdate notification --- Monal/Classes/MLConstants.h | 1 + Monal/Classes/MLOMEMO.m | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLConstants.h b/Monal/Classes/MLConstants.h index f5532f429..864be98d5 100644 --- a/Monal/Classes/MLConstants.h +++ b/Monal/Classes/MLConstants.h @@ -172,6 +172,7 @@ static inline NSString* _Nonnull LocalizationNotNeeded(NSString* _Nonnull s) #define kMonalFinishedOmemoBundleFetch @"kMonalFinishedOmemoBundleFetch" #define kMonalOmemoStateUpdated @"kMonalOmemoStateUpdated" #define kMonalUpdateBundleFetchStatus @"kMonalUpdateBundleFetchStatus" +#define kMonalOmemoFetchingStateUpdate @"kMonalOmemoFetchingStateUpdate" #define kMonalIdle @"kMonalIdle" #define kMonalFiletransfersIdle @"kMonalFiletransfersIdle" #define kMonalNotIdle @"kMonalNotIdle" diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 35cb9a9f2..450399c99 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -339,12 +339,16 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe //fetch newest devicelist (this is needed even after a subscribe on at least prosody) [self.account.pubsub fetchNode:@"eu.siacs.conversations.axolotl.devicelist" from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleDevicelistFetch, handleDevicelistFetchInvalidation, $BOOL(subscribe))]; [self.state.openDevicelistFetches addObject:jid]; + + [self sendFetchUpdateNotificationForJid:jid]; } } $$instance_handler(handleDevicelistSubscribeInvalidation, account.omemo, $$ID(xmpp*, account), $$ID(NSString*, jid)) //mark devicelist subscription as done [self.state.openDevicelistSubscriptions removeObject:jid]; + + [self sendFetchUpdateNotificationForJid:jid]; $$ $$instance_handler(handleDevicelistSubscribe, account.omemo, $$ID(xmpp*, account), $$ID(NSString*, jid), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason)) @@ -352,12 +356,14 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe if(success == NO) { + // TODO: improve error handling if(errorIq) DDLogError(@"Error while subscribe to omemo deviceslist from: %@ - %@", jid, errorIq); else DDLogError(@"Error while subscribe to omemo deviceslist from: %@ - %@", jid, errorReason); } - // TODO: improve error handling + + [self sendFetchUpdateNotificationForJid:jid]; $$ $$instance_handler(handleDevicelistFetchInvalidation, account.omemo, $$ID(xmpp*, account), $$ID(NSString*, jid)) @@ -371,6 +377,8 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe //retrigger queued key transport elements for this jid (if any) [self retriggerKeyTransportElementsForJid:jid]; + + [self sendFetchUpdateNotificationForJid:jid]; $$ $$instance_handler(handleDevicelistFetch, account.omemo, $$ID(xmpp*, account), $$BOOL(subscribe), $$ID(NSString*, jid), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $_ID((NSDictionary*), data)) @@ -424,6 +432,8 @@ -(void) queryOMEMODevices:(NSString*) jid withSubscribe:(BOOL) subscribe [self repairQueuedSessions]; //now try to repair all broken sessions (our catchup is now really done) else [self retriggerKeyTransportElementsForJid:jid]; //retrigger queued key transport elements for this jid (if any) + + [self sendFetchUpdateNotificationForJid:jid]; $$ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString*) source @@ -572,6 +582,7 @@ -(void) queryOMEMOBundleFrom:(NSString*) jid andDevice:(NSNumber*) deviceid self.state.openBundleFetches[jid] = [NSMutableSet new]; [self.state.openBundleFetches[jid] addObject:deviceid]; + [self sendFetchUpdateNotificationForJid:jid]; } //don't mark any devices as deleted in this invalidation handler (like we do for an error in the normal handler below), @@ -589,6 +600,8 @@ -(void) queryOMEMOBundleFrom:(NSString*) jid andDevice:(NSNumber*) deviceid //retrigger queued key transport elements for this jid (if any) [self retriggerKeyTransportElementsForJid:jid]; + + [self sendFetchUpdateNotificationForJid:jid]; $$ $$instance_handler(handleBundleFetchResult, account.omemo, $$ID(xmpp*, account), $$ID(NSString*, jid), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $_ID((NSDictionary*), data), $$ID(NSNumber*, rid)) @@ -640,6 +653,8 @@ -(void) queryOMEMOBundleFrom:(NSString*) jid andDevice:(NSNumber*) deviceid //retrigger queued key transport elements for this jid (if any) [self retriggerKeyTransportElementsForJid:jid]; + + [self sendFetchUpdateNotificationForJid:jid]; $$ -(void) handleBundleWithInvalidEntryForJid:(NSString*) jid andRid:(NSNumber*) rid @@ -1306,10 +1321,14 @@ -(void) subscribeAndFetchDevicelistIfNoSessionExistsForJid:(NSString*) buddyJid { if([self.monalSignalStore sessionsExistForBuddy:buddyJid] == NO) { + DDLogVerbose(@"No omemo session for %@", buddyJid); MLContact* contact = [MLContact createContactFromJid:buddyJid andAccountNo:self.account.accountNo]; //only do so if we don't receive automatic headline pushes of the devicelist if(!contact.isSubscribedTo) + { + DDLogVerbose(@"Fetching devicelist with subscribe from contact: %@", contact); [self queryOMEMODevices:buddyJid withSubscribe:YES]; + } } } @@ -1322,10 +1341,11 @@ -(void) checkIfSessionIsStillNeeded:(NSString*) buddyJid isMuc:(BOOL) isMuc else if([self.monalSignalStore checkIfSessionIsStillNeeded:buddyJid] == NO) [danglingJids addObject:buddyJid]; - [self notifyKnownDevicesUpdated:buddyJid]; DDLogVerbose(@"Unsubscribing from dangling jids: %@", danglingJids); for(NSString* jid in danglingJids) [self.account.pubsub unsubscribeFromNode:@"eu.siacs.conversations.axolotl.devicelist" forJid:jid withHandler:$newHandler(self, handleDevicelistUnsubscribe)]; + + [self notifyKnownDevicesUpdated:buddyJid]; } //interfaces for UI @@ -1403,6 +1423,18 @@ -(void) clearAllSessionsForJid:(NSString*) jid [self.account.pubsub fetchNode:@"eu.siacs.conversations.axolotl.devicelist" from:jid withItemsList:nil andHandler:$newHandlerWithInvalidation(self, handleDevicelistFetch, handleDevicelistFetchInvalidation, $BOOL(subscribe, NO))]; } +-(void) sendFetchUpdateNotificationForJid:(NSString*) jid +{ + BOOL isFetching = self.state.openBundleFetches[jid] != nil || [self.state.openDevicelistFetches containsObject:jid] || [self.state.openDevicelistSubscriptions containsObject:jid]; + [[MLNotificationQueue currentQueue] postNotificationName:kMonalOmemoFetchingStateUpdate object:self.account userInfo:@{ + @"jid": jid, + @"isFetching": @(isFetching), + @"fetchingBundle": @(self.state.openBundleFetches[jid] != nil), + @"fetchingDevicelist": @([self.state.openDevicelistFetches containsObject:jid]), + @"subscribingDevicelist": @([self.state.openDevicelistSubscriptions containsObject:jid]), + }]; +} + @end NS_ASSUME_NONNULL_END From 22b25845ec8adad946f867596ae8aa48b5898ce9 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 16:41:56 +0200 Subject: [PATCH 10/14] Show HUD while loading omemo keys the first time When opening a new chat and not having any omemo keys/devices for this contact, we try to fetch the omemo devicelist+bundles while showing a progress HUD. If we do not succeed fetching those, we display a warning to the user and disable encryption. --- Monal/Classes/MLChatViewHelper.m | 2 +- Monal/Classes/MLContact.m | 1 + Monal/Classes/MLOMEMO.m | 14 +-- Monal/Classes/chatViewController.m | 164 +++++++++++++++++++---------- 4 files changed, 121 insertions(+), 60 deletions(-) diff --git a/Monal/Classes/MLChatViewHelper.m b/Monal/Classes/MLChatViewHelper.m index e8738e41b..b9cea10f1 100644 --- a/Monal/Classes/MLChatViewHelper.m +++ b/Monal/Classes/MLChatViewHelper.m @@ -20,7 +20,7 @@ +(void) toggleEncryptionForContact:(MLContact*) contact withSelf:(id) andSelf af if(![contact toggleEncryption:!contact.isEncrypted]) { // Show a warning when no device keys could be found and the user tries to enable encryption -> encryption is not possible - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Encryption Not Supported", @"") message:NSLocalizedString(@"This contact does not appear to have any devices that support encryption.", @"") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Encryption Not Supported", @"") message:NSLocalizedString(@"This contact does not appear to have any devices that support encryption, please try again later if you think this is wrong.", @"") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction* action __unused) { [alert dismissViewControllerAnimated:YES completion:nil]; }]]; diff --git a/Monal/Classes/MLContact.m b/Monal/Classes/MLContact.m index 5532729a7..5e55a1058 100644 --- a/Monal/Classes/MLContact.m +++ b/Monal/Classes/MLContact.m @@ -620,6 +620,7 @@ -(BOOL) toggleEncryption:(BOOL) encrypt if(self.isGroup == NO) { NSSet* knownDevices = [account.omemo knownDevicesForAddressName:self.contactJid]; + DDLogVerbose(@"Current isEncrypted=%@, encrypt=%@, knownDevices=%@", bool2str(self.isEncrypted), bool2str(encrypt), knownDevices); if(!self.isEncrypted && encrypt && knownDevices.count == 0) { // request devicelist again diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 450399c99..cf127794b 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -1323,12 +1323,14 @@ -(void) subscribeAndFetchDevicelistIfNoSessionExistsForJid:(NSString*) buddyJid { DDLogVerbose(@"No omemo session for %@", buddyJid); MLContact* contact = [MLContact createContactFromJid:buddyJid andAccountNo:self.account.accountNo]; - //only do so if we don't receive automatic headline pushes of the devicelist - if(!contact.isSubscribedTo) - { - DDLogVerbose(@"Fetching devicelist with subscribe from contact: %@", contact); - [self queryOMEMODevices:buddyJid withSubscribe:YES]; - } + //only subscribe if we don't receive automatic headline pushes of the devicelist + DDLogVerbose(@"Fetching devicelist %@ from contact: %@", !contact.isSubscribedTo ? @"with subscribe" : @"without subscribe", contact); + [self queryOMEMODevices:buddyJid withSubscribe:!contact.isSubscribedTo]; + } + else + { + //make sure we don't show the omemo key fetching hud forever + [self sendFetchUpdateNotificationForJid:buddyJid]; } } diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 615add3ff..d5fc278cd 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -66,6 +66,7 @@ @interface chatViewController()* messageList; @@ -781,7 +782,7 @@ -(void) viewWillAppear:(BOOL)animated [nc addObserver:self selector:@selector(handleDeletedMessage:) name:kMonalDeletedMessageNotice object:nil]; [nc addObserver:self selector:@selector(handleSentMessage:) name:kMonalSentMessageNotice object:nil]; [nc addObserver:self selector:@selector(handleMessageError:) name:kMonalMessageErrorNotice object:nil]; - + [nc addObserver:self selector:@selector(handleOmemoFetchStateUpdate:) name:kMonalOmemoFetchingStateUpdate object:nil]; [nc addObserver:self selector:@selector(dismissKeyboard:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [nc addObserver:self selector:@selector(handleForeGround) name:kMonalRefresh object:nil]; @@ -847,58 +848,8 @@ -(void) viewWillAppear:(BOOL)animated -(void) viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; -#ifndef DISABLE_OMEMO - if(self.xmppAccount && [[DataLayer sharedInstance] isAccountEnabled:self.xmppAccount.accountNo]) - { - BOOL omemoDeviceForContactFound = NO; - if(!self.contact.isGroup) - omemoDeviceForContactFound = [self.xmppAccount.omemo knownDevicesForAddressName:self.contact.contactJid].count > 0; - else - { - omemoDeviceForContactFound = NO; - for(NSDictionary* participant in [[DataLayer sharedInstance] getMembersAndParticipantsOfMuc:self.contact.contactJid forAccountId:self.xmppAccount.accountNo]) - { - if(participant[@"participant_jid"]) - omemoDeviceForContactFound |= [self.xmppAccount.omemo knownDevicesForAddressName:participant[@"participant_jid"]].count > 0; - else if(participant[@"member_jid"]) - omemoDeviceForContactFound |= [self.xmppAccount.omemo knownDevicesForAddressName:participant[@"member_jid"]].count > 0; - if(omemoDeviceForContactFound) - break; - } - } - if(!omemoDeviceForContactFound && self.contact.isEncrypted) - { - if(!self.contact.isGroup && [[HelperTools splitJid:self.contact.contactJid][@"host"] isEqualToString:@"cheogram.com"]) - { - // cheogram.com does not support OMEMO encryption as it is a PSTN gateway - // --> disable it - self.contact.isEncrypted = NO; - [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; - } - else if(self.contact.isGroup && ![self.contact.mucType isEqualToString:@"group"]) - { - // a channel type muc has OMEMO encryption enabled, but channels don't support encryption - // --> disable it - self.contact.isEncrypted = NO; - [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; - } - else if(!self.contact.isGroup || (self.contact.isGroup && [self.contact.mucType isEqualToString:@"group"])) - { - // a 1:1 contact or a group type muc has OMEMO encryption enabled - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"No OMEMO keys found", @"") message:NSLocalizedString(@"This contact may not support OMEMO encrypted messages. Please try to enable encryption again in a few seconds, if you think this is wrong.", @"") preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Disable Encryption", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - // Disable encryption - self.contact.isEncrypted = NO; - [self updateUIElements]; - [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; - [alert dismissViewControllerAnimated:YES completion:nil]; - }]]; - - [self presentViewController:alert animated:YES completion:nil]; - } - } - } -#endif + [self checkOmemoSupportWithAlert:NO]; + [self refreshCounter]; //init the floating last message button @@ -3196,6 +3147,113 @@ -(void) stopEditing self.editingCallback(nil); //dismiss swipe action } +-(void) checkOmemoSupportWithAlert:(BOOL) showWarning +{ +#ifndef DISABLE_OMEMO + if(self.xmppAccount && [[DataLayer sharedInstance] isAccountEnabled:self.xmppAccount.accountNo]) + { + BOOL omemoDeviceForContactFound = NO; + if(!self.contact.isGroup) + omemoDeviceForContactFound = [self.xmppAccount.omemo knownDevicesForAddressName:self.contact.contactJid].count > 0; + else + { + omemoDeviceForContactFound = NO; + for(NSDictionary* participant in [[DataLayer sharedInstance] getMembersAndParticipantsOfMuc:self.contact.contactJid forAccountId:self.xmppAccount.accountNo]) + { + if(participant[@"participant_jid"]) + omemoDeviceForContactFound |= [self.xmppAccount.omemo knownDevicesForAddressName:participant[@"participant_jid"]].count > 0; + else if(participant[@"member_jid"]) + omemoDeviceForContactFound |= [self.xmppAccount.omemo knownDevicesForAddressName:participant[@"member_jid"]].count > 0; + if(omemoDeviceForContactFound) + break; + } + } + if(!omemoDeviceForContactFound && self.contact.isEncrypted) + { + if(!self.contact.isGroup && [[HelperTools splitJid:self.contact.contactJid][@"host"] isEqualToString:@"cheogram.com"]) + { + // cheogram.com does not support OMEMO encryption as it is a PSTN gateway + // --> disable it + self.contact.isEncrypted = NO; + [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; + } + else if(self.contact.isGroup && ![self.contact.mucType isEqualToString:@"group"]) + { + // a channel type muc has OMEMO encryption enabled, but channels don't support encryption + // --> disable it + self.contact.isEncrypted = NO; + [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; + } + else if(!self.contact.isGroup || (self.contact.isGroup && [self.contact.mucType isEqualToString:@"group"])) + { + [self hideOmemoHUD]; + if(showWarning) + { + DDLogWarn(@"Showing omemo not supported alert for: %@", self.contact); + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"No OMEMO keys found", @"") message:NSLocalizedString(@"This contact may not support OMEMO encrypted messages. Please try to enable encryption again in a few seconds, if you think this is wrong.", @"") preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Disable Encryption", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + // Disable encryption + self.contact.isEncrypted = NO; + [self updateUIElements]; + [[DataLayer sharedInstance] disableEncryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; + [alert dismissViewControllerAnimated:YES completion:nil]; + }]]; + [self presentViewController:alert animated:YES completion:nil]; + } + else + { + // async dispatch is needed to show hud on chat open + // we won't do this twice, because the user won't be able to change isEncrypted to YES, + // unless we have omemo devices for that contact + dispatch_async(dispatch_get_main_queue(), ^{ + [self showOmemoHUD]; + }); + // request omemo devicelist + [self.xmppAccount.omemo subscribeAndFetchDevicelistIfNoSessionExistsForJid:self.contact.contactJid]; + } + } + } + else + [self hideOmemoHUD]; + } +#endif +} + +-(void) showOmemoHUD +{ + DDLogVerbose(@"Showing omemo HUD..."); + if(!self.omemoHUD) + { + self.omemoHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + self.omemoHUD.removeFromSuperViewOnHide = YES; + self.omemoHUD.label.text = NSLocalizedString(@"Loading OMEMO keys", @""); + } + else + self.omemoHUD.hidden = NO; +} + +-(void) hideOmemoHUD +{ + DDLogVerbose(@"Hiding omemo HUD..."); + self.omemoHUD.hidden = YES; +} + +-(void) handleOmemoFetchStateUpdate:(NSNotification*) notification +{ + xmpp* account = notification.object; + MLContact* contact = [MLContact createContactFromJid:notification.userInfo[@"jid"] andAccountNo:account.accountNo]; + if(self.contact && [self.contact isEqualToContact:contact]) + { + DDLogDebug(@"Got omemo fetching update: %@ --> %@", contact, notification.userInfo); + if(!((NSNumber*)notification.userInfo[@"isFetching"]).boolValue) + dispatch_async(dispatch_get_main_queue(), ^{ + //recheck support and show alert if needed + DDLogVerbose(@"Rechecking omemo support with alert, if needed..."); + [self checkOmemoSupportWithAlert:YES]; + }); + } +} + -(void) showUploadHUD { if(!self.uploadHUD) From 5ffb13f1006cae5ccdf7ad731f78855f59c1d7d3 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 12 Aug 2024 17:06:03 +0200 Subject: [PATCH 11/14] Use workaround for ios bug not properly removing share intentions When removing or disabling a contact, all share intentions for all contacts on this account should be properly removed to nos provide dangling share extension in ios' ui. --- Monal/Classes/HelperTools.h | 1 + Monal/Classes/HelperTools.m | 8 ++++++++ Monal/Classes/MLXMPPManager.m | 5 +---- Monal/Classes/XMPPEdit.m | 10 ++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Monal/Classes/HelperTools.h b/Monal/Classes/HelperTools.h index 636809c64..1765c6669 100644 --- a/Monal/Classes/HelperTools.h +++ b/Monal/Classes/HelperTools.h @@ -130,6 +130,7 @@ void swizzle(Class c, SEL orig, SEL new); +(void) configureFileProtection:(NSString*) protectionLevel forFile:(NSString*) file; +(void) configureFileProtectionFor:(NSString*) file; +(BOOL) isContactBlacklistedForEncryption:(MLContact*) contact; ++(void) removeAllShareInteractionsForAccountNo:(NSNumber*) accountNo; +(NSDictionary*) splitJid:(NSString*) jid; +(void) scheduleBackgroundTask:(BOOL) force; diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index a58a0a58c..6c1fcb886 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -1529,6 +1529,14 @@ +(BOOL) isContactBlacklistedForEncryption:(MLContact*) contact return blacklisted; } ++(void) removeAllShareInteractionsForAccountNo:(NSNumber*) accountNo +{ + DDLogInfo(@"Removing share interaction for all contacts on account id %@", accountNo); + for(MLContact* contact in [[DataLayer sharedInstance] contactList]) + if(contact.accountId.intValue == accountNo.intValue) + [contact removeShareInteractions]; +} + +(void) scheduleBackgroundTask:(BOOL) force { DDLogInfo(@"Scheduling new BackgroundTask with force=%s...", force ? "yes" : "no"); diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index 1aa9fa13a..d2c979b14 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -761,10 +761,7 @@ -(void) removeAccountForAccountNo:(NSNumber*) accountNo [self disconnectAccount:accountNo withExplicitLogout:YES]; [[DataLayer sharedInstance] removeAccount:accountNo]; [SAMKeychain deletePasswordForService:kMonalKeychainName account:accountNo.stringValue]; - [INInteraction deleteAllInteractionsWithCompletion:^(NSError* error) { - if(error != nil) - DDLogError(@"Could not delete all SiriKit interactions: %@", error); - }]; + [HelperTools removeAllShareInteractionsForAccountNo:accountNo]; // trigger UI removal [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; } diff --git a/Monal/Classes/XMPPEdit.m b/Monal/Classes/XMPPEdit.m index 9f7c66940..c769b70b1 100644 --- a/Monal/Classes/XMPPEdit.m +++ b/Monal/Classes/XMPPEdit.m @@ -376,10 +376,7 @@ -(IBAction) save:(id) sender { DDLogVerbose(@"Making sure newly created account is not connected and deleting all SiriKit interactions: %@", self.accountNo); [[MLXMPPManager sharedInstance] disconnectAccount:self.accountNo withExplicitLogout:YES]; - [INInteraction deleteAllInteractionsWithCompletion:^(NSError* error) { - if(error != nil) - DDLogError(@"Could not delete all SiriKit interactions: %@", error); - }]; + [HelperTools removeAllShareInteractionsForAccountNo:self.accountNo]; } //trigger view updates to make sure enabled/disabled account state propagates to all ui elements [[MLNotificationQueue currentQueue] postNotificationName:kMonalRefresh object:nil userInfo:nil]; @@ -400,10 +397,7 @@ -(IBAction) save:(id) sender { DDLogVerbose(@"Account is not enabled anymore, deleting all SiriKit interactions and making sure it's disconnected: %@", self.accountNo); [[MLXMPPManager sharedInstance] disconnectAccount:self.accountNo withExplicitLogout:YES]; - [INInteraction deleteAllInteractionsWithCompletion:^(NSError* error) { - if(error != nil) - DDLogError(@"Could not delete all SiriKit interactions: %@", error); - }]; + [HelperTools removeAllShareInteractionsForAccountNo:self.accountNo]; } //this case makes sure we recreate a completely new account instance below (using our new settings) if the account details changed else if(self.detailsChanged) From 5abfafac4d35d71e2341dfac5c3bdb02d09f89fb Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Mon, 5 Aug 2024 03:43:05 +0200 Subject: [PATCH 12/14] Allow PLAIN for quicksy.im, too --- Monal/Classes/MLXMPPManager.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index d2c979b14..2ab2b49b4 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -735,9 +735,9 @@ -(NSNumber*) login:(NSString*) jid password:(NSString*) password //if they still want to connect to this server //only exception: yax.im --> we don't want to suggest a server during account creation that has a scary warning //when logging in using another device afterwards - //TODO: to be removed once yax.im supports SASL2 and SSDP!! + //TODO: to be removed once yax.im and quicksy.im supports SASL2 and SSDP!! //TODO: use preload list and allow PLAIN for all others once enough domains are on this list - [dic setObject:([domain isEqualToString:@"yax.im"] ? @YES : @NO) forKey:kPlainActivated]; + [dic setObject:([domain isEqualToString:@"yax.im"] || [domain isEqualToString:@"quicksy.im"] ? @YES : @NO) forKey:kPlainActivated]; NSNumber* accountNo = [[DataLayer sharedInstance] addAccountWithDictionary:dic]; if(accountNo == nil) From b3b8f088113fc4b277ffb10021bb75477fb9209c Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sun, 11 Aug 2024 11:57:18 +0200 Subject: [PATCH 13/14] Make sure to not warn about PLAIN, SASL2 on prosody isn't released yet Fixes #1199 --- Monal/Classes/MLXMPPManager.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index 2ab2b49b4..aad95a359 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -737,7 +737,9 @@ -(NSNumber*) login:(NSString*) jid password:(NSString*) password //when logging in using another device afterwards //TODO: to be removed once yax.im and quicksy.im supports SASL2 and SSDP!! //TODO: use preload list and allow PLAIN for all others once enough domains are on this list - [dic setObject:([domain isEqualToString:@"yax.im"] || [domain isEqualToString:@"quicksy.im"] ? @YES : @NO) forKey:kPlainActivated]; + //allow plain for all servers not on preload list, since prosody with SASL2 wasn't even released yet + NSNumber* defaultPlainActivated = @YES; + [dic setObject:([domain isEqualToString:@"yax.im"] || [domain isEqualToString:@"quicksy.im"] ? @YES : defaultPlainActivated) forKey:kPlainActivated]; NSNumber* accountNo = [[DataLayer sharedInstance] addAccountWithDictionary:dic]; if(accountNo == nil) From e82f57209db31bafc272e37464c1eef5b9bde131 Mon Sep 17 00:00:00 2001 From: Thilo Molitor Date: Sat, 17 Aug 2024 20:19:50 +0200 Subject: [PATCH 14/14] Fix xmpp: uri handling (qrcode or from other apps) --- Monal/Classes/ActiveChatsViewController.h | 2 + Monal/Classes/ActiveChatsViewController.m | 5 + Monal/Classes/MonalAppDelegate.m | 201 +++++++++++----------- 3 files changed, 111 insertions(+), 97 deletions(-) diff --git a/Monal/Classes/ActiveChatsViewController.h b/Monal/Classes/ActiveChatsViewController.h index 8541382c5..0cab6291e 100644 --- a/Monal/Classes/ActiveChatsViewController.h +++ b/Monal/Classes/ActiveChatsViewController.h @@ -49,6 +49,8 @@ NS_ASSUME_NONNULL_BEGIN -(void) showAddContact; -(void) sheetDismissed; +-(void) segueToIntroScreensIfNeeded; +-(void) resetViewQueue; -(void) dismissCompleteViewChainWithAnimation:(BOOL) animation andCompletion:(monal_void_block_t _Nullable) completion; @end diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index aea4b0328..b9628b7f1 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -115,6 +115,11 @@ -(void) commonInit _blockQueueSemaphore = dispatch_semaphore_create(1); } +-(void) resetViewQueue +{ + [_blockQueue removeAllObjects]; +} + -(void) prependToViewQueue:(view_queue_block_t) block withId:(MLViewID) viewId andFile:(char*) file andLine:(int) line andFunc:(char*) func { @synchronized(_blockQueue) { diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index 0206838a0..278f740e1 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -735,114 +735,121 @@ -(void) handleXMPPURL:(NSURL*) url while(self.activeChats == nil) usleep(100000); dispatch_async(dispatch_get_main_queue(), ^{ - BOOL registerNeeded = [MLXMPPManager sharedInstance].connectedXMPP.count == 0; - NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - DDLogVerbose(@"URI path '%@'", components.path); - DDLogVerbose(@"URI query '%@'", components.query); - - NSString* jid = components.path; - NSDictionary* jidParts = [HelperTools splitJid:jid]; - BOOL isRegister = NO; - BOOL isRoster = NO; - BOOL isGroupJoin = NO; - BOOL isIbr = NO; - NSString* preauthToken = nil; - NSMutableDictionary* omemoFingerprints = [NSMutableDictionary new]; - //someone had the really superior (NOT!) idea to split uri query parts by ';' instead of the standard '&' - //making all existing uri libs useless, see: https://xmpp.org/extensions/xep-0147.html - //blame this author: Peter Saint-Andre - NSArray* queryItems = [components.query componentsSeparatedByString:@";"]; - for(NSString* item in queryItems) - { - NSArray* itemParts = [item componentsSeparatedByString:@"="]; - NSString* name = itemParts[0]; - NSString* value = @""; - if([itemParts count] > 1) - value = itemParts[1]; - DDLogVerbose(@"URI part '%@' = '%@'", name, value); - if([name isEqualToString:@"register"]) - isRegister = YES; - if([name isEqualToString:@"roster"]) - isRoster = YES; - if([name isEqualToString:@"join"]) - isGroupJoin = YES; - if([name isEqualToString:@"ibr"] && [value isEqualToString:@"y"]) - isIbr = YES; - if([name isEqualToString:@"preauth"]) - preauthToken = [value copy]; - if([name hasPrefix:@"omemo-sid-"]) + //remove everything from our view queue (including currently displayed views) + //and add intro screens back to the queue, if needed, followed by the view handling the xmpp uri action + [self.activeChats resetViewQueue]; + [self.activeChats dismissCompleteViewChainWithAnimation:NO andCompletion:^{ + [self.activeChats segueToIntroScreensIfNeeded]; + + BOOL registerNeeded = [MLXMPPManager sharedInstance].connectedXMPP.count == 0; + NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + DDLogVerbose(@"URI path '%@'", components.path); + DDLogVerbose(@"URI query '%@'", components.query); + + NSString* jid = components.path; + NSDictionary* jidParts = [HelperTools splitJid:jid]; + BOOL isRegister = NO; + BOOL isRoster = NO; + BOOL isGroupJoin = NO; + BOOL isIbr = NO; + NSString* preauthToken = nil; + NSMutableDictionary* omemoFingerprints = [NSMutableDictionary new]; + //someone had the really superior (NOT!) idea to split uri query parts by ';' instead of the standard '&' + //making all existing uri libs useless, see: https://xmpp.org/extensions/xep-0147.html + //blame this author: Peter Saint-Andre + NSArray* queryItems = [components.query componentsSeparatedByString:@";"]; + for(NSString* item in queryItems) { - NSNumber* sid = [NSNumber numberWithUnsignedInteger:(NSUInteger)[[name substringFromIndex:10] longLongValue]]; - NSData* fingerprint = [HelperTools signalIdentityWithHexKey:value]; - omemoFingerprints[sid] = fingerprint; + NSArray* itemParts = [item componentsSeparatedByString:@"="]; + NSString* name = itemParts[0]; + NSString* value = @""; + if([itemParts count] > 1) + value = itemParts[1]; + DDLogVerbose(@"URI part '%@' = '%@'", name, value); + if([name isEqualToString:@"register"]) + isRegister = YES; + if([name isEqualToString:@"roster"]) + isRoster = YES; + if([name isEqualToString:@"join"]) + isGroupJoin = YES; + if([name isEqualToString:@"ibr"] && [value isEqualToString:@"y"]) + isIbr = YES; + if([name isEqualToString:@"preauth"]) + preauthToken = [value copy]; + if([name hasPrefix:@"omemo-sid-"]) + { + NSNumber* sid = [NSNumber numberWithUnsignedInteger:(NSUInteger)[[name substringFromIndex:10] longLongValue]]; + NSData* fingerprint = [HelperTools signalIdentityWithHexKey:value]; + omemoFingerprints[sid] = fingerprint; + } } - } - - if(!jidParts[@"host"]) - { - DDLogError(@"Ignoring xmpp: uri without host jid part!"); - return; - } - - if(isRegister || (isRoster && registerNeeded)) - { - NSString* username = nilDefault(jidParts[@"node"], @""); - NSString* host = jidParts[@"host"]; - if(isRoster) + if(!jidParts[@"host"]) { - //isRoster variant does not specify a predefined username for the new account, register does (but this is still optional) - username = @""; - //isRoster variant without ibr does not specify a host to register on, too - if(!isIbr) - host = @""; + DDLogError(@"Ignoring xmpp: uri without host jid part!"); + return; } - //show register view and, if isRoster, add contact as usual after register (e.g. call this method again) - weakify(self); - [self.activeChats showRegisterWithUsername:username onHost:host withToken:preauthToken usingCompletion:^(NSNumber* accountNo) { - strongify(self); - DDLogVerbose(@"Got accountNo for newly registered account: %@", accountNo); - xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:accountNo]; - DDLogInfo(@"Got newly registered account: %@", account); + if(isRegister || (isRoster && registerNeeded)) + { + NSString* username = nilDefault(jidParts[@"node"], @""); + NSString* host = jidParts[@"host"]; - //this should never happen - MLAssert(account != nil, @"Can not use account after register!", (@{ - @"components": components, - @"username": username, - @"host": host, - })); + if(isRoster) + { + //isRoster variant does not specify a predefined username for the new account, register does (but this is still optional) + username = @""; + //isRoster variant without ibr does not specify a host to register on, too + if(!isIbr) + host = @""; + } - //add given jid to our roster if in roster mode (e.g. the jid is not the jid we just registered as like in register mode) - if(account != nil && isRoster) //silence memory warning despite assertion above - return [self handleXMPPURL:url]; - }]; - } - //I know this if is moot, but I wanted to preserve the different cases: - //either we already have one or more accounts and the xmpp: uri is of type subscription (ibr does not matter here, - //because we already have an account) or muc join - //OR the xmpp: uri is a normal xmpp uri having only a jid we should add as our new contact (preauthToken will be nil in this case) - else if((!registerNeeded && (isRoster || isGroupJoin)) || !registerNeeded) - { - if([MLXMPPManager sharedInstance].connectedXMPP.count == 1) + //show register view and, if isRoster, add contact as usual after register (e.g. call this method again) + weakify(self); + [self.activeChats showRegisterWithUsername:username onHost:host withToken:preauthToken usingCompletion:^(NSNumber* accountNo) { + strongify(self); + DDLogVerbose(@"Got accountNo for newly registered account: %@", accountNo); + xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:accountNo]; + DDLogInfo(@"Got newly registered account: %@", account); + + //this should never happen + MLAssert(account != nil, @"Can not use account after register!", (@{ + @"components": components, + @"username": username, + @"host": host, + })); + + //add given jid to our roster if in roster mode (e.g. the jid is not the jid we just registered as like in register mode) + if(account != nil && isRoster) //silence memory warning despite assertion above + return [self handleXMPPURL:url]; + }]; + } + //I know this if is moot, but I wanted to preserve the different cases: + //either we already have one or more accounts and the xmpp: uri is of type subscription (ibr does not matter here, + //because we already have an account) or muc join + //OR the xmpp: uri is a normal xmpp uri having only a jid we should add as our new contact (preauthToken will be nil in this case) + else if((!registerNeeded && (isRoster || isGroupJoin)) || !registerNeeded) { - //the add contacts ui will check if the contact is already present on the selected account - xmpp* account = [[MLXMPPManager sharedInstance].connectedXMPP firstObject]; - [self.activeChats showAddContactWithJid:jid preauthToken:preauthToken prefillAccount:account andOmemoFingerprints:omemoFingerprints]; + if([MLXMPPManager sharedInstance].connectedXMPP.count == 1) + { + //the add contacts ui will check if the contact is already present on the selected account + xmpp* account = [[MLXMPPManager sharedInstance].connectedXMPP firstObject]; + [self.activeChats showAddContactWithJid:jid preauthToken:preauthToken prefillAccount:account andOmemoFingerprints:omemoFingerprints]; + } + else + //the add contacts ui will check if the contact is already present on the selected account + [self.activeChats showAddContactWithJid:jid preauthToken:preauthToken prefillAccount:nil andOmemoFingerprints:omemoFingerprints]; } else - //the add contacts ui will check if the contact is already present on the selected account - [self.activeChats showAddContactWithJid:jid preauthToken:preauthToken prefillAccount:nil andOmemoFingerprints:omemoFingerprints]; - } - else - { - DDLogError(@"No account available to handel xmpp: uri!"); - - UIAlertController* messageAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error adding contact or channel", @"") message:NSLocalizedString(@"No account available to handel 'xmpp:' URI!", @"") preferredStyle:UIAlertControllerStyleAlert]; - [messageAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction* action __unused) { - }]]; - [self.activeChats presentViewController:messageAlert animated:YES completion:nil]; - } + { + DDLogError(@"No account available to handel xmpp: uri!"); + + UIAlertController* messageAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error adding contact or channel", @"") message:NSLocalizedString(@"No account available to handel 'xmpp:' URI!", @"") preferredStyle:UIAlertControllerStyleAlert]; + [messageAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction* action __unused) { + }]]; + [self.activeChats presentViewController:messageAlert animated:YES completion:nil]; + } + }]; }); }); }