summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/PasswordRulesManager.sys.mjs
blob: 32757e51c1412b9f96e27c71eb642ae3e965a271 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  PasswordGenerator: "resource://gre/modules/PasswordGenerator.sys.mjs",
  PasswordRulesParser: "resource://gre/modules/PasswordRulesParser.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("PasswordRulesManager");
  return logger.log.bind(logger);
});

const IMPROVED_PASSWORD_GENERATION_HISTOGRAM =
  "PWMGR_NUM_IMPROVED_GENERATED_PASSWORDS";

/**
 * Handles interactions between PasswordRulesParser and the "password-rules" Remote Settings collection
 *
 * @class PasswordRulesManagerParent
 * @extends {JSWindowActorParent}
 */
export class PasswordRulesManagerParent extends JSWindowActorParent {
  /**
   * @type RemoteSettingsClient
   *
   * @memberof PasswordRulesManagerParent
   */
  _passwordRulesClient = null;

  async initPasswordRulesCollection() {
    if (!this._passwordRulesClient) {
      this._passwordRulesClient = lazy.RemoteSettings(
        lazy.LoginHelper.improvedPasswordRulesCollection
      );
    }
  }
  /**
   * Transforms the parsed rules returned from PasswordRulesParser into a Map for easier access.
   * The returned Map could have the following keys: "allowed", "required", "maxlength", "minlength", and "max-consecutive"
   * @example
   * // Returns a Map with a key-value pair of "allowed": "ascii-printable"
   * _transformRulesToMap([{ _name: "allowed", value: [{ _name: "ascii-printable" }] }])
   * @param {Object[]} rules rules from PasswordRulesParser.parsePasswordRules
   * @return {Map} mapped rules
   * @memberof PasswordRulesManagerParent
   */
  _transformRulesToMap(rules) {
    let map = new Map();
    for (let rule of rules) {
      let { _name, value } = rule;
      if (
        _name === "minlength" ||
        _name === "maxlength" ||
        _name === "max-consecutive"
      ) {
        map.set(_name, value);
      } else {
        let _value = [];
        if (map.get(_name)) {
          _value = map.get(_name);
        }
        for (let _class of value) {
          let { _name: _className } = _class;
          if (_className) {
            _value.push(_className);
          } else {
            let { _characters } = _class;
            _value.push(_characters);
          }
        }
        map.set(_name, _value);
      }
    }
    return map;
  }

  /**
   * Generates a password based on rules from the origin parameters.
   * @param {nsIURI} uri
   * @return {string} password
   * @memberof PasswordRulesManagerParent
   */
  async generatePassword(uri, { inputMaxLength } = {}) {
    await this.initPasswordRulesCollection();
    let originDisplayHost = uri.displayHost;
    let records = await this._passwordRulesClient.get();
    let currentRecord;
    for (let record of records) {
      if (Services.eTLD.hasRootDomain(originDisplayHost, record.Domain)) {
        currentRecord = record;
        break;
      }
    }
    let isCustomRule = false;
    // If we found a matching result, use that to generate a stronger password.
    // Otherwise, generate a password using the default rules set.
    if (currentRecord?.Domain) {
      isCustomRule = true;
      lazy.log(
        `Password rules for ${currentRecord.Domain}:  ${currentRecord["password-rules"]}.`
      );
      let currentRules = lazy.PasswordRulesParser.parsePasswordRules(
        currentRecord["password-rules"]
      );
      let mapOfRules = this._transformRulesToMap(currentRules);
      Services.telemetry
        .getHistogramById(IMPROVED_PASSWORD_GENERATION_HISTOGRAM)
        .add(isCustomRule);
      return lazy.PasswordGenerator.generatePassword({
        rules: mapOfRules,
        inputMaxLength,
      });
    }
    lazy.log(
      `No password rules for specified origin, generating standard password.`
    );
    Services.telemetry
      .getHistogramById(IMPROVED_PASSWORD_GENERATION_HISTOGRAM)
      .add(isCustomRule);
    return lazy.PasswordGenerator.generatePassword({ inputMaxLength });
  }
}