Skip to content

Commit

Permalink
Adds new debug screen to view raw credentials data (#3400)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/1201462886803403/1207890203714429/f
Tech Design URL:
CC:

Description:
New debug screen to display raw data from the credentials DB to help diagnose credential sync issues between platforms
  • Loading branch information
amddg44 authored Oct 7, 2024
1 parent 3606f6e commit a02f5b8
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 12 deletions.
14 changes: 13 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@
C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */; };
C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */; };
C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */; };
C1E42C7B2C5CD8AE00509204 /* AutofillCredentialsDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */; };
C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */; };
C1EA86622C74CB8B00E8604D /* SyncPromoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */; };
C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F341C42A6924000032057B /* EmailAddressPromptView.swift */; };
Expand Down Expand Up @@ -2705,6 +2706,7 @@
C1CDA31D2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManagerTests.swift; sourceTree = "<group>"; };
C1D21E2C293A5965006E5A05 /* AutofillLoginSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSession.swift; sourceTree = "<group>"; };
C1D21E2E293A599C006E5A05 /* AutofillLoginSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginSessionTests.swift; sourceTree = "<group>"; };
C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillCredentialsDebugViewController.swift; sourceTree = "<group>"; };
C1EA865F2C74CB6C00E8604D /* SyncPromoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoView.swift; sourceTree = "<group>"; };
C1EA86612C74CB8B00E8604D /* SyncPromoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPromoViewModel.swift; sourceTree = "<group>"; };
C1F341C42A6924000032057B /* EmailAddressPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressPromptView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4386,9 +4388,9 @@
858566F1252E55AE007501B8 /* Debug */ = {
isa = PBXGroup;
children = (
C1047CA32CA59BDC00B916FD /* Autofill */,
6FB1FE9C2C24D4060075B68B /* NewTabPageSectionsDebugView */,
9F9A922F2C86AACB001D036D /* OnboardingDebugView */,
C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */,
858566E7252E4F56007501B8 /* Debug.storyboard */,
D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */,
8590CB602684D0600089F6BF /* CookieDebugViewController.swift */,
Expand Down Expand Up @@ -5040,6 +5042,15 @@
name = "Feature Visibility";
sourceTree = "<group>";
};
C1047CA32CA59BDC00B916FD /* Autofill */ = {
isa = PBXGroup;
children = (
C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */,
C1E42C7A2C5CD8AD00509204 /* AutofillCredentialsDebugViewController.swift */,
);
name = Autofill;
sourceTree = "<group>";
};
C14882D627F2010700D59F0C /* ImportExport */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -7374,6 +7385,7 @@
C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */,
83134D7D20E2D725006CE65D /* FeedbackSender.swift in Sources */,
B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */,
C1E42C7B2C5CD8AE00509204 /* AutofillCredentialsDebugViewController.swift in Sources */,
314C92BA27C3E7CB0042EC96 /* QuickLookContainerViewController.swift in Sources */,
855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */,
CB258D1229A4F24900DEBA24 /* ConfigurationManager.swift in Sources */,
Expand Down
246 changes: 246 additions & 0 deletions DuckDuckGo/AutofillCredentialsDebugViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
//
// AutofillCredentialsDebugViewController.swift
// DuckDuckGo
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import UIKit
import BrowserServicesKit
import Common
import OSLog

class AutofillCredentialsDebugViewController: UITableViewController {

struct DisplayCredentials {

let tld: TLD
let autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher
var credential: SecureVaultModels.WebsiteCredentials

var accountId: String {
credential.account.id ?? ""
}

var accountTitle: String {
credential.account.title ?? ""
}

var displayTitle: String {
credential.account.name(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher)
}

var websiteUrl: String {
credential.account.domain ?? ""
}

var domain: String {
guard let url = credential.account.domain,
let urlComponents = autofillDomainNameUrlMatcher.normalizeSchemeForAutofill(url),
let domain = urlComponents.eTLDplus1(tld: tld) ?? urlComponents.host else {
return ""
}
return domain
}

var username: String {
credential.account.username ?? ""
}

var displayPassword: String {
return credential.password.flatMap { String(data: $0, encoding: .utf8) } ?? "FAILED TO DECODE PW"
}

var notes: String {
credential.account.notes ?? ""
}

var created: String {
"\(credential.account.created)"
}

var lastUpdated: String {
"\(credential.account.lastUpdated)"
}

var lastUsed: String {
credential.account.lastUsed != nil ? "\(credential.account.lastUsed!)" : ""
}

var signature: String {
credential.account.signature ?? ""
}
}

private let tld: TLD = AppDependencyProvider.shared.storageCache.tld
private let autofillDomainNameUrlMatcher: AutofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher()
private var credentials: [DisplayCredentials] = []
private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason)

override func viewDidLoad() {
super.viewDidLoad()

beginAuthentication()
}

private func beginAuthentication() {
authenticator.authenticate { [weak self] error in
if error == nil {
self?.reloadCredentials()
}
}
}

private func reloadCredentials() {
credentials = loadCredentials()
tableView.reloadData()
}

private func loadCredentials() -> [DisplayCredentials] {
credentials = []

do {
let secureVault = try AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter())
let accounts = try secureVault.accounts()
var accountsFailedToLoad: [String?] = []

for account in accounts {
guard let accountId = account.id,
let accountIdInt = Int64(accountId),
let credential = try secureVault.websiteCredentialsFor(accountId: accountIdInt) else {
accountsFailedToLoad.append(account.id)
continue
}

let displayCredential = DisplayCredentials(tld: tld, autofillDomainNameUrlMatcher: autofillDomainNameUrlMatcher, credential: credential)
credentials.append(displayCredential)
}

if !accountsFailedToLoad.isEmpty {
os_log("Failed to load credentials for accounts: %@", accountsFailedToLoad)
showErrorAlertFor(accountsFailedToLoad)
}

return credentials
} catch {
os_log("Failed to fetch accounts")
return []
}
}

private func showErrorAlertFor(_ accountIds: [String?]) {
let alert = UIAlertController(title: "Failed to load credentials for accounts:",
message: accountIds.compactMap { $0 }.joined(separator: ", "),
preferredStyle: .alert)
let action = UIAlertAction(title: UserText.actionOK, style: .default)
alert.addAction(action)
present(alert, animated: true)
}

// MARK: - Table view data source

override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return credentials.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CredentialsTableViewCell.reuseIdentifier,
for: indexPath) as? CredentialsTableViewCell else {
fatalError("Could not dequeue cell")
}

let credential = credentials[indexPath.row]

let details = """
<style>
body { font-size: 15px; }
</style>
<b>ID:</b> \(credential.accountId),<br>
<b>Title:</b> \(credential.accountTitle),<br>
<b>Display Title:</b> \(credential.displayTitle),<br>
<b>Website URL:</b> \(credential.websiteUrl),<br>
<b>Domain:</b> \(credential.domain),<br>
<b>Username:</b> \(credential.username),<br>
<b>Password:</b> \(credential.displayPassword),<br>
<b>Notes:</b> \(credential.notes),<br>
<b>Created:</b> \(credential.created),<br>
<b>LastUpdated:</b> \(credential.lastUpdated),<br>
<b>LastUsed:</b> \(credential.lastUsed),<br>
<b>Signature:</b> \(credential.signature).<br>
"""

if let data = details.data(using: .utf8) {
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]

do {
let attributedString = try NSAttributedString(data: data, options: options, documentAttributes: nil)
cell.details.attributedText = attributedString
cell.details.textColor = .label
} catch {
os_log("Error creating attributed string: \(error)")
}
}

return cell
}

@IBAction func sortButtonAction(_ sender: Any) {
let alert = UIAlertController(title: "Sort By...", message: nil, preferredStyle: .actionSheet)

alert.addAction(UIAlertAction(title: "ID (default)", style: .default, handler: { [weak self] _ in
self?.reloadCredentials()
}))
alert.addAction(UIAlertAction(title: "URL", style: .default, handler: { [weak self] _ in
self?.credentials.sort { $0.websiteUrl < $1.websiteUrl }
self?.tableView.reloadData()
}))
alert.addAction(UIAlertAction(title: "Domain", style: .default, handler: { [weak self] _ in
self?.credentials.sort { $0.domain < $1.domain }
self?.tableView.reloadData()
}))
alert.addAction(UIAlertAction(title: "Display title", style: .default, handler: { [weak self] _ in
self?.credentials.sort { $0.displayTitle < $1.displayTitle }
self?.tableView.reloadData()
}))
alert.addAction(UIAlertAction(title: "Last Updated", style: .default, handler: { [weak self] _ in
self?.credentials.sort { $0.lastUpdated > $1.lastUpdated }
self?.tableView.reloadData()
}))
alert.addAction(UIAlertAction(title: "Last Used", style: .default, handler: { [weak self] _ in
self?.credentials.sort { $0.lastUsed > $1.lastUsed }
self?.tableView.reloadData()
}))

alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))

present(alert, animated: true)

}
}

class CredentialsTableViewCell: UITableViewCell {

static let reuseIdentifier = "CredentialsTableViewCell"

@IBOutlet weak var details: UITextView!

}
3 changes: 3 additions & 0 deletions DuckDuckGo/AutofillDebugViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class AutofillDebugViewController: UITableViewController {
case addAutofillData = 205
case resetAutofillBrokenReports = 206
case resetAutofillSurveys = 207
case viewAllCredentials = 208
}

let defaults = AppUserDefaults()
Expand Down Expand Up @@ -93,6 +94,8 @@ class AutofillDebugViewController: UITableViewController {
let autofillSurveyManager = AutofillSurveyManager()
autofillSurveyManager.resetSurveys()
ActionMessageView.present(message: "Autofill Surveys reset")
} else if cell.tag == Row.viewAllCredentials.rawValue {
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
Expand Down
Loading

0 comments on commit a02f5b8

Please sign in to comment.