chatsecureios / ChatSecure / Classes / View Controllers / UserProfileViewController.swift @ 8d76e2e3
History | View | Annotate | Download (19.7 KB)
1 |
// |
---|---|
2 |
// OMEMODeviceVerificationViewController.swift |
3 |
// ChatSecure |
4 |
// |
5 |
// Created by Chris Ballinger on 10/13/16. |
6 |
// Copyright © 2016 Chris Ballinger. All rights reserved. |
7 |
// |
8 |
|
9 |
import UIKit |
10 |
import XLForm |
11 |
import YapDatabase |
12 |
import OTRAssets |
13 |
|
14 |
open class UserProfileViewController: XLFormViewController { |
15 |
|
16 |
@objc open var completionBlock: (()->Void)? |
17 |
|
18 |
// Crypto Chooser row tags |
19 |
open static let DefaultRowTag = "DefaultRowTag" |
20 |
open static let PlaintextRowTag = "PlaintextRowTag" |
21 |
open static let OTRRowTag = "OTRRowTag" |
22 |
open static let OMEMORowTag = "OMEMORowTag" |
23 |
open static let ShowAdvancedCryptoSettingsTag = "ShowAdvancedCryptoSettingsTag" |
24 |
|
25 |
open let accountKey:String |
26 |
open var connection: YapDatabaseConnection |
27 |
|
28 |
lazy var signalCoordinator:OTROMEMOSignalCoordinator? = { |
29 |
var account:OTRAccount? = nil |
30 |
self.connection.read { (transaction) in |
31 |
account = OTRAccount.fetchObject(withUniqueID: self.accountKey, transaction: transaction) |
32 |
} |
33 |
|
34 |
guard let acct = account else { |
35 |
return nil |
36 |
} |
37 |
|
38 |
guard let xmpp = OTRProtocolManager.sharedInstance().protocol(for: acct) as? XMPPManager else { |
39 |
return nil |
40 |
} |
41 |
return xmpp.omemoSignalCoordinator |
42 |
}() |
43 |
|
44 |
@objc public init(accountKey:String, connection: YapDatabaseConnection, form: XLFormDescriptor) { |
45 |
self.accountKey = accountKey |
46 |
self.connection = connection |
47 |
super.init(nibName: nil, bundle: nil) |
48 |
|
49 |
self.form = form |
50 |
} |
51 |
|
52 |
required public init!(coder aDecoder: NSCoder!) { |
53 |
fatalError("init(coder:) has not been implemented") |
54 |
} |
55 |
|
56 |
open override func viewDidLoad() { |
57 |
// gotta register cell before super |
58 |
OMEMODeviceFingerprintCell.registerCellClass(OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
59 |
UserInfoProfileCell.registerCellClass(UserInfoProfileCell.defaultRowDescriptorType()) |
60 |
|
61 |
super.viewDidLoad() |
62 |
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed(_:))) |
63 |
self.tableView.allowsMultipleSelectionDuringEditing = false |
64 |
|
65 |
// Overriding superclass behaviour. This prevents the red icon on left of cell for deletion. Just want swipe to delete on device/fingerprint. |
66 |
self.tableView.setEditing(false, animated: false) |
67 |
} |
68 |
|
69 |
open override func didReceiveMemoryWarning() { |
70 |
super.didReceiveMemoryWarning() |
71 |
// Dispose of any resources that can be recreated. |
72 |
} |
73 |
|
74 |
@objc open func doneButtonPressed(_ sender: AnyObject?) { |
75 |
var devicesToSave: [OTROMEMODevice] = [] |
76 |
var otrFingerprintsToSave: [OTRFingerprint] = [] |
77 |
for (_, value) in form.formValues() { |
78 |
switch value { |
79 |
case let device as OTROMEMODevice: |
80 |
devicesToSave.append(device) |
81 |
case let fingerprint as OTRFingerprint: |
82 |
otrFingerprintsToSave.append(fingerprint) |
83 |
default: |
84 |
break |
85 |
} |
86 |
} |
87 |
OTRDatabaseManager.sharedInstance().readWriteDatabaseConnection?.asyncReadWrite({ (t: YapDatabaseReadWriteTransaction) in |
88 |
for viewedDevice in devicesToSave { |
89 |
if var device = t.object(forKey: viewedDevice.uniqueId, inCollection: OTROMEMODevice.collection) as? OTROMEMODevice { |
90 |
device = device.copy() as! OTROMEMODevice |
91 |
device.trustLevel = viewedDevice.trustLevel |
92 |
|
93 |
if (device.trustLevel == .trustedUser && device.isExpired()) { |
94 |
device.lastSeenDate = viewedDevice.lastSeenDate |
95 |
} |
96 |
|
97 |
device.save(with: t) |
98 |
} |
99 |
} |
100 |
}) |
101 |
|
102 |
otrFingerprintsToSave.forEach { (fingerprint) in |
103 |
OTRProtocolManager.sharedInstance().encryptionManager.save(fingerprint) |
104 |
} |
105 |
if let completion = self.completionBlock { |
106 |
completion() |
107 |
} |
108 |
dismiss(animated: true, completion: nil) |
109 |
} |
110 |
|
111 |
fileprivate func isAbleToDeleteCellAtIndexPath(_ indexPath:IndexPath) -> Bool { |
112 |
if let rowDescriptor = self.form.formRow(atIndex: indexPath) { |
113 |
|
114 |
switch rowDescriptor.value { |
115 |
case let device as OTROMEMODevice: |
116 |
if let myBundle = self.signalCoordinator?.fetchMyBundle() { |
117 |
// This is only used to compare so we don't allow delete UI on our device |
118 |
let thisDeviceYapKey = OTROMEMODevice.yapKey(withDeviceId: NSNumber(value: myBundle.deviceId as UInt32), parentKey: self.accountKey, parentCollection: OTRAccount.collection) |
119 |
if device.uniqueId != thisDeviceYapKey { |
120 |
return true |
121 |
} |
122 |
} |
123 |
case let fingerprint as OTRFingerprint: |
124 |
if (fingerprint.accountName != fingerprint.username) { |
125 |
return true |
126 |
} |
127 |
default: |
128 |
break |
129 |
} |
130 |
} |
131 |
return false |
132 |
} |
133 |
|
134 |
fileprivate func performEdit(_ action:UITableViewCellEditingStyle, indexPath:IndexPath) { |
135 |
if ( action == .delete ) { |
136 |
if let rowDescriptor = self.form.formRow(atIndex: indexPath) { |
137 |
rowDescriptor.sectionDescriptor.removeFormRow(rowDescriptor) |
138 |
switch rowDescriptor.value { |
139 |
case let device as OTROMEMODevice: |
140 |
|
141 |
self.signalCoordinator?.removeDevice([device], completion: { (success) in |
142 |
|
143 |
}) |
144 |
break |
145 |
case let fingerprint as OTRFingerprint: |
146 |
do { |
147 |
try OTRProtocolManager.sharedInstance().encryptionManager.otrKit.delete(fingerprint) |
148 |
} catch { |
149 |
|
150 |
} |
151 |
break |
152 |
default: |
153 |
break |
154 |
} |
155 |
} |
156 |
} |
157 |
} |
158 |
|
159 |
open static func cryptoChooserRows(_ buddy: OTRBuddy, connection: YapDatabaseConnection) -> [XLFormRowDescriptor] { |
160 |
|
161 |
let bestAvailableRow = XLFormRowDescriptor(tag: DefaultRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Best_Available()) |
162 |
let plaintextOnlyRow = XLFormRowDescriptor(tag: PlaintextRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Plaintext_Only()) |
163 |
let plaintextOtrRow = XLFormRowDescriptor(tag: PlaintextRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: Plaintext_Opportunistic_OTR()) |
164 |
let otrRow = XLFormRowDescriptor(tag: OTRRowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: "OTR") |
165 |
let omemoRow = XLFormRowDescriptor(tag: OMEMORowTag, rowType: XLFormRowDescriptorTypeBooleanCheck, title: "OMEMO") |
166 |
|
167 |
var hasDevices = false |
168 |
|
169 |
connection.read { (transaction: YapDatabaseReadTransaction) in |
170 |
if OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: type(of: buddy).collection, transaction: transaction).count > 0 { |
171 |
hasDevices = true |
172 |
} |
173 |
} |
174 |
|
175 |
if (!hasDevices) { |
176 |
omemoRow.disabled = NSNumber(value: true as Bool) |
177 |
} |
178 |
|
179 |
let trueValue = NSNumber(value: true as Bool) |
180 |
switch buddy.preferredSecurity { |
181 |
case .plaintextOnly: |
182 |
plaintextOnlyRow.value = trueValue |
183 |
break |
184 |
case .bestAvailable: |
185 |
bestAvailableRow.value = trueValue |
186 |
break |
187 |
case .OTR: |
188 |
otrRow.value = trueValue |
189 |
break |
190 |
case .OMEMO: |
191 |
omemoRow.value = trueValue |
192 |
break |
193 |
case .omemOandOTR: |
194 |
omemoRow.value = trueValue |
195 |
break |
196 |
case .plaintextWithOTR: |
197 |
plaintextOtrRow.value = trueValue |
198 |
} |
199 |
|
200 |
let formRows = [bestAvailableRow, plaintextOnlyRow, plaintextOtrRow, otrRow, omemoRow] |
201 |
|
202 |
var currentRow: XLFormRowDescriptor? = nil |
203 |
var rowsToDeselect: NSMutableSet = NSMutableSet() |
204 |
let onChangeBlock = { (oldValue: Any?, newValue: Any?, rowDescriptor: XLFormRowDescriptor) in |
205 |
// Prevent infinite loops |
206 |
// Allow deselection |
207 |
if rowsToDeselect.count > 0 { |
208 |
rowsToDeselect.remove(rowDescriptor) |
209 |
return |
210 |
} |
211 |
if currentRow != nil { |
212 |
return |
213 |
} |
214 |
currentRow = rowDescriptor |
215 |
|
216 |
// Don't allow user to unselect a true value |
217 |
if (newValue as AnyObject?)?.boolValue == false { |
218 |
rowDescriptor.value = NSNumber(value: true as Bool) |
219 |
currentRow = nil |
220 |
return |
221 |
} |
222 |
|
223 |
// Deselect other rows |
224 |
rowsToDeselect = NSMutableSet(array: formRows.filter({ $0 != rowDescriptor })) |
225 |
for row in rowsToDeselect { |
226 |
guard let row = row as? XLFormRowDescriptor else { |
227 |
continue |
228 |
} |
229 |
let newValue = NSNumber(value: false as Bool) |
230 |
row.value = newValue |
231 |
// Wow that's janky |
232 |
(row.sectionDescriptor.formDescriptor.delegate as! XLFormViewControllerDelegate).reloadFormRow!(row) |
233 |
} |
234 |
|
235 |
var preferredSecurity: OTRSessionSecurity = .bestAvailable |
236 |
if (plaintextOnlyRow.value as AnyObject?)?.boolValue == true { |
237 |
preferredSecurity = .plaintextOnly |
238 |
} else if (otrRow.value as AnyObject?)?.boolValue == true { |
239 |
preferredSecurity = .OTR |
240 |
} else if (omemoRow.value as AnyObject?)?.boolValue == true { |
241 |
preferredSecurity = .OMEMO |
242 |
} else if (bestAvailableRow.value as AnyObject?)?.boolValue == true { |
243 |
preferredSecurity = .bestAvailable |
244 |
} else if (plaintextOtrRow.value as AnyObject?)?.boolValue == true { |
245 |
preferredSecurity = .plaintextWithOTR |
246 |
} |
247 |
|
248 |
OTRDatabaseManager.sharedInstance().readWriteDatabaseConnection?.readWrite({ (transaction: YapDatabaseReadWriteTransaction) in |
249 |
guard var buddy = transaction.object(forKey: buddy.uniqueId, inCollection: type(of: buddy).collection) as? OTRBuddy else { |
250 |
return |
251 |
} |
252 |
guard let account = buddy.account(with: transaction) else { |
253 |
return |
254 |
} |
255 |
buddy = buddy.copy() as! OTRBuddy |
256 |
buddy.preferredSecurity = preferredSecurity |
257 |
buddy.save(with: transaction) |
258 |
// Cancel OTR session if plaintext or omemo only |
259 |
if (preferredSecurity == .plaintextOnly || preferredSecurity == .OMEMO) { |
260 |
OTRProtocolManager.sharedInstance().encryptionManager.otrKit.disableEncryption(withUsername: buddy.username, accountName: account.username, protocol: account.protocolTypeString()) |
261 |
} |
262 |
}) |
263 |
currentRow = nil |
264 |
} |
265 |
|
266 |
for row in formRows { |
267 |
row.onChangeBlock = onChangeBlock |
268 |
} |
269 |
|
270 |
return formRows |
271 |
} |
272 |
|
273 |
//MARK UITableView Delegate overrides |
274 |
|
275 |
open override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { |
276 |
if self.isAbleToDeleteCellAtIndexPath(indexPath) { |
277 |
return true |
278 |
} |
279 |
return false |
280 |
} |
281 |
|
282 |
open override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { |
283 |
if self.isAbleToDeleteCellAtIndexPath(indexPath) { |
284 |
return .delete |
285 |
} |
286 |
return .none |
287 |
} |
288 |
|
289 |
open override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { |
290 |
|
291 |
self.performEdit(editingStyle, indexPath: indexPath) |
292 |
} |
293 |
|
294 |
|
295 |
@objc open static func profileFormDescriptorForAccount(_ account: OTRAccount, buddies: [OTRBuddy], connection: YapDatabaseConnection) -> XLFormDescriptor { |
296 |
let form = XLFormDescriptor(title: Profile_String()) |
297 |
|
298 |
let yourProfileSection = XLFormSectionDescriptor.formSection(withTitle: Me_String()) |
299 |
let yourProfileRow = XLFormRowDescriptor(tag: account.uniqueId, rowType: UserInfoProfileCell.defaultRowDescriptorType()) |
300 |
yourProfileRow.value = account |
301 |
yourProfileSection.addFormRow(yourProfileRow) |
302 |
|
303 |
guard let xmpp = OTRProtocolManager.sharedInstance().protocol(for: account) as? XMPPManager else { |
304 |
return form |
305 |
} |
306 |
guard let myBundle = xmpp.omemoSignalCoordinator?.fetchMyBundle() else { |
307 |
return form |
308 |
} |
309 |
let thisDevice = OTROMEMODevice(deviceId: NSNumber(value: myBundle.deviceId as UInt32), trustLevel: .trustedUser, parentKey: account.uniqueId, parentCollection: type(of: account).collection, publicIdentityKeyData: myBundle.identityKey, lastSeenDate: Date()) |
310 |
var ourDevices: [OTROMEMODevice] = [] |
311 |
connection.read { (transaction: YapDatabaseReadTransaction) in |
312 |
ourDevices = OTROMEMODevice.allDevices(forParentKey: account.uniqueId, collection: type(of: account).collection, transaction: transaction) |
313 |
} |
314 |
|
315 |
|
316 |
let ourFilteredDevices = ourDevices.filter({ (device: OTROMEMODevice) -> Bool in |
317 |
return device.uniqueId != thisDevice.uniqueId |
318 |
}) |
319 |
|
320 |
// TODO - Sort ourDevices and theirDevices by lastSeen |
321 |
|
322 |
let addDevicesToSection: ([OTROMEMODevice], XLFormSectionDescriptor) -> Void = { devices, section in |
323 |
for device in devices { |
324 |
guard let _ = device.publicIdentityKeyData else { |
325 |
continue |
326 |
} |
327 |
let row = XLFormRowDescriptor(tag: device.uniqueId, rowType: OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
328 |
row.value = device.copy() |
329 |
|
330 |
// Don't allow editing of your own device |
331 |
if device.uniqueId == thisDevice.uniqueId { |
332 |
row.disabled = true |
333 |
} |
334 |
|
335 |
section.addFormRow(row) |
336 |
} |
337 |
} |
338 |
|
339 |
let otrKit = OTRProtocolManager.sharedInstance().encryptionManager.otrKit |
340 |
let allFingerprints = otrKit.allFingerprints() |
341 |
let myFingerprint = otrKit.fingerprint(forAccountName: account.username, protocol: account.protocolTypeString()) |
342 |
let addFingerprintsToSection: ([OTRFingerprint], XLFormSectionDescriptor) -> Void = { fingerprints, section in |
343 |
for fingerprint in fingerprints { |
344 |
let row = XLFormRowDescriptor(tag: (fingerprint.fingerprint as NSData).otr_hexString(), rowType: OMEMODeviceFingerprintCell.defaultRowDescriptorType()) |
345 |
if let myFingerprint = myFingerprint { |
346 |
if (fingerprint === myFingerprint) { |
347 |
// We implicitly trust ourselves with OTR |
348 |
row.disabled = true |
349 |
} else { |
350 |
row.disabled = false |
351 |
} |
352 |
} |
353 |
|
354 |
row.value = fingerprint |
355 |
|
356 |
section.addFormRow(row) |
357 |
} |
358 |
} |
359 |
|
360 |
var allMyDevices: [OTROMEMODevice] = [] |
361 |
allMyDevices.append(thisDevice) |
362 |
allMyDevices.append(contentsOf: ourFilteredDevices) |
363 |
addDevicesToSection(allMyDevices, yourProfileSection) |
364 |
|
365 |
var theirSections: [XLFormSectionDescriptor] = [] |
366 |
|
367 |
if let myFingerprint = myFingerprint { |
368 |
addFingerprintsToSection([myFingerprint], yourProfileSection) |
369 |
} |
370 |
|
371 |
// Add section for each buddy's device |
372 |
for buddy in buddies { |
373 |
let theirSection = XLFormSectionDescriptor.formSection(withTitle: buddy.username) |
374 |
|
375 |
let buddyRow = XLFormRowDescriptor(tag: buddy.uniqueId, rowType: UserInfoProfileCell.defaultRowDescriptorType()) |
376 |
buddyRow.value = buddy |
377 |
theirSection.addFormRow(buddyRow) |
378 |
var theirDevices: [OTROMEMODevice] = [] |
379 |
connection.read({ (transaction: YapDatabaseReadTransaction) in |
380 |
theirDevices = OTROMEMODevice.allDevices(forParentKey: buddy.uniqueId, collection: type(of: buddy).collection, transaction: transaction) |
381 |
}) |
382 |
let theirFingerprints = allFingerprints.filter({ (fingerprint: OTRFingerprint) -> Bool in |
383 |
return fingerprint.username == buddy.username && |
384 |
fingerprint.accountName == account.username |
385 |
}) |
386 |
|
387 |
addDevicesToSection(theirDevices, theirSection) |
388 |
addFingerprintsToSection(theirFingerprints, theirSection) |
389 |
theirSections.append(theirSection) |
390 |
} |
391 |
|
392 |
|
393 |
var sectionsToAdd: [XLFormSectionDescriptor] = [] |
394 |
sectionsToAdd.append(contentsOf: theirSections) |
395 |
|
396 |
// cryptoChooserRows is only meaningful for 1:1 conversations at the moment |
397 |
if buddies.count == 1 { |
398 |
let buddy = buddies.first! |
399 |
let cryptoSection = XLFormSectionDescriptor.formSection(withTitle: Advanced_Encryption_Settings()) |
400 |
cryptoSection.footerTitle = Advanced_Crypto_Warning() |
401 |
let showAdvancedSwitch = XLFormRowDescriptor.init(tag: self.ShowAdvancedCryptoSettingsTag, rowType: XLFormRowDescriptorTypeBooleanSwitch, title: Show_Advanced_Encryption_Settings()) |
402 |
showAdvancedSwitch.value = NSNumber(value: false as Bool) |
403 |
let cryptoChooser = cryptoChooserRows(buddy, connection: connection) |
404 |
for row in cryptoChooser { |
405 |
cryptoSection.addFormRow(row) |
406 |
} |
407 |
cryptoSection.hidden = "$\(ShowAdvancedCryptoSettingsTag)==0" |
408 |
let buddySection = theirSections.first! |
409 |
buddySection.addFormRow(showAdvancedSwitch) |
410 |
sectionsToAdd.append(cryptoSection) |
411 |
} |
412 |
|
413 |
sectionsToAdd.append(yourProfileSection) |
414 |
|
415 |
for section in sectionsToAdd { |
416 |
if section.formRows.count > 0 { |
417 |
form.addFormSection(section) |
418 |
} |
419 |
} |
420 |
|
421 |
return form |
422 |
} |
423 |
|
424 |
// MARK: - UITableViewDelegate |
425 |
|
426 |
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
427 |
super.tableView(tableView, didSelectRowAt: indexPath) |
428 |
tableView.deselectRow(at: indexPath, animated: true) |
429 |
guard let cell = self.tableView(tableView, cellForRowAt: indexPath) as? OMEMODeviceFingerprintCell else { |
430 |
return |
431 |
} |
432 |
var fingerprint = "" |
433 |
var username = "" |
434 |
var cryptoType = "" |
435 |
if let device = cell.rowDescriptor.value as? OTROMEMODevice { |
436 |
cryptoType = "OMEMO" |
437 |
fingerprint = device.humanReadableFingerprint |
438 |
self.connection.read({ (transaction) in |
439 |
if let buddy = transaction.object(forKey: device.parentKey, inCollection: device.parentCollection) as? OTRBuddy { |
440 |
username = buddy.username |
441 |
} |
442 |
}) |
443 |
} |
444 |
if let otrFingerprint = cell.rowDescriptor.value as? OTRFingerprint { |
445 |
cryptoType = "OTR" |
446 |
fingerprint = (otrFingerprint.fingerprint as NSData).humanReadableFingerprint() |
447 |
username = otrFingerprint.username |
448 |
} |
449 |
if fingerprint.count == 0 || username.count == 0 || cryptoType.count == 0 { |
450 |
return |
451 |
} |
452 |
let stringToShare = "\(username): \(cryptoType) \(fingerprint)" |
453 |
let activityViewController = UIActivityViewController(activityItems: [stringToShare], applicationActivities: nil) |
454 |
if let ppc = activityViewController.popoverPresentationController { |
455 |
ppc.sourceView = cell |
456 |
ppc.sourceRect = cell.frame |
457 |
} |
458 |
present(activityViewController, animated: true, completion: nil) |
459 |
} |
460 |
|
461 |
} |