summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/PasswordRulesManager.sys.mjs
blob: e80fb519627464fef86f64fd22c1c939862b9496 (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
129
130
/* 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/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

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",
});

XPCOMUtils.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 });
  }
}