1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/autofill/core/common/save_password_progress_logger.h"
6
7#include <algorithm>
8
9#include "base/json/json_writer.h"
10#include "base/logging.h"
11#include "base/numerics/safe_conversions.h"
12#include "base/strings/string16.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/values.h"
16#include "components/autofill/core/common/password_form.h"
17
18using base::checked_cast;
19using base::Value;
20using base::DictionaryValue;
21using base::FundamentalValue;
22using base::StringValue;
23
24namespace autofill {
25
26namespace {
27
28// Note 1: Caching the ID->string map in an array would be probably faster, but
29// the switch statement is (a) robust against re-ordering, and (b) checks in
30// compile-time, that all IDs get a string assigned. The expected frequency of
31// calls is low enough (in particular, zero if password manager internals page
32// is not open), that optimizing for code robustness is preferred against speed.
33// Note 2: The messages can be used as dictionary keys. Do not use '.' in them.
34std::string GetStringFromID(SavePasswordProgressLogger::StringID id) {
35  switch (id) {
36    case SavePasswordProgressLogger::STRING_DECISION_ASK:
37      return "Decision: ASK the user";
38    case SavePasswordProgressLogger::STRING_DECISION_DROP:
39      return "Decision: DROP the password";
40    case SavePasswordProgressLogger::STRING_DECISION_SAVE:
41      return "Decision: SAVE the password";
42    case SavePasswordProgressLogger::STRING_OTHER:
43      return "(other)";
44    case SavePasswordProgressLogger::STRING_SCHEME_HTML:
45      return "HTML";
46    case SavePasswordProgressLogger::STRING_SCHEME_BASIC:
47      return "Basic";
48    case SavePasswordProgressLogger::STRING_SCHEME_DIGEST:
49      return "Digest";
50    case SavePasswordProgressLogger::STRING_SCHEME_MESSAGE:
51      return "Scheme";
52    case SavePasswordProgressLogger::STRING_SIGNON_REALM:
53      return "Signon realm";
54    case SavePasswordProgressLogger::STRING_ORIGINAL_SIGNON_REALM:
55      return "Original signon realm";
56    case SavePasswordProgressLogger::STRING_ORIGIN:
57      return "Origin";
58    case SavePasswordProgressLogger::STRING_ACTION:
59      return "Action";
60    case SavePasswordProgressLogger::STRING_USERNAME_ELEMENT:
61      return "Username element";
62    case SavePasswordProgressLogger::STRING_PASSWORD_ELEMENT:
63      return "Password element";
64    case SavePasswordProgressLogger::STRING_PASSWORD_AUTOCOMPLETE_SET:
65      return "Password autocomplete set";
66    case SavePasswordProgressLogger::STRING_NEW_PASSWORD_ELEMENT:
67      return "New password element";
68    case SavePasswordProgressLogger::STRING_SSL_VALID:
69      return "SSL valid";
70    case SavePasswordProgressLogger::STRING_PASSWORD_GENERATED:
71      return "Password generated";
72    case SavePasswordProgressLogger::STRING_TIMES_USED:
73      return "Times used";
74    case SavePasswordProgressLogger::STRING_USE_ADDITIONAL_AUTHENTICATION:
75      return "Use additional authentication";
76    case SavePasswordProgressLogger::STRING_PSL_MATCH:
77      return "PSL match";
78    case SavePasswordProgressLogger::STRING_NAME_OR_ID:
79      return "Form name or ID";
80    case SavePasswordProgressLogger::STRING_MESSAGE:
81      return "Message";
82    case SavePasswordProgressLogger::STRING_SET_AUTH_METHOD:
83      return "LoginHandler::SetAuth";
84    case SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED:
85      return "Authentication already handled";
86    case SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM:
87      return "LoginHandler reports this form";
88    case SavePasswordProgressLogger::STRING_SEND_PASSWORD_FORMS_METHOD:
89      return "PasswordAutofillAgent::SendPasswordForms";
90    case SavePasswordProgressLogger::STRING_SECURITY_ORIGIN:
91      return "Security origin";
92    case SavePasswordProgressLogger::STRING_SECURITY_ORIGIN_FAILURE:
93      return "Security origin cannot access password manager.";
94    case SavePasswordProgressLogger::STRING_WEBPAGE_EMPTY:
95      return "Webpage is empty.";
96    case SavePasswordProgressLogger::STRING_NUMBER_OF_ALL_FORMS:
97      return "Number of all forms";
98    case SavePasswordProgressLogger::STRING_FORM_FOUND_ON_PAGE:
99      return "Form found on page";
100    case SavePasswordProgressLogger::STRING_FORM_IS_VISIBLE:
101      return "Form is visible";
102    case SavePasswordProgressLogger::STRING_FORM_IS_PASSWORD:
103      return "Form is a password form";
104    case SavePasswordProgressLogger::STRING_WILL_SUBMIT_FORM_METHOD:
105      return "PasswordAutofillAgent::WillSubmitForm";
106    case SavePasswordProgressLogger::STRING_HTML_FORM_FOR_SUBMIT:
107      return "HTML form for submit";
108    case SavePasswordProgressLogger::STRING_CREATED_PASSWORD_FORM:
109      return "Created PasswordForm";
110    case SavePasswordProgressLogger::STRING_SUBMITTED_PASSWORD_REPLACED:
111      return "Submitted password replaced with the provisionally saved one.";
112    case SavePasswordProgressLogger::STRING_DID_START_PROVISIONAL_LOAD_METHOD:
113      return "PasswordAutofillAgent::DidStartProvisionalLoad";
114    case SavePasswordProgressLogger::STRING_FORM_FRAME_EQ_FRAME:
115      return "form_frame == frame";
116    case SavePasswordProgressLogger::STRING_PROVISIONALLY_SAVED_FORM_FOR_FRAME:
117      return "provisionally_saved_forms_[form_frame]";
118    case SavePasswordProgressLogger::STRING_PASSWORD_FORM_FOUND_ON_PAGE:
119      return "PasswordForm found on the page";
120    case SavePasswordProgressLogger::STRING_PROVISIONALLY_SAVE_PASSWORD_METHOD:
121      return "PasswordManager::ProvisionallySavePassword";
122    case SavePasswordProgressLogger::STRING_PROVISIONALLY_SAVE_PASSWORD_FORM:
123      return "ProvisionallySavePassword form";
124    case SavePasswordProgressLogger::STRING_IS_SAVING_ENABLED:
125      return "IsSavingEnabledForCurrentPage";
126    case SavePasswordProgressLogger::STRING_EMPTY_PASSWORD:
127      return "Empty password";
128    case SavePasswordProgressLogger::STRING_EXACT_MATCH:
129      return "Form manager found, exact match.";
130    case SavePasswordProgressLogger::STRING_MATCH_WITHOUT_ACTION:
131      return "Form manager found, match except for action.";
132    case SavePasswordProgressLogger::STRING_MATCHING_NOT_COMPLETE:
133      return "No form manager has completed matching.";
134    case SavePasswordProgressLogger::STRING_FORM_BLACKLISTED:
135      return "Form blacklisted.";
136    case SavePasswordProgressLogger::STRING_INVALID_FORM:
137      return "Invalid form.";
138    case SavePasswordProgressLogger::STRING_AUTOCOMPLETE_OFF:
139      return "Autocomplete=off.";
140    case SavePasswordProgressLogger::STRING_SYNC_CREDENTIAL:
141      return "Credential is used for syncing passwords.";
142    case SavePasswordProgressLogger::STRING_PROVISIONALLY_SAVED_FORM:
143      return "provisionally_saved_form";
144    case SavePasswordProgressLogger::STRING_IGNORE_POSSIBLE_USERNAMES:
145      return "Ignore other possible usernames";
146    case SavePasswordProgressLogger::STRING_ON_PASSWORD_FORMS_RENDERED_METHOD:
147      return "PasswordManager::OnPasswordFormsRendered";
148    case SavePasswordProgressLogger::STRING_NO_PROVISIONAL_SAVE_MANAGER:
149      return "No provisional save manager";
150    case SavePasswordProgressLogger::STRING_NUMBER_OF_VISIBLE_FORMS:
151      return "Number of visible forms";
152    case SavePasswordProgressLogger::STRING_PASSWORD_FORM_REAPPEARED:
153      return "Password form re-appeared";
154    case SavePasswordProgressLogger::STRING_SAVING_DISABLED:
155      return "Saving disabled";
156    case SavePasswordProgressLogger::STRING_NO_MATCHING_FORM:
157      return "No matching form";
158    case SavePasswordProgressLogger::STRING_SSL_ERRORS_PRESENT:
159      return "SSL errors present";
160    case SavePasswordProgressLogger::STRING_ONLY_VISIBLE:
161      return "only_visible";
162    case SavePasswordProgressLogger::STRING_SHOW_PASSWORD_PROMPT:
163      return "Show password prompt";
164    case SavePasswordProgressLogger::STRING_INVALID:
165      return "INVALID";
166      // Intentionally no default: clause here -- all IDs need to get covered.
167  }
168  NOTREACHED();  // Win compilers don't believe this is unreachable.
169  return std::string();
170};
171
172// Removes privacy sensitive parts of |url| (currently all but host and scheme).
173std::string ScrubURL(const GURL& url) {
174  if (url.is_valid())
175    return url.GetWithEmptyPath().spec();
176  return std::string();
177}
178
179// Returns true for all characters which we don't want to see in the logged IDs
180// or names of HTML elements.
181bool IsUnwantedInElementID(char c) {
182  return !(c == '_' || c == '-' || IsAsciiAlpha(c) || IsAsciiDigit(c));
183}
184
185// Replaces all characters satisfying IsUnwantedInElementID by a ' ', and turns
186// all characters to lowercase. This damages some valid HTML element IDs or
187// names, but it is likely that it will be still possible to match the scrubbed
188// string to the original ID or name in the HTML doc. That's good enough for the
189// logging purposes, and provides some security benefits.
190std::string ScrubElementID(std::string element_id) {
191  std::replace_if(
192      element_id.begin(), element_id.end(), IsUnwantedInElementID, ' ');
193  return base::StringToLowerASCII(element_id);
194}
195
196std::string ScrubElementID(const base::string16& element_id) {
197  return ScrubElementID(base::UTF16ToUTF8(element_id));
198}
199
200std::string FormSchemeToString(PasswordForm::Scheme scheme) {
201  SavePasswordProgressLogger::StringID result_id =
202      SavePasswordProgressLogger::STRING_INVALID;
203  switch (scheme) {
204    case PasswordForm::SCHEME_HTML:
205      result_id = SavePasswordProgressLogger::STRING_SCHEME_HTML;
206      break;
207    case PasswordForm::SCHEME_BASIC:
208      result_id = SavePasswordProgressLogger::STRING_SCHEME_BASIC;
209      break;
210    case PasswordForm::SCHEME_DIGEST:
211      result_id = SavePasswordProgressLogger::STRING_SCHEME_DIGEST;
212      break;
213    case PasswordForm::SCHEME_OTHER:
214      result_id = SavePasswordProgressLogger::STRING_OTHER;
215      break;
216  }
217  return GetStringFromID(result_id);
218}
219
220}  // namespace
221
222SavePasswordProgressLogger::SavePasswordProgressLogger() {
223}
224
225SavePasswordProgressLogger::~SavePasswordProgressLogger() {
226}
227
228void SavePasswordProgressLogger::LogPasswordForm(
229    SavePasswordProgressLogger::StringID label,
230    const PasswordForm& form) {
231  DictionaryValue log;
232  log.SetString(GetStringFromID(STRING_SCHEME_MESSAGE),
233                FormSchemeToString(form.scheme));
234  log.SetString(GetStringFromID(STRING_SCHEME_MESSAGE),
235                FormSchemeToString(form.scheme));
236  log.SetString(GetStringFromID(STRING_SIGNON_REALM),
237                ScrubURL(GURL(form.signon_realm)));
238  log.SetString(GetStringFromID(STRING_ORIGINAL_SIGNON_REALM),
239                ScrubURL(GURL(form.original_signon_realm)));
240  log.SetString(GetStringFromID(STRING_ORIGIN), ScrubURL(form.origin));
241  log.SetString(GetStringFromID(STRING_ACTION), ScrubURL(form.action));
242  log.SetString(GetStringFromID(STRING_USERNAME_ELEMENT),
243                ScrubElementID(form.username_element));
244  log.SetString(GetStringFromID(STRING_PASSWORD_ELEMENT),
245                ScrubElementID(form.password_element));
246  log.SetBoolean(GetStringFromID(STRING_PASSWORD_AUTOCOMPLETE_SET),
247                 form.password_autocomplete_set);
248  log.SetString(GetStringFromID(STRING_NEW_PASSWORD_ELEMENT),
249                ScrubElementID(form.new_password_element));
250  log.SetBoolean(GetStringFromID(STRING_SSL_VALID), form.ssl_valid);
251  log.SetBoolean(GetStringFromID(STRING_PASSWORD_GENERATED),
252                 form.type == PasswordForm::TYPE_GENERATED);
253  log.SetInteger(GetStringFromID(STRING_TIMES_USED), form.times_used);
254  log.SetBoolean(GetStringFromID(STRING_USE_ADDITIONAL_AUTHENTICATION),
255                 form.use_additional_authentication);
256  log.SetBoolean(GetStringFromID(STRING_PSL_MATCH), form.IsPublicSuffixMatch());
257  LogValue(label, log);
258}
259
260void SavePasswordProgressLogger::LogHTMLForm(
261    SavePasswordProgressLogger::StringID label,
262    const std::string& name_or_id,
263    const GURL& action) {
264  DictionaryValue log;
265  log.SetString(GetStringFromID(STRING_NAME_OR_ID), ScrubElementID(name_or_id));
266  log.SetString(GetStringFromID(STRING_ACTION), ScrubURL(action));
267  LogValue(label, log);
268}
269
270void SavePasswordProgressLogger::LogURL(
271    SavePasswordProgressLogger::StringID label,
272    const GURL& url) {
273  LogValue(label, StringValue(ScrubURL(url)));
274}
275
276void SavePasswordProgressLogger::LogBoolean(
277    SavePasswordProgressLogger::StringID label,
278    bool truth_value) {
279  LogValue(label, FundamentalValue(truth_value));
280}
281
282void SavePasswordProgressLogger::LogNumber(
283    SavePasswordProgressLogger::StringID label,
284    int signed_number) {
285  LogValue(label, FundamentalValue(signed_number));
286}
287
288void SavePasswordProgressLogger::LogNumber(
289    SavePasswordProgressLogger::StringID label,
290    size_t unsigned_number) {
291  int signed_number = checked_cast<int, size_t>(unsigned_number);
292  LogNumber(label, signed_number);
293}
294
295void SavePasswordProgressLogger::LogMessage(
296    SavePasswordProgressLogger::StringID message) {
297  LogValue(STRING_MESSAGE, StringValue(GetStringFromID(message)));
298}
299
300void SavePasswordProgressLogger::LogValue(StringID label, const Value& log) {
301  std::string log_string;
302  bool conversion_to_string_successful = base::JSONWriter::WriteWithOptions(
303      &log, base::JSONWriter::OPTIONS_PRETTY_PRINT, &log_string);
304  DCHECK(conversion_to_string_successful);
305  SendLog(GetStringFromID(label) + ": " + log_string);
306}
307
308}  // namespace autofill
309