diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/passwordmgr/storage-geckoview.sys.mjs | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/passwordmgr/storage-geckoview.sys.mjs')
-rw-r--r-- | toolkit/components/passwordmgr/storage-geckoview.sys.mjs | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/storage-geckoview.sys.mjs b/toolkit/components/passwordmgr/storage-geckoview.sys.mjs new file mode 100644 index 0000000000..6be2b3ed16 --- /dev/null +++ b/toolkit/components/passwordmgr/storage-geckoview.sys.mjs @@ -0,0 +1,256 @@ +/* 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/. */ + +/** + * nsILoginManagerStorage implementation for GeckoView + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { LoginManagerStorage_json } from "resource://gre/modules/storage-json.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + GeckoViewAutocomplete: "resource://gre/modules/GeckoViewAutocomplete.jsm", + LoginEntry: "resource://gre/modules/GeckoViewAutocomplete.jsm", +}); + +export class LoginManagerStorage_geckoview extends LoginManagerStorage_json { + get classID() { + return Components.ID("{337f317f-f713-452a-962d-db831c785fec}"); + } + get QueryInterface() { + return ChromeUtils.generateQI(["nsILoginManagerStorage"]); + } + + get _crypto() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + initialize() { + try { + return Promise.resolve(); + } catch (e) { + this.log("Initialization failed:", e); + throw new Error("Initialization failed"); + } + } + + /** + * Internal method used by regression tests only. It is called before + * replacing this storage module with a new instance. + */ + terminate() {} + + addLogin( + login, + preEncrypted = false, + plaintextUsername = null, + plaintextPassword = null + ) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + removeLogin(login) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + modifyLogin(oldLogin, newLoginData) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + recordPasswordUse(login) { + lazy.GeckoViewAutocomplete.onLoginPasswordUsed( + lazy.LoginEntry.fromLoginInfo(login) + ); + } + + getAllLogins() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + /** + * Returns an array of all saved logins that can be decrypted. + * + * @resolve {nsILoginInfo[]} + */ + async getAllLoginsAsync() { + return this._getLoginsAsync({}); + } + + async searchLoginsAsync(matchData) { + this.log( + `Searching for matching saved logins for origin: ${matchData.origin}` + ); + return this._getLoginsAsync(matchData); + } + + _baseHostnameFromOrigin(origin) { + if (!origin) { + return null; + } + + let originURI = Services.io.newURI(origin); + try { + return Services.eTLD.getBaseDomain(originURI); + } catch (ex) { + if (ex.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) { + // `getBaseDomain` cannot handle IP addresses and `nsIURI` cannot return + // IPv6 hostnames with the square brackets so use `URL.hostname`. + return new URL(origin).hostname; + } else if (ex.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { + return originURI.asciiHost; + } + throw ex; + } + } + + async _getLoginsAsync(matchData) { + let baseHostname = this._baseHostnameFromOrigin(matchData.origin); + + // Query all logins for the eTLD+1 and then filter the logins in _searchLogins + // so that we can handle the logic for scheme upgrades, subdomains, etc. + // Convert from the new shape to one which supports the legacy getters used + // by _searchLogins. + let candidateLogins = await lazy.GeckoViewAutocomplete.fetchLogins( + baseHostname + ).catch(_ => { + // No GV delegate is attached. + }); + + if (!candidateLogins) { + // May be undefined if there is no delegate attached to handle the request. + // Ignore the request. + return []; + } + + let realMatchData = {}; + let options = {}; + + if (matchData.guid) { + // Enforce GUID-based filtering when available, since the origin of the + // login may not match the origin of the form in the case of scheme + // upgrades. + realMatchData = { guid: matchData.guid }; + } else { + for (let [name, value] of Object.entries(matchData)) { + switch (name) { + // Some property names aren't field names but are special options to + // affect the search. + case "acceptDifferentSubdomains": + case "schemeUpgrades": { + options[name] = value; + break; + } + default: { + realMatchData[name] = value; + break; + } + } + } + } + + const [logins] = this._searchLogins( + realMatchData, + options, + candidateLogins.map(this._vanillaLoginToStorageLogin) + ); + return logins; + } + + /** + * Convert a modern decrypted vanilla login object to one expected from logins.json. + * + * The storage login is usually encrypted but not in this case, this aligns + * with the `_decryptLogins` method being a no-op. + * + * @param {object} vanillaLogin using `origin`/`formActionOrigin`/`username` properties. + * @returns {object} a vanilla login for logins.json using + * `hostname`/`formSubmitURL`/`encryptedUsername`. + */ + _vanillaLoginToStorageLogin(vanillaLogin) { + return { + ...vanillaLogin, + hostname: vanillaLogin.origin, + formSubmitURL: vanillaLogin.formActionOrigin, + encryptedUsername: vanillaLogin.username, + encryptedPassword: vanillaLogin.password, + }; + } + + /** + * Use `searchLoginsAsync` instead. + */ + searchLogins(matchData) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + /** + * Removes all logins from storage. + */ + removeAllLogins() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + countLogins(origin, formActionOrigin, httpRealm) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + get uiBusy() { + return false; + } + + get isLoggedIn() { + return true; + } + + /** + * GeckoView will encrypt the login itself. + */ + _encryptLogin(login) { + return login; + } + + /** + * GeckoView logins are already decrypted before this component receives them + * so this method is a no-op for this backend. + * @see _vanillaLoginToStorageLogin + */ + _decryptLogins(logins) { + return logins; + } + + /** + * Sync metadata, which isn't supported by GeckoView. + */ + async getSyncID() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + async setSyncID(syncID) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + async getLastSync() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } + + async setLastSync(timestamp) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + } +} + +XPCOMUtils.defineLazyGetter( + LoginManagerStorage_geckoview.prototype, + "log", + () => { + let logger = lazy.LoginHelper.createLogger("Login storage"); + return logger.log.bind(logger); + } +); |