803 lines
29 KiB
Objective-C
803 lines
29 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2019 The Matrix.org Foundation C.I.C
|
|
Copyright 2018 New Vector Ltd
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKAccountManager.h"
|
|
#import "MXKAppSettings.h"
|
|
|
|
#import "MXKTools.h"
|
|
#import "MXKAccountData.h"
|
|
#import "MXRefreshTokenData.h"
|
|
|
|
static NSString *const kMXKAccountsKeyOld = @"accounts";
|
|
static NSString *const kMXKAccountsKey = @"accountsV2";
|
|
|
|
NSString *const kMXKAccountManagerDidAddAccountNotification = @"kMXKAccountManagerDidAddAccountNotification";
|
|
NSString *const kMXKAccountManagerDidRemoveAccountNotification = @"kMXKAccountManagerDidRemoveAccountNotification";
|
|
NSString *const kMXKAccountManagerDidSoftlogoutAccountNotification = @"kMXKAccountManagerDidSoftlogoutAccountNotification";
|
|
NSString *const MXKAccountManagerDataType = @"org.matrix.kit.MXKAccountManagerDataType";
|
|
|
|
@interface MXKAccountManager()
|
|
{
|
|
/**
|
|
The list of all accounts (enabled and disabled). Each value is a `MXKAccount` instance.
|
|
*/
|
|
NSMutableArray<MXKAccount *> *mxAccounts;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MXKAccountManager
|
|
|
|
+ (MXKAccountManager *)sharedManager
|
|
{
|
|
return [MXKAccountManager sharedManagerWithReload:NO];
|
|
}
|
|
|
|
+ (MXKAccountManager *)sharedManagerWithReload:(BOOL)reload
|
|
{
|
|
static MXKAccountManager *sharedAccountManager = nil;
|
|
static dispatch_once_t onceToken;
|
|
__block BOOL didLoad = false;
|
|
dispatch_once(&onceToken, ^{
|
|
didLoad = true;
|
|
sharedAccountManager = [[super allocWithZone:NULL] init];
|
|
});
|
|
|
|
if (reload && !didLoad) {
|
|
[sharedAccountManager loadAccounts];
|
|
}
|
|
return sharedAccountManager;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_storeClass = [MXFileStore class];
|
|
_savingAccountsEnabled = YES;
|
|
|
|
// Migrate old account file to new format
|
|
[self migrateAccounts];
|
|
|
|
// Load existing accounts from local storage
|
|
[self loadAccounts];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
mxAccounts = nil;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)prepareSessionForActiveAccounts
|
|
{
|
|
for (MXKAccount *account in mxAccounts)
|
|
{
|
|
// Check whether the account is enabled. Open a new matrix session if none.
|
|
if (!account.isDisabled && !account.isSoftLogout && !account.mxSession)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] openSession for %@ account", account.mxCredentials.userId);
|
|
|
|
id<MXStore> store = [[_storeClass alloc] init];
|
|
[account openSessionWithStore:store];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
- (void)saveAccounts
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] saveAccounts...");
|
|
|
|
if (!self.isSavingAccountsEnabled)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] saveAccounts: saving disabled.");
|
|
return;
|
|
}
|
|
NSDate *startDate = [NSDate date];
|
|
|
|
NSMutableData *data = [NSMutableData data];
|
|
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
|
|
|
|
[encoder encodeObject:mxAccounts forKey:@"mxAccounts"];
|
|
|
|
[encoder finishEncoding];
|
|
|
|
[data setData:[self encryptData:data]];
|
|
|
|
BOOL result = [data writeToFile:[self accountFile] atomically:YES];
|
|
|
|
MXLogDebug(@"[MXKAccountManager] saveAccounts. Done (result: %@) in %.0fms", @(result), [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
- (void)addAccount:(MXKAccount *)account andOpenSession:(BOOL)openSession
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] login (%@)", account.mxCredentials.userId);
|
|
|
|
[mxAccounts addObject:account];
|
|
[self saveAccounts];
|
|
|
|
// Check conditions to open a matrix session
|
|
if (openSession && !account.disabled)
|
|
{
|
|
// Open a new matrix session by default
|
|
MXLogDebug(@"[MXKAccountManager] openSession for %@ account (device %@)", account.mxCredentials.userId, account.mxCredentials.deviceId);
|
|
id<MXStore> store = [[_storeClass alloc] init];
|
|
[account openSessionWithStore:store];
|
|
}
|
|
|
|
// Post notification
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification object:account userInfo:nil];
|
|
}
|
|
|
|
- (void)removeAccount:(MXKAccount*)theAccount completion:(void (^)(void))completion
|
|
{
|
|
[self removeAccount:theAccount sendLogoutRequest:YES completion:completion];
|
|
}
|
|
|
|
- (void)removeAccount:(MXKAccount*)theAccount
|
|
sendLogoutRequest:(BOOL)sendLogoutRequest
|
|
completion:(void (^)(void))completion
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] logout (%@), send logout request to homeserver: %d", theAccount.mxCredentials.userId, sendLogoutRequest);
|
|
|
|
// Close session and clear associated store.
|
|
[theAccount logoutSendingServerRequest:sendLogoutRequest completion:^{
|
|
|
|
// Retrieve the corresponding account in the internal array
|
|
MXKAccount* removedAccount = nil;
|
|
|
|
for (MXKAccount *account in self->mxAccounts)
|
|
{
|
|
if ([account.mxCredentials.userId isEqualToString:theAccount.mxCredentials.userId])
|
|
{
|
|
removedAccount = account;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (removedAccount)
|
|
{
|
|
[self->mxAccounts removeObject:removedAccount];
|
|
|
|
[self saveAccounts];
|
|
|
|
// Post notification
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidRemoveAccountNotification object:removedAccount userInfo:nil];
|
|
}
|
|
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
|
|
}];
|
|
}
|
|
|
|
|
|
- (void)logoutWithCompletion:(void (^)(void))completion
|
|
{
|
|
// Logout one by one the existing accounts
|
|
if (mxAccounts.count)
|
|
{
|
|
[self removeAccount:mxAccounts.lastObject completion:^{
|
|
|
|
// loop: logout the next existing account (if any)
|
|
[self logoutWithCompletion:completion];
|
|
|
|
}];
|
|
|
|
return;
|
|
}
|
|
|
|
NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
|
|
|
|
// Remove APNS device token
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
|
|
|
// Remove Push device token
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
|
|
|
// Be sure that no account survive in local storage
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
|
|
[sharedUserDefaults removeObjectForKey:kMXKAccountsKey];
|
|
[[NSFileManager defaultManager] removeItemAtPath:[self accountFile] error:nil];
|
|
|
|
if (completion)
|
|
{
|
|
completion();
|
|
}
|
|
}
|
|
|
|
- (void)softLogout:(MXKAccount*)account
|
|
{
|
|
[account softLogout];
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidSoftlogoutAccountNotification
|
|
object:account
|
|
userInfo:nil];
|
|
}
|
|
|
|
- (void)hydrateAccount:(MXKAccount*)account withCredentials:(MXCredentials*)credentials
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] hydrateAccount: %@", account.mxCredentials.userId);
|
|
|
|
if ([account.mxCredentials.userId isEqualToString:credentials.userId])
|
|
{
|
|
// Restart the account
|
|
[account hydrateWithCredentials:credentials];
|
|
|
|
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Open session");
|
|
|
|
id<MXStore> store = [[_storeClass alloc] init];
|
|
[account openSessionWithStore:store];
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kMXKAccountManagerDidAddAccountNotification
|
|
object:account
|
|
userInfo:nil];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] hydrateAccount: Credentials given for another account: %@", credentials.userId);
|
|
|
|
// Logout the old account and create a new one with the new credentials
|
|
[self removeAccount:account sendLogoutRequest:YES completion:nil];
|
|
|
|
MXKAccount *newAccount = [[MXKAccount alloc] initWithCredentials:credentials];
|
|
[self addAccount:newAccount andOpenSession:YES];
|
|
}
|
|
}
|
|
|
|
- (MXKAccount *)accountForUserId:(NSString *)userId
|
|
{
|
|
for (MXKAccount *account in mxAccounts)
|
|
{
|
|
if ([account.mxCredentials.userId isEqualToString:userId])
|
|
{
|
|
return account;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (MXKAccount *)accountKnowingRoomWithRoomIdOrAlias:(NSString *)roomIdOrAlias
|
|
{
|
|
MXKAccount *theAccount = nil;
|
|
|
|
NSArray *activeAccounts = self.activeAccounts;
|
|
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if ([roomIdOrAlias hasPrefix:@"#"])
|
|
{
|
|
if ([account.mxSession roomWithAlias:roomIdOrAlias])
|
|
{
|
|
theAccount = account;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ([account.mxSession roomWithRoomId:roomIdOrAlias])
|
|
{
|
|
theAccount = account;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return theAccount;
|
|
}
|
|
|
|
- (MXKAccount *)accountKnowingUserWithUserId:(NSString *)userId
|
|
{
|
|
MXKAccount *theAccount = nil;
|
|
|
|
NSArray *activeAccounts = self.activeAccounts;
|
|
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if ([account.mxSession userWithUserId:userId])
|
|
{
|
|
theAccount = account;
|
|
break;
|
|
}
|
|
}
|
|
return theAccount;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setStoreClass:(Class)storeClass
|
|
{
|
|
// Sanity check
|
|
NSAssert([storeClass conformsToProtocol:@protocol(MXStore)], @"MXKAccountManager only manages store class that conforms to MXStore protocol");
|
|
|
|
_storeClass = storeClass;
|
|
}
|
|
|
|
- (NSArray<MXKAccount *> *)accounts
|
|
{
|
|
return [mxAccounts copy];
|
|
}
|
|
|
|
- (NSArray<MXKAccount *> *)activeAccounts
|
|
{
|
|
NSMutableArray *activeAccounts = [NSMutableArray arrayWithCapacity:mxAccounts.count];
|
|
for (MXKAccount *account in mxAccounts)
|
|
{
|
|
if (!account.disabled && !account.isSoftLogout)
|
|
{
|
|
[activeAccounts addObject:account];
|
|
}
|
|
}
|
|
return activeAccounts;
|
|
}
|
|
|
|
- (NSData *)apnsDeviceToken
|
|
{
|
|
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"apnsDeviceToken"];
|
|
if (!token.length)
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
|
token = nil;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] apnsDeviceToken: %@", [MXKTools logForPushToken:token]);
|
|
return token;
|
|
}
|
|
|
|
- (void)setApnsDeviceToken:(NSData *)apnsDeviceToken
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: %@", [MXKTools logForPushToken:apnsDeviceToken]);
|
|
|
|
NSData *oldToken = self.apnsDeviceToken;
|
|
if (!apnsDeviceToken.length)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: reset APNS device token");
|
|
|
|
if (oldToken)
|
|
{
|
|
// turn off the Apns flag for all accounts if any
|
|
for (MXKAccount *account in mxAccounts)
|
|
{
|
|
[account enablePushNotifications:NO success:nil failure:nil];
|
|
}
|
|
}
|
|
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"apnsDeviceToken"];
|
|
}
|
|
else
|
|
{
|
|
NSArray *activeAccounts = self.activeAccounts;
|
|
|
|
if (!oldToken)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: set APNS device token");
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
|
|
|
|
// turn on the Apns flag for all accounts, when the Apns registration succeeds for the first time
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
[account enablePushNotifications:YES success:nil failure:nil];
|
|
}
|
|
}
|
|
else if (![oldToken isEqualToData:apnsDeviceToken])
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: update APNS device token");
|
|
|
|
NSMutableArray<MXKAccount*> *accountsWithAPNSPusher = [NSMutableArray new];
|
|
|
|
// Delete the pushers related to the old token
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if (account.hasPusherForPushNotifications)
|
|
{
|
|
[accountsWithAPNSPusher addObject:account];
|
|
}
|
|
|
|
[account enablePushNotifications:NO success:nil failure:nil];
|
|
}
|
|
|
|
// Update the token
|
|
[[NSUserDefaults standardUserDefaults] setObject:apnsDeviceToken forKey:@"apnsDeviceToken"];
|
|
|
|
// Refresh pushers with the new token.
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if ([accountsWithAPNSPusher containsObject:account])
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Resync APNS for %@ account", account.mxCredentials.userId);
|
|
[account enablePushNotifications:YES success:nil failure:nil];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: hasPusherForPushNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setApnsDeviceToken: Same token. Nothing to do.");
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)isAPNSAvailable
|
|
{
|
|
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
|
|
// remote notifications or not. Receiving remote notifications does not guarantee it will
|
|
// display them to the user as they may have notifications set to deliver quietly.
|
|
|
|
BOOL isRemoteNotificationsAllowed = NO;
|
|
|
|
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
|
if (sharedApplication)
|
|
{
|
|
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
|
|
}
|
|
|
|
BOOL isAPNSAvailable = (isRemoteNotificationsAllowed && self.apnsDeviceToken);
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] isAPNSAvailable: %@", @(isAPNSAvailable));
|
|
|
|
return isAPNSAvailable;
|
|
}
|
|
|
|
- (NSData *)pushDeviceToken
|
|
{
|
|
NSData *token = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushDeviceToken"];
|
|
if (!token.length)
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
|
token = nil;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] pushDeviceToken: %@", [MXKTools logForPushToken:token]);
|
|
return token;
|
|
}
|
|
|
|
- (NSDictionary *)pushOptions
|
|
{
|
|
NSDictionary *pushOptions = [[NSUserDefaults standardUserDefaults] objectForKey:@"pushOptions"];
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] pushOptions: %@", pushOptions);
|
|
return pushOptions;
|
|
}
|
|
|
|
- (void)setPushDeviceToken:(NSData *)pushDeviceToken withPushOptions:(NSDictionary *)pushOptions
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: %@ withPushOptions: %@", [MXKTools logForPushToken:pushDeviceToken], pushOptions);
|
|
|
|
NSData *oldToken = self.pushDeviceToken;
|
|
if (!pushDeviceToken.length)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Reset Push device token");
|
|
|
|
if (oldToken)
|
|
{
|
|
// turn off the Push flag for all accounts if any
|
|
for (MXKAccount *account in mxAccounts)
|
|
{
|
|
[account enablePushKitNotifications:NO success:^{
|
|
// make sure pusher really removed before losing token.
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushDeviceToken"];
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
|
} failure:nil];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSArray *activeAccounts = self.activeAccounts;
|
|
|
|
if (!oldToken)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Set Push device token");
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
|
|
if (pushOptions)
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
|
|
}
|
|
else
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
|
}
|
|
|
|
// turn on the Push flag for all accounts
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
[account enablePushKitNotifications:YES success:nil failure:nil];
|
|
}
|
|
}
|
|
else if (![oldToken isEqualToData:pushDeviceToken])
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Update Push device token");
|
|
|
|
NSMutableArray<MXKAccount*> *accountsWithPushKitPusher = [NSMutableArray new];
|
|
|
|
// Delete the pushers related to the old token
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if (account.hasPusherForPushKitNotifications)
|
|
{
|
|
[accountsWithPushKitPusher addObject:account];
|
|
}
|
|
|
|
[account enablePushKitNotifications:NO success:nil failure:nil];
|
|
}
|
|
|
|
// Update the token
|
|
[[NSUserDefaults standardUserDefaults] setObject:pushDeviceToken forKey:@"pushDeviceToken"];
|
|
if (pushOptions)
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] setObject:pushOptions forKey:@"pushOptions"];
|
|
}
|
|
else
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"pushOptions"];
|
|
}
|
|
|
|
// Refresh pushers with the new token.
|
|
for (MXKAccount *account in activeAccounts)
|
|
{
|
|
if ([accountsWithPushKitPusher containsObject:account])
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Resync Push for %@ account", account.mxCredentials.userId);
|
|
[account enablePushKitNotifications:YES success:nil failure:nil];
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: hasPusherForPushKitNotifications = NO for %@ account. Do not enable Push", account.mxCredentials.userId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager][Push] setPushDeviceToken: Same token. Nothing to do.");
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)isPushAvailable
|
|
{
|
|
// [UIApplication isRegisteredForRemoteNotifications] tells whether your app can receive
|
|
// remote notifications or not. Receiving remote notifications does not guarantee it will
|
|
// display them to the user as they may have notifications set to deliver quietly.
|
|
|
|
BOOL isRemoteNotificationsAllowed = NO;
|
|
|
|
UIApplication *sharedApplication = [UIApplication performSelector:@selector(sharedApplication)];
|
|
if (sharedApplication)
|
|
{
|
|
isRemoteNotificationsAllowed = [sharedApplication isRegisteredForRemoteNotifications];
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: The user %@ remote notification", (isRemoteNotificationsAllowed ? @"allowed" : @"denied"));
|
|
}
|
|
|
|
BOOL isPushAvailable = (isRemoteNotificationsAllowed && self.pushDeviceToken);
|
|
|
|
MXLogDebug(@"[MXKAccountManager][Push] isPushAvailable: %@", @(isPushAvailable));
|
|
return isPushAvailable;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
// Return the path of the file containing stored MXAccounts array
|
|
- (NSString*)accountFile
|
|
{
|
|
NSString *matrixKitCacheFolder = [MXKAppSettings cacheFolder];
|
|
return [matrixKitCacheFolder stringByAppendingPathComponent:kMXKAccountsKey];
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
- (void)loadAccounts
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts");
|
|
NSString *accountFile = [self accountFile];
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:accountFile])
|
|
{
|
|
NSDate *startDate = [NSDate date];
|
|
|
|
NSError *error = nil;
|
|
NSData* filecontent = [NSData dataWithContentsOfFile:accountFile options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:&error];
|
|
|
|
if (!error)
|
|
{
|
|
// Decrypt data if encryption method is provided
|
|
NSData *unciphered = [self decryptData:filecontent];
|
|
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:unciphered];
|
|
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
|
|
|
|
if (!mxAccounts && [[MXKeyProvider sharedInstance] isEncryptionAvailableForDataOfType:MXKAccountManagerDataType])
|
|
{
|
|
// This happens if the V2 file has not been encrypted -> read file content then save encrypted accounts
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts. Failed to read decrypted data: reading file data without encryption.");
|
|
decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:filecontent];
|
|
mxAccounts = [decoder decodeObjectForKey:@"mxAccounts"];
|
|
|
|
if (mxAccounts)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts. saving encrypted accounts");
|
|
[self saveAccounts];
|
|
}
|
|
}
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts. %tu accounts loaded in %.0fms", mxAccounts.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000);
|
|
}
|
|
else
|
|
{
|
|
// Migration of accountData from sharedUserDefaults to a file
|
|
NSUserDefaults *sharedDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults;
|
|
|
|
NSData *accountData = [sharedDefaults objectForKey:kMXKAccountsKey];
|
|
if (!accountData)
|
|
{
|
|
// Migration of accountData from [NSUserDefaults standardUserDefaults], the first location storage
|
|
accountData = [[NSUserDefaults standardUserDefaults] objectForKey:kMXKAccountsKey];
|
|
}
|
|
|
|
if (accountData)
|
|
{
|
|
mxAccounts = [NSMutableArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:accountData]];
|
|
[self saveAccounts];
|
|
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts: performed data migration");
|
|
|
|
// Now that data has been migrated, erase old location of accountData
|
|
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kMXKAccountsKey];
|
|
|
|
[sharedDefaults removeObjectForKey:kMXKAccountsKey];
|
|
}
|
|
}
|
|
|
|
if (!mxAccounts)
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] loadAccounts. No accounts");
|
|
mxAccounts = [NSMutableArray array];
|
|
}
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
- (NSData*)encryptData:(NSData*)data
|
|
{
|
|
// Exceptions are not caught as the key is always needed if the KeyProviderDelegate
|
|
// is provided.
|
|
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
|
|
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
|
|
{
|
|
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
|
|
NSData *cipher = [MXAes encrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
|
|
return cipher;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager] encryptData: no key method provided for encryption.");
|
|
return data;
|
|
}
|
|
|
|
- (NSData*)decryptData:(NSData*)data
|
|
{
|
|
// Exceptions are not cached as the key is always needed if the KeyProviderDelegate
|
|
// is provided.
|
|
MXKeyData *keyData = [[MXKeyProvider sharedInstance] requestKeyForDataOfType:MXKAccountManagerDataType isMandatory:YES expectedKeyType:kAes];
|
|
if (keyData && [keyData isKindOfClass:[MXAesKeyData class]])
|
|
{
|
|
MXAesKeyData *aesKey = (MXAesKeyData *) keyData;
|
|
NSData *decrypt = [MXAes decrypt:data aesKey:aesKey.key iv:aesKey.iv error:nil];
|
|
return decrypt;
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager] decryptData: no key method provided for decryption.");
|
|
return data;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
- (void)migrateAccounts
|
|
{
|
|
NSString *pathOld = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKeyOld];
|
|
NSString *pathNew = [[MXKAppSettings cacheFolder] stringByAppendingPathComponent:kMXKAccountsKey];
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
if ([fileManager fileExistsAtPath:pathOld])
|
|
{
|
|
if (![fileManager fileExistsAtPath:pathNew])
|
|
{
|
|
MXLogDebug(@"[MXKAccountManager] migrateAccounts: reading account");
|
|
mxAccounts = [NSKeyedUnarchiver unarchiveObjectWithFile:pathOld];
|
|
MXLogDebug(@"[MXKAccountManager] migrateAccounts: writing to accountV2");
|
|
[self saveAccounts];
|
|
}
|
|
|
|
MXLogDebug(@"[MXKAccountManager] migrateAccounts: removing account");
|
|
[fileManager removeItemAtPath:pathOld error:nil];
|
|
}
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
- (void)readAndWriteCredentials:(void (^)(NSArray<MXCredentials*> * _Nullable readData, void (^completion)(BOOL didUpdateCredentials)))readAnWriteHandler
|
|
{
|
|
NSError *error;
|
|
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] init];
|
|
__block BOOL coordinatorSuccess = NO;
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials: purposeIdentifier = %@", fileCoordinator.purposeIdentifier);
|
|
NSDate *coordinateStartTime = [NSDate date];
|
|
[fileCoordinator coordinateReadingItemAtURL:[self accountFileUrl]
|
|
options:0
|
|
writingItemAtURL:[self accountFileUrl]
|
|
options:NSFileCoordinatorWritingForMerging
|
|
error:&error
|
|
byAccessor:^(NSURL * _Nonnull newReadingURL, NSURL * _Nonnull newWritingURL) {
|
|
|
|
NSDate *accessorStartTime = [NSDate date];
|
|
NSTimeInterval acquireInterval = [accessorStartTime timeIntervalSinceDate:coordinateStartTime];
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials: acquireInterval = %f", acquireInterval);
|
|
NSError *error = nil;
|
|
NSData* data = [NSData dataWithContentsOfURL:newReadingURL options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:&error];
|
|
|
|
// Decrypt data if encryption method is provided
|
|
NSData *unciphered = [self decryptData:data];
|
|
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingFromData:unciphered error:&error];
|
|
decoder.requiresSecureCoding = false;
|
|
[decoder setClass:[MXKAccountData class] forClassName:@"MXKAccount"];
|
|
NSMutableArray<MXKAccountData*>* mxAccountsData = [decoder decodeObjectForKey:@"mxAccounts"];
|
|
NSMutableArray<MXCredentials*>* mxAccountCredentials = [NSMutableArray arrayWithCapacity:mxAccounts.count];
|
|
for(MXKAccountData *account in mxAccountsData){
|
|
[mxAccountCredentials addObject:account.mxCredentials];
|
|
}
|
|
|
|
dispatch_group_t dispatchGroup = dispatch_group_create();
|
|
dispatch_group_enter(dispatchGroup);
|
|
|
|
__block BOOL didUpdate = NO;
|
|
readAnWriteHandler(mxAccountCredentials, ^(BOOL didUpdateCredentials) {
|
|
didUpdate = didUpdateCredentials;
|
|
dispatch_group_leave(dispatchGroup);
|
|
});
|
|
|
|
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
|
|
|
|
if (didUpdate) {
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials: did update saving credential data");
|
|
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initRequiringSecureCoding: NO];
|
|
[encoder setClassName:@"MXKAccount" forClass:[MXKAccountData class]];
|
|
[encoder encodeObject:mxAccountsData forKey:@"mxAccounts"];
|
|
NSData *writeData = [self encryptData:[encoder encodedData]];
|
|
coordinatorSuccess = [writeData writeToURL:newWritingURL atomically:YES];
|
|
} else {
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials: did not update not saving credential data");
|
|
coordinatorSuccess = YES;
|
|
}
|
|
NSDate *accessorEndTime = [NSDate date];
|
|
NSTimeInterval lockedTime = [accessorEndTime timeIntervalSinceDate:accessorStartTime];
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials: lockedTime = %f", lockedTime);
|
|
}];
|
|
MXLogDebug(@"[MXKAccountManager] readAndWriteCredentials:exit %d", coordinatorSuccess);
|
|
}
|
|
|
|
- (NSURL *)accountFileUrl
|
|
{
|
|
return [NSURL fileURLWithPath: [self accountFile]];
|
|
}
|
|
|
|
@end
|