// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2018-2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
import Foundation
import CocoaLumberjackSwift
import Gzip
import ThreemaFramework
@objc class SafeStore: NSObject {
private static let SAFE_DEFAULT_SERVER: String = "https://safe-%02hhx.threema.ch"
private var safeConfigManager: SafeConfigManagerProtocol
private var serverApiConnector: ServerAPIConnector
@objc public let masterKeyLength: Int = 64
private let backupIdLength: Int = 32
private let encryptionKeyLength: Int = 32
enum SafeError: Error {
case invalidMasterKey
case inavlidData
case restoreError(message: String)
case restoreFailed(message: String)
}
init(safeConfigManager: SafeConfigManagerProtocol, serverApiConnector: ServerAPIConnector) {
self.safeConfigManager = safeConfigManager
self.serverApiConnector = serverApiConnector
}
//NSObject thereby not the whole SafeConfigManagerProtocol interface must be like @objc
@objc convenience init(safeConfigManagerAsObject safeConfigManager: NSObject, serverApiConnector: ServerAPIConnector) {
self.init(safeConfigManager: safeConfigManager as! SafeConfigManagerProtocol, serverApiConnector: serverApiConnector)
}
//MARK: - keys and encryption
func createKey(identity: String, password: String) -> [UInt8]? {
let pPassword = UnsafeMutablePointer(strdup(password))
let pSalt = UnsafeMutablePointer(strdup(identity))
var pOut = UnsafeMutablePointer.allocate(capacity: self.masterKeyLength)
defer {
pPassword?.deallocate()
pSalt?.deallocate()
pOut.deallocate()
}
if(getDerivedKey(pPassword, pSalt, pOut) != 0) {
return nil
}
return Array(UnsafeMutableBufferPointer(start: pOut, count: self.masterKeyLength))
}
func getBackupId(key: [UInt8]) -> [UInt8]? {
if key.count == self.masterKeyLength {
return Array(key[0.. [UInt8]? {
if key.count == self.masterKeyLength {
return Array(key[self.masterKeyLength - self.encryptionKeyLength.. [UInt8] {
guard key.count == self.masterKeyLength else {
throw SafeError.invalidMasterKey
}
guard data.count != 0 else {
throw SafeError.inavlidData
}
let backupId = getBackupId(key: key)
let encryptionKey = getEncryptionKey(key: key)
guard backupId != nil && encryptionKey != nil else {
throw SafeError.invalidMasterKey
}
let decryptedData: Data = Data(data)
let compressedData: Data = try decryptedData.gzipped()
if let nonce: Data = generateRandomBytes() {
let crypto: NaClCrypto = NaClCrypto()
let encryptedData: Data = crypto.symmetricEncryptData(compressedData, withKey: Data(bytes: encryptionKey!, count: encryptionKey!.count), nonce: nonce)
var encryptedBackup = Data()
encryptedBackup.append(nonce)
encryptedBackup.append(encryptedData)
return Array(encryptedBackup)
}
else {
throw SafeError.inavlidData
}
}
func decryptBackupData(key: [UInt8], data: [UInt8]) throws -> [UInt8] {
guard key.count == self.masterKeyLength else {
throw SafeError.invalidMasterKey
}
guard data.count != 0 else {
throw SafeError.inavlidData
}
let backupId = getBackupId(key: key)
let encryptionKey = getEncryptionKey(key: key)
guard backupId != nil && encryptionKey != nil else {
throw SafeError.invalidMasterKey
}
let nonce = data[0...23]
let encryptedData = data[24...data.count-1]
let crypto: NaClCrypto = NaClCrypto()
let decryptedData = crypto.symmetricDecryptData(Data(encryptedData), withKey: Data(encryptionKey!), nonce: Data(nonce))!
let unpressedData = try decryptedData.gunzipped()
return Array(unpressedData)
}
public static func dataToHexString(_ data: [UInt8]) -> String {
return data.map { String(format: "%02hhx", $0) }.joined(separator: "")
}
public static func hexStringToData(_ hexString: String) -> Data {
var hex = hexString
var data = Data()
while(hex.count > 0) {
let subIndex = hex.index(hex.startIndex, offsetBy: 2)
let c = String(hex[.. [UInt8] {
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA1($0, CC_LONG(data.count), &digest)
}
return digest
}
private func generateRandomBytes() -> Data? {
var keyData = Data(count: 24)
let result = keyData.withUnsafeMutableBytes {
SecRandomCopyBytes(kSecRandomDefault, 24, $0)
}
if result == errSecSuccess {
return keyData
} else {
return nil
}
}
func isDateOlderThenDays(date: Date?, days: Int) -> Bool {
return date == nil || (date != nil && (date!.addingTimeInterval(TimeInterval(86400 * days)) < Date()))
}
//MARK: - safe server
func getSafeServerToDisplay() -> String {
if let server = self.safeConfigManager.getServer() {
do {
let regexDefaultServer = try NSRegularExpression(pattern: "https://safe-[0-9a-z]{2}.threema.ch")
let regexResult = regexDefaultServer.matches(in: server, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: server.count))
if regexResult.count > 0 {
return BundleUtil.localizedString(forKey: "safe_use_default_server")
}
} catch let error {
print("regex faild to check default server: \(error.localizedDescription)")
}
return self.safeConfigManager.getCustomServer() != nil ? self.safeConfigManager.getCustomServer()! : server
} else {
return BundleUtil.localizedString(forKey: "safe_use_default_server")
}
}
func getSafeServer(key: [UInt8]) -> URL? {
return self.safeConfigManager.getServer() != nil ? URL(string: self.safeConfigManager.getServer()!) : self.getSafeDefaultServer(key: key)!
}
func getSafeDefaultServer(key: [UInt8]) -> URL? {
guard let backupId = getBackupId(key: key) else {
return nil
}
return URL(string: String(format: SafeStore.SAFE_DEFAULT_SERVER, backupId[0]))
}
/// Compose URL like https://user:password@host.com
/// - returns: Server Url with credentials
@objc func composeSafeServerAuth(server: String?, user: String?, password: String?) -> URL? {
guard let server = server, !server.lowercased().starts(with: "http://") else {
return nil
}
let httpProtocol = "https://"
var url: String!
if !server.lowercased().starts(with: httpProtocol) {
url = httpProtocol
url.append(server)
}
else {
url = server
}
let userEncoded = user?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
let passwordEncoded = password?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)
if userEncoded != nil || passwordEncoded != nil {
var concatUrl = url[...String.Index(utf16Offset: httpProtocol.count - 1, in: httpProtocol)]
concatUrl.append(contentsOf: userEncoded != nil ? userEncoded! : "")
concatUrl.append(contentsOf: ":")
concatUrl.append(contentsOf: passwordEncoded != nil ? passwordEncoded! : "")
concatUrl.append(contentsOf: "@")
concatUrl.append(contentsOf: url[String.Index(utf16Offset: httpProtocol.count, in: httpProtocol)...])
return URL(string: String(concatUrl))
}
return URL(string: url)
}
/// Extract and return server url, user and password from https://user:password@host.com
/// - returns: User, Password and Server Url without credentials
func extractSafeServerAuth(server: URL) -> (user: String?, password: String?, server: URL) {
let httpProtocol = "https://"
guard server.absoluteString.starts(with: httpProtocol) else {
return (user: nil, password: nil, server: server)
}
var user: String?
var password: String?
var serverUrl: URL?
if server.user != nil || server.password != nil {
user = server.user?.removingPercentEncoding
password = server.password?.removingPercentEncoding
if let startServerUrl = server.absoluteString.firstIndex(of: "@") {
serverUrl = URL(string: httpProtocol + server.absoluteString[String.Index(utf16Offset: startServerUrl.utf16Offset(in: server.absoluteString)+1, in: server.absoluteString)...].description)
} else {
serverUrl = server
}
} else {
serverUrl = server
}
return (user: user, password: password, server: serverUrl!)
}
//MARK: - back up and restore data
func backupData() -> [UInt8]? {
// get identity
if MyIdentityStore.shared().keySecret() == nil {
return nil
}
let jUser = SafeJsonParser.SafeBackupData.User(privatekey: MyIdentityStore.shared().keySecret().base64EncodedString())
jUser.nickname = MyIdentityStore.shared().pushFromName
// get identity profile picture and its settings
if let profilePicture = MyIdentityStore.shared().profilePicture {
if let imageData = profilePicture["ProfilePicture"] as? Data {
jUser.profilePic = downscaleImageAsBase64(data: imageData, max: 400)
}
}
switch UserSettings.shared().sendProfilePicture {
case SendProfilePictureAll:
jUser.profilePicRelease = ["*"]
case SendProfilePictureContacts:
if let identities = UserSettings.shared().profilePictureContactList {
jUser.profilePicRelease = identities.map({ (identity) -> String in
return "\(identity)"
})
}
default:
jUser.profilePicRelease = nil
}
// get identity linking, backup only email or mobile no is verified
var jLinks = [SafeJsonParser.SafeBackupData.User.Link]()
if let mobileNo = MyIdentityStore.shared().linkedMobileNo,
!MyIdentityStore.shared().linkMobileNoPending {
jLinks.append(SafeJsonParser.SafeBackupData.User.Link(type: "mobile", value: mobileNo))
}
if let email = MyIdentityStore.shared().linkedEmail,
!MyIdentityStore.shared().linkEmailPending {
jLinks.append(SafeJsonParser.SafeBackupData.User.Link(type: "email", value: email))
}
if jLinks.count > 0 {
jUser.links = jLinks
}
// get contacts
var jContacts = [SafeJsonParser.SafeBackupData.Contact]()
for item in ContactStore.shared().allContacts() {
// do not backup me as contact
if let contact = item as? Contact,
contact.identity != MyIdentityStore.shared()?.identity {
let jContact = SafeJsonParser.SafeBackupData.Contact(identity: contact.identity, verification: Int(truncating: contact.verificationLevel))
jContact.publickey = contact.verificationLevel == 2 ? contact.publicKey.base64EncodedString() : nil
jContact.workVerified = contact.workContact != 0
// function to hide contacts is not implemented in iOS; hidden could be nil
// till then set hidden to false
jContact.hidden = false
if let firstname = contact.firstName {
jContact.firstname = firstname
}
if let lastname = contact.lastName {
jContact.lastname = lastname
}
if let nickname = contact.publicNickname {
jContact.nickname = nickname
}
jContacts.append(jContact)
}
}
// get groups
var jGroups = [SafeJsonParser.SafeBackupData.Group]()
let entityManager = EntityManager()
let groupConversations = entityManager.entityFetcher.allGroupConversations()
for item in groupConversations! {
if let groupConversation = item as? Conversation {
if groupConversation.isGroup() {
if let group = GroupProxy(for: groupConversation, entityManager: entityManager) {
let id = SafeStore.dataToHexString(Array(group.groupId))
var creator: String? = nil
if group.isOwnGroup() {
creator = MyIdentityStore.shared().identity
} else if let groupCreator = group.creator {
creator = groupCreator.identity
}
let name = group.name
let members = Array(group.memberIdsIncludingSelf) as! [String]
if creator != nil {
let jGroup = SafeJsonParser.SafeBackupData.Group(id: id, creator: creator!, groupname: name!, members: members, deleted: false)
jGroups.append(jGroup)
}
}
}
}
}
// get settings
let jSettings = SafeJsonParser.SafeBackupData.Settings()
jSettings.syncContacts = UserSettings.shared().syncContacts
jSettings.blockUnknown = UserSettings.shared().blockUnknown
jSettings.readReceipts = UserSettings.shared().sendReadReceipts
jSettings.sendTyping = UserSettings.shared().sendTypingIndicator
jSettings.threemaCalls = UserSettings.shared().enableThreemaCall
jSettings.relayThreemaCalls = UserSettings.shared().alwaysRelayCalls
jSettings.blockedContacts = UserSettings.shared().blacklist.map { (identity) -> String in
return (identity as! String)
}
jSettings.syncExcludedIds = UserSettings.shared().syncExclusionList.map { (identity) -> String in
return (identity as! String)
}
let parser = SafeJsonParser()
var safeBackupData = parser.getSafeBackupData()
safeBackupData.user = jUser;
if jContacts.count > 0 {
safeBackupData.contacts = jContacts
}
if jGroups.count > 0 {
safeBackupData.groups = jGroups
}
safeBackupData.settings = jSettings
//print(parser.getJsonAsString(from: safeBackupData)!)
return parser.getJsonAsBytes(from: safeBackupData)!
}
func restoreData(identity: String, data: [UInt8], onlyIdentity: Bool, completionHandler: @escaping (SafeError?) -> Swift.Void) throws {
//print(String(bytes: data, encoding: .utf8)!)
//Check backup version
let parser = SafeJsonParser()
var safeBackupData: SafeJsonParser.SafeBackupData
do {
safeBackupData = try parser.getSafeBackupData(from: Data(data))
} catch let error {
throw SafeError.restoreFailed(message: error.localizedDescription)
}
guard safeBackupData.info.version == 1 else {
throw SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_version_mismatch"))
}
// Restore identity store
guard let privateKey = safeBackupData.user?.privatekey,
let secretKey = Data(base64Encoded: privateKey) else {
DDLogError("Private key could not be restored")
throw SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found"))
}
MyIdentityStore.shared().restore(fromBackup: identity, withSecretKey: secretKey, onCompletion: {
MyIdentityStore.shared().storeInKeychain()
// Store identity in keychain
self.serverApiConnector.update(MyIdentityStore.shared(), onCompletion: { () in
MyIdentityStore.shared().storeInKeychain()
if let nickname = safeBackupData.user?.nickname {
MyIdentityStore.shared().pushFromName = nickname
}
// Use MDM configuration for linking ID, if exists. Otherwise get linking configuration from Threema Safe backup
let mdmSetup = MDMSetup(setup: true)!
if mdmSetup.existsMdmKey(MDM_KEY_LINKED_PHONE) || mdmSetup.existsMdmKey(MDM_KEY_LINKED_EMAIL) || mdmSetup.readonlyProfile() {
if let createIDPhone = MyIdentityStore.shared()?.createIDPhone,
createIDPhone.count > 0 {
let normalizer: PhoneNumberNormalizer = PhoneNumberNormalizer.sharedInstance()
var prettyMobileNo: NSString? = nil
if let mobileNo = normalizer.phoneNumber(toE164: MyIdentityStore.shared()?.createIDPhone, withDefaultRegion: PhoneNumberNormalizer.userRegion(), prettyFormat: &prettyMobileNo),
mobileNo.count > 0 {
self.link(mobileNo: mobileNo)
}
}
if let createIDEmail = MyIdentityStore.shared()?.createIDEmail,
createIDEmail.count > 0 {
self.link(email: createIDEmail)
}
}
else {
if let links = safeBackupData.user?.links,
links.count > 0 {
links.forEach { (link) in
if link.type == "mobile" {
if var linkMobile = link.value {
if !linkMobile.starts(with: "+") {
linkMobile = "+\(linkMobile)"
}
let numbers = self.localizedMobileNo("+\(linkMobile)")
if let mobileNo = numbers.mobileNo {
self.link(mobileNo: mobileNo)
}
}
}
if link.type == "email",
let email = link.value {
self.link(email: email)
}
}
}
}
// Restore profile picture
if let profilePic = safeBackupData.user?.profilePic,
let profilePicData = Data(base64Encoded: profilePic) {
let profilePicture: NSMutableDictionary = MyIdentityStore.shared().profilePicture != nil ? MyIdentityStore.shared().profilePicture : NSMutableDictionary(dictionary: ["ProfilePicture": profilePicData])
MyIdentityStore.shared().profilePicture = profilePicture
}
if onlyIdentity {
self.setProfilePictureRequestList()
completionHandler(nil)
} else {
self.restoreUserSettings(safeBackupData: safeBackupData)
self.restoreContactsAndGroups(identity: identity, safeBackupData: safeBackupData, completionHandler: completionHandler)
}
}, onError: { (error) in
DDLogError("Safe restore error:update identity store failed")
completionHandler(SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found")))
})
}) { (error) in
DDLogError("Safe restore error:update restore identity store failed")
completionHandler(SafeError.restoreFailed(message: BundleUtil.localizedString(forKey: "safe_no_backup_found")))
}
}
private func restoreUserSettings(safeBackupData: SafeJsonParser.SafeBackupData) {
let userSettings = UserSettings.shared()!
if let profilePicRelease = safeBackupData.user?.profilePicRelease {
if profilePicRelease.count == 1 && profilePicRelease[0] == "*" {
userSettings.sendProfilePicture = SendProfilePictureAll
}
else if profilePicRelease.count == 1 && profilePicRelease[0] == nil {
userSettings.sendProfilePicture = SendProfilePictureNone
} else if profilePicRelease.count > 0 {
userSettings.sendProfilePicture = SendProfilePictureContacts
userSettings.profilePictureContactList = profilePicRelease as [Any]
} else {
userSettings.sendProfilePicture = SendProfilePictureNone
}
} else {
userSettings.sendProfilePicture = SendProfilePictureNone
}
// Restore settings, contacts and groups
let mdmSetup = MDMSetup(setup: true)!
let settings = safeBackupData.settings!
userSettings.safeIntroShown = true
if !mdmSetup.existsMdmKey(MDM_KEY_CONTACT_SYNC) {
userSettings.syncContacts = settings.syncContacts
}
userSettings.blockUnknown = settings.blockUnknown ?? false
userSettings.sendReadReceipts = settings.readReceipts ?? true
userSettings.sendTypingIndicator = settings.sendTyping ?? true
userSettings.enableThreemaCall = settings.threemaCalls ?? true
userSettings.alwaysRelayCalls = settings.relayThreemaCalls ?? false
if let blockedContacts = settings.blockedContacts {
userSettings.blacklist = NSOrderedSet(array: blockedContacts)
}
if let syncExcludedIds = settings.syncExcludedIds {
userSettings.syncExclusionList = syncExcludedIds
}
}
private func restoreContactsAndGroups(identity: String, safeBackupData: SafeJsonParser.SafeBackupData, completionHandler: @escaping (SafeError?) -> Swift.Void) {
let entityManager = EntityManager()
if let bContacts = safeBackupData.contacts {
var fetchIdentities = [String]()
bContacts.forEach { (bContact) in
if let identity = bContact.identity {
fetchIdentities.append(identity)
}
}
self.serverApiConnector.fetchBulkIdentityInfo(fetchIdentities, onCompletion: { (identities, publicKeys, featureMasks, states, types) in
var index = 0
for id in identities! {
var bContact: SafeJsonParser.SafeBackupData.Contact?
if bContacts.contains(where: { (c) -> Bool in
if let identity = c.identity,
identity.uppercased() == id as! String {
bContact = c
return true
}
return false
}) {
// Do not restore me as contact
if let bContact = bContact,
let contactIdentity = bContact.identity,
contactIdentity.uppercased() != identity.uppercased() {
if let publicKey = publicKeys?[index] as? Data {
// check is contact already stored, could be when Threema MDM sync was running (it's a bug, should not before restore is finished)
if let contact = entityManager.entityFetcher.contact(forId: bContact.identity) {
entityManager.performSyncBlockAndSafe({
contact.verificationLevel = Int32(bContact.verification ?? 0) as NSNumber
contact.firstName = bContact.firstname
contact.lastName = bContact.lastname
contact.publicNickname = bContact.nickname
})
}
else {
entityManager.performSyncBlockAndSafe({
if let contact = entityManager.entityCreator.contact() {
contact.identity = bContact.identity?.uppercased()
contact.verificationLevel = Int32(bContact.verification ?? 0) as NSNumber
contact.firstName = bContact.firstname
contact.lastName = bContact.lastname
contact.publicNickname = bContact.nickname
// function to hide contacts is not implemented in iOS; hidden could be nil
// till then set hidden to false
contact.hidden = 0
if let workVerified = bContact.workVerified {
contact.workContact = workVerified ? 1 : 0
} else {
contact.workContact = 0
}
contact.publicKey = publicKey
if let featureMasks = featureMasks,
let featureMask = featureMasks[index] as? Int {
contact.setFeatureMask(NSNumber(integerLiteral: featureMask))
}
if let states = states,
let state = states[index] as? Int {
contact.state = NSNumber(integerLiteral: state)
}
if let types = types,
let type = types[index] as? Int {
if type == 1 && contact.identity != nil {
let workIdentities = NSMutableOrderedSet.init(orderedSet: UserSettings.shared().workIdentities)
if !workIdentities.contains(contact.identity!) {
workIdentities.add(contact.identity!)
UserSettings.shared().workIdentities = workIdentities
}
}
}
}
})
}
}
}
}
index += 1
}
if index > 0 {
self.setProfilePictureRequestList()
self.restoreGroups(identity: identity, safeBackupData: safeBackupData, entityManager: entityManager)
}
completionHandler(nil)
}) { (error) in
if let error = error {
DDLogError("Safe error while request identities:\(error.localizedDescription)")
completionHandler(SafeError.restoreError(message: BundleUtil.localizedString(forKey: "safe_restore_error")))
}
}
}
}
private func restoreGroups(identity: String, safeBackupData: SafeJsonParser.SafeBackupData, entityManager: EntityManager) {
if let bGroups = safeBackupData.groups {
for bGroup in bGroups {
if let creatorIdentity = bGroup.creator {
var creatorContact: Contact?
if creatorIdentity.uppercased() != identity.uppercased() {
creatorContact = entityManager.entityFetcher.contact(forId: creatorIdentity.uppercased())
}
if creatorIdentity.uppercased() == identity.uppercased() || creatorContact != nil {
var conversation: Conversation?
if let groupId = bGroup.id,
let groupMembers = bGroup.members {
entityManager.performSyncBlockAndSafe({
conversation = entityManager.entityCreator.conversation()
conversation?.groupId = SafeStore.hexStringToData(groupId)
if groupMembers.contains(where: { (member) -> Bool in
member.uppercased().contains(identity.uppercased())
}) {
conversation?.groupMyIdentity = identity.uppercased()
}
conversation?.contact = creatorContact
conversation?.groupName = bGroup.groupname
})
if let conversation = conversation {
//sync restored group
if let group = GroupProxy(for: conversation, entityManager: entityManager) {
group.adminAddMembers(fromBackup: Set(groupMembers), entityManager: entityManager)
//sync only group is active
if (group.isSelfMember()) {
if creatorIdentity.uppercased() == identity.uppercased() {
group.syncGroupInfoToAll()
} else {
GroupProxy.sendSyncRequest(withGroupId: group.groupId, creator: group.creator.identity)
}
}
}
}
}
else {
DDLogWarn("Safe restore group id or members missing")
}
}
else {
DDLogWarn("Safe restore group creator not found")
}
} else {
DDLogWarn("Safe restore group has no creator")
}
}
}
}
private func setProfilePictureRequestList() {
if let userSettings = UserSettings.shared() {
var profilePicRequest = [String]()
let entityManager = EntityManager()
if let contacts = entityManager.entityFetcher.allContacts() as? [Contact] {
for contact in contacts {
if contact.identity != "ECHOECHO" && contact.identity != MyIdentityStore.shared()?.identity {
profilePicRequest.append(contact.identity)
}
}
}
userSettings.profilePictureRequestList = profilePicRequest
}
}
private func downscaleImageAsBase64(data: Data, max: CGFloat) -> String? {
if let image: UIImage = UIImage(data: data) {
// downscale profile picture to max. size and quality 60, if is necessary
if image.size.height > max || image.size.width > max {
var newHeight: CGFloat
var newWidth: CGFloat
if image.size.height > image.size.width {
newHeight = max
newWidth = (newHeight / image.size.height) * image.size.width
} else {
newWidth = max
newHeight = (newWidth / image.size.width) * image.size.height
}
let resizedImage = resizeImage(image: image, size: CGSize(width: newWidth, height: newHeight))!
let resizedData = resizedImage.jpegData(compressionQuality: 0.6)
return resizedData?.base64EncodedString()
} else {
return data.base64EncodedString()
}
} else {
return nil;
}
}
private func resizeImage(image: UIImage, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContext(size)
image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
func localizedMobileNo(_ mobileNo: String) -> (mobileNo: String?, prettyMobileNo: String?) {
if mobileNo.count > 0 {
let normalizer: PhoneNumberNormalizer = PhoneNumberNormalizer.sharedInstance()
var prettyMobileNo: NSString?
let localMobileNo = normalizer.phoneNumber(toE164: mobileNo, withDefaultRegion: PhoneNumberNormalizer.userRegion(), prettyFormat: &prettyMobileNo)
return (mobileNo: localMobileNo, prettyMobileNo: prettyMobileNo as String?)
}
return (mobileNo: nil, prettyMobileNo: nil)
}
private func link(mobileNo: String) {
self.serverApiConnector.linkMobileNo(with: MyIdentityStore.shared(), mobileNo: mobileNo, onCompletion: { (linked) in
DDLogInfo("Safe restore linking mobile no with identity successfull")
}, onError: { (error) in
DDLogError("Safe restore linking mobile no with identity failed")
})
}
private func link(email: String) {
self.serverApiConnector.linkEmail(with: MyIdentityStore.shared(), email: email, onCompletion: { (linked) in
DDLogInfo("Safe restore linking email with identity successfull")
}, onError: { (error) in
DDLogError("Safe restore linking email with identity failed")
})
}
}