toolkit/components/passwordmgr/nsLoginManager.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 const Cc = Components.classes;
     7 const Ci = Components.interfaces;
     9 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    10 Components.utils.import("resource://gre/modules/Services.jsm");
    11 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
    13 XPCOMUtils.defineLazyModuleGetter(this,
    14   "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm");
    16 var debug = false;
    17 function log(...pieces) {
    18     function generateLogMessage(args) {
    19         let strings = ['Login Manager:'];
    21         args.forEach(function(arg) {
    22             if (typeof arg === 'string') {
    23                 strings.push(arg);
    24             } else if (typeof arg === 'undefined') {
    25                 strings.push('undefined');
    26             } else if (arg === null) {
    27                 strings.push('null');
    28             } else {
    29                 try {
    30                   strings.push(JSON.stringify(arg, null, 2));
    31                 } catch(err) {
    32                   strings.push("<<something>>");
    33                 }
    34             }
    35         });
    36         return strings.join(' ');
    37     }
    39     if (!debug)
    40         return;
    42     let message = generateLogMessage(pieces);
    43     dump(message + "\n");
    44     Services.console.logStringMessage(message);
    45 }
    47 function LoginManager() {
    48     this.init();
    49 }
    51 LoginManager.prototype = {
    53     classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
    54     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManager,
    55                                             Ci.nsISupportsWeakReference,
    56                                             Ci.nsIInterfaceRequestor]),
    57     getInterface : function(aIID) {
    58       if (aIID.equals(Ci.mozIStorageConnection) && this._storage) {
    59         let ir = this._storage.QueryInterface(Ci.nsIInterfaceRequestor);
    60         return ir.getInterface(aIID);
    61       }
    63       throw Cr.NS_ERROR_NO_INTERFACE;
    64     },
    67     /* ---------- private memebers ---------- */
    70     __formFillService : null, // FormFillController, for username autocompleting
    71     get _formFillService() {
    72         if (!this.__formFillService)
    73             this.__formFillService =
    74                             Cc["@mozilla.org/satchel/form-fill-controller;1"].
    75                             getService(Ci.nsIFormFillController);
    76         return this.__formFillService;
    77     },
    80     __storage : null, // Storage component which contains the saved logins
    81     get _storage() {
    82         if (!this.__storage) {
    84             var contractID = "@mozilla.org/login-manager/storage/mozStorage;1";
    85             try {
    86                 var catMan = Cc["@mozilla.org/categorymanager;1"].
    87                              getService(Ci.nsICategoryManager);
    88                 contractID = catMan.getCategoryEntry("login-manager-storage",
    89                                                      "nsILoginManagerStorage");
    90                 log("Found alternate nsILoginManagerStorage with contract ID:", contractID);
    91             } catch (e) {
    92                 log("No alternate nsILoginManagerStorage registered");
    93             }
    95             this.__storage = Cc[contractID].
    96                              createInstance(Ci.nsILoginManagerStorage);
    97             try {
    98                 this.__storage.init();
    99             } catch (e) {
   100                 log("Initialization of storage component failed:", e);
   101                 this.__storage = null;
   102             }
   103         }
   105         return this.__storage;
   106     },
   108     _prefBranch  : null, // Preferences service
   109     _remember : true,  // mirrors signon.rememberSignons preference
   112     /*
   113      * init
   114      *
   115      * Initialize the Login Manager. Automatically called when service
   116      * is created.
   117      *
   118      * Note: Service created in /browser/base/content/browser.js,
   119      *       delayedStartup()
   120      */
   121     init : function () {
   123         // Cache references to current |this| in utility objects
   124         this._observer._pwmgr            = this;
   126         // Preferences. Add observer so we get notified of changes.
   127         this._prefBranch = Services.prefs.getBranch("signon.");
   128         this._prefBranch.addObserver("", this._observer, false);
   130         // Get current preference values.
   131         debug = this._prefBranch.getBoolPref("debug");
   133         this._remember = this._prefBranch.getBoolPref("rememberSignons");
   135         // Form submit observer checks forms for new logins and pw changes.
   136         Services.obs.addObserver(this._observer, "xpcom-shutdown", false);
   138         // XXX gross hacky workaround for bug 881996. The WPL does nothing.
   139         var progress = Cc["@mozilla.org/docloaderservice;1"].
   140                        getService(Ci.nsIWebProgress);
   141         progress.addProgressListener(this._webProgressListener,
   142                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
   143     },
   146     /* ---------- Utility objects ---------- */
   149     /*
   150      * _observer object
   151      *
   152      * Internal utility object, implements the nsIObserver interface.
   153      * Used to receive notification for: form submission, preference changes.
   154      */
   155     _observer : {
   156         _pwmgr : null,
   158         QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
   159                                                 Ci.nsISupportsWeakReference]),
   161         // nsObserver
   162         observe : function (subject, topic, data) {
   164             if (topic == "nsPref:changed") {
   165                 var prefName = data;
   166                 log("got change to", prefName, "preference");
   168                 if (prefName == "debug") {
   169                     debug = this._pwmgr._prefBranch.getBoolPref("debug");
   170                 } else if (prefName == "rememberSignons") {
   171                     this._pwmgr._remember =
   172                         this._pwmgr._prefBranch.getBoolPref("rememberSignons");
   173                 } else {
   174                     log("Oops! Pref not handled, change ignored.");
   175                 }
   176             } else if (topic == "xpcom-shutdown") {
   177                 for (let i in this._pwmgr) {
   178                   try {
   179                     this._pwmgr[i] = null;
   180                   } catch(ex) {}
   181                 }
   182                 this._pwmgr = null;
   183             } else {
   184                 log("Oops! Unexpected notification:", topic);
   185             }
   186         }
   187     },
   190     _webProgressListener : {
   191         QueryInterface : XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
   192                                                 Ci.nsISupportsWeakReference]),
   193         onStateChange    : function() { /* NOP */ },
   194         onProgressChange : function() { throw "Unexpected onProgressChange"; },
   195         onLocationChange : function() { throw "Unexpected onLocationChange"; },
   196         onStatusChange   : function() { throw "Unexpected onStatusChange";   },
   197         onSecurityChange : function() { throw "Unexpected onSecurityChange"; }
   198     },
   203     /* ---------- Primary Public interfaces ---------- */
   208     /*
   209      * addLogin
   210      *
   211      * Add a new login to login storage.
   212      */
   213     addLogin : function (login) {
   214         // Sanity check the login
   215         if (login.hostname == null || login.hostname.length == 0)
   216             throw "Can't add a login with a null or empty hostname.";
   218         // For logins w/o a username, set to "", not null.
   219         if (login.username == null)
   220             throw "Can't add a login with a null username.";
   222         if (login.password == null || login.password.length == 0)
   223             throw "Can't add a login with a null or empty password.";
   225         if (login.formSubmitURL || login.formSubmitURL == "") {
   226             // We have a form submit URL. Can't have a HTTP realm.
   227             if (login.httpRealm != null)
   228                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
   229         } else if (login.httpRealm) {
   230             // We have a HTTP realm. Can't have a form submit URL.
   231             if (login.formSubmitURL != null)
   232                 throw "Can't add a login with both a httpRealm and formSubmitURL.";
   233         } else {
   234             // Need one or the other!
   235             throw "Can't add a login without a httpRealm or formSubmitURL.";
   236         }
   239         // Look for an existing entry.
   240         var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
   241                                      login.httpRealm);
   243         if (logins.some(function(l) login.matches(l, true)))
   244             throw "This login already exists.";
   246         log("Adding login");
   247         return this._storage.addLogin(login);
   248     },
   251     /*
   252      * removeLogin
   253      *
   254      * Remove the specified login from the stored logins.
   255      */
   256     removeLogin : function (login) {
   257         log("Removing login");
   258         return this._storage.removeLogin(login);
   259     },
   262     /*
   263      * modifyLogin
   264      *
   265      * Change the specified login to match the new login.
   266      */
   267     modifyLogin : function (oldLogin, newLogin) {
   268         log("Modifying login");
   269         return this._storage.modifyLogin(oldLogin, newLogin);
   270     },
   273     /*
   274      * getAllLogins
   275      *
   276      * Get a dump of all stored logins. Used by the login manager UI.
   277      *
   278      * |count| is only needed for XPCOM.
   279      *
   280      * Returns an array of logins. If there are no logins, the array is empty.
   281      */
   282     getAllLogins : function (count) {
   283         log("Getting a list of all logins");
   284         return this._storage.getAllLogins(count);
   285     },
   288     /*
   289      * removeAllLogins
   290      *
   291      * Remove all stored logins.
   292      */
   293     removeAllLogins : function () {
   294         log("Removing all logins");
   295         this._storage.removeAllLogins();
   296     },
   298     /*
   299      * getAllDisabledHosts
   300      *
   301      * Get a list of all hosts for which logins are disabled.
   302      *
   303      * |count| is only needed for XPCOM.
   304      *
   305      * Returns an array of disabled logins. If there are no disabled logins,
   306      * the array is empty.
   307      */
   308     getAllDisabledHosts : function (count) {
   309         log("Getting a list of all disabled hosts");
   310         return this._storage.getAllDisabledHosts(count);
   311     },
   314     /*
   315      * findLogins
   316      *
   317      * Search for the known logins for entries matching the specified criteria.
   318      */
   319     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
   320         log("Searching for logins matching host:", hostname,
   321             "formSubmitURL:", formSubmitURL, "httpRealm:", httpRealm);
   323         return this._storage.findLogins(count, hostname, formSubmitURL,
   324                                         httpRealm);
   325     },
   328     /*
   329      * searchLogins
   330      *
   331      * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   332      * JavaScript object and decrypt the results.
   333      *
   334      * Returns an array of decrypted nsILoginInfo.
   335      */
   336     searchLogins : function(count, matchData) {
   337        log("Searching for logins");
   339         return this._storage.searchLogins(count, matchData);
   340     },
   343     /*
   344      * countLogins
   345      *
   346      * Search for the known logins for entries matching the specified criteria,
   347      * returns only the count.
   348      */
   349     countLogins : function (hostname, formSubmitURL, httpRealm) {
   350         log("Counting logins matching host:", hostname,
   351             "formSubmitURL:", formSubmitURL, "httpRealm:", httpRealm);
   353         return this._storage.countLogins(hostname, formSubmitURL, httpRealm);
   354     },
   357     /*
   358      * uiBusy
   359      */
   360     get uiBusy() {
   361         return this._storage.uiBusy;
   362     },
   365     /*
   366      * isLoggedIn
   367      */
   368     get isLoggedIn() {
   369         return this._storage.isLoggedIn;
   370     },
   373     /*
   374      * getLoginSavingEnabled
   375      *
   376      * Check to see if user has disabled saving logins for the host.
   377      */
   378     getLoginSavingEnabled : function (host) {
   379         log("Checking if logins to", host, "can be saved.");
   380         if (!this._remember)
   381             return false;
   383         return this._storage.getLoginSavingEnabled(host);
   384     },
   387     /*
   388      * setLoginSavingEnabled
   389      *
   390      * Enable or disable storing logins for the specified host.
   391      */
   392     setLoginSavingEnabled : function (hostname, enabled) {
   393         // Nulls won't round-trip with getAllDisabledHosts().
   394         if (hostname.indexOf("\0") != -1)
   395             throw "Invalid hostname";
   397         log("Login saving for", hostname, "now enabled?", enabled);
   398         return this._storage.setLoginSavingEnabled(hostname, enabled);
   399     },
   402     /*
   403      * autoCompleteSearch
   404      *
   405      * Yuck. This is called directly by satchel:
   406      * nsFormFillController::StartSearch()
   407      * [toolkit/components/satchel/src/nsFormFillController.cpp]
   408      *
   409      * We really ought to have a simple way for code to register an
   410      * auto-complete provider, and not have satchel calling pwmgr directly.
   411      */
   412     autoCompleteSearch : function (aSearchString, aPreviousResult, aElement) {
   413         // aPreviousResult & aResult are nsIAutoCompleteResult,
   414         // aElement is nsIDOMHTMLInputElement
   416         if (!this._remember)
   417             return null;
   419         log("AutoCompleteSearch invoked. Search is:", aSearchString);
   421         var result = null;
   423         if (aPreviousResult &&
   424                 aSearchString.substr(0, aPreviousResult.searchString.length) == aPreviousResult.searchString) {
   425             log("Using previous autocomplete result");
   426             result = aPreviousResult;
   427             result.wrappedJSObject.searchString = aSearchString;
   429             // We have a list of results for a shorter search string, so just
   430             // filter them further based on the new search string.
   431             // Count backwards, because result.matchCount is decremented
   432             // when we remove an entry.
   433             for (var i = result.matchCount - 1; i >= 0; i--) {
   434                 var match = result.getValueAt(i);
   436                 // Remove results that are too short, or have different prefix.
   437                 if (aSearchString.length > match.length ||
   438                     aSearchString.toLowerCase() !=
   439                         match.substr(0, aSearchString.length).toLowerCase())
   440                 {
   441                     log("Removing autocomplete entry:", match);
   442                     result.removeValueAt(i, false);
   443                 }
   444             }
   445         } else {
   446             log("Creating new autocomplete search result.");
   448             var doc = aElement.ownerDocument;
   449             var origin = this._getPasswordOrigin(doc.documentURI);
   450             var actionOrigin = this._getActionOrigin(aElement.form);
   452             // This shouldn't trigger a master password prompt, because we
   453             // don't attach to the input until after we successfully obtain
   454             // logins for the form.
   455             var logins = this.findLogins({}, origin, actionOrigin, null);
   456             var matchingLogins = [];
   458             // Filter out logins that don't match the search prefix. Also
   459             // filter logins without a username, since that's confusing to see
   460             // in the dropdown and we can't autocomplete them anyway.
   461             for (i = 0; i < logins.length; i++) {
   462                 var username = logins[i].username.toLowerCase();
   463                 if (username &&
   464                     aSearchString.length <= username.length &&
   465                     aSearchString.toLowerCase() ==
   466                         username.substr(0, aSearchString.length))
   467                 {
   468                     matchingLogins.push(logins[i]);
   469                 }
   470             }
   471             log(matchingLogins.length, "autocomplete logins avail.");
   472             result = new UserAutoCompleteResult(aSearchString, matchingLogins);
   473         }
   475         return result;
   476     },
   481     /* ------- Internal methods / callbacks for document integration ------- */
   486     /*
   487      * _getPasswordOrigin
   488      *
   489      * Get the parts of the URL we want for identification.
   490      */
   491     _getPasswordOrigin : function (uriString, allowJS) {
   492         var realm = "";
   493         try {
   494             var uri = Services.io.newURI(uriString, null, null);
   496             if (allowJS && uri.scheme == "javascript")
   497                 return "javascript:"
   499             realm = uri.scheme + "://" + uri.host;
   501             // If the URI explicitly specified a port, only include it when
   502             // it's not the default. (We never want "http://foo.com:80")
   503             var port = uri.port;
   504             if (port != -1) {
   505                 var handler = Services.io.getProtocolHandler(uri.scheme);
   506                 if (port != handler.defaultPort)
   507                     realm += ":" + port;
   508             }
   510         } catch (e) {
   511             // bug 159484 - disallow url types that don't support a hostPort.
   512             // (although we handle "javascript:..." as a special case above.)
   513             log("Couldn't parse origin for", uriString);
   514             realm = null;
   515         }
   517         return realm;
   518     },
   520     _getActionOrigin : function (form) {
   521         var uriString = form.action;
   523         // A blank or missing action submits to where it came from.
   524         if (uriString == "")
   525             uriString = form.baseURI; // ala bug 297761
   527         return this._getPasswordOrigin(uriString, true);
   528     },
   531     /*
   532      * fillForm
   533      *
   534      * Fill the form with login information if we can find it.
   535      */
   536     fillForm : function (form) {
   537         log("fillForm processing form[ id:", form.id, "]");
   538         return LoginManagerContent._fillForm(form, true, true, false, null)[0];
   539     },
   541 }; // end of LoginManager implementation
   546 // nsIAutoCompleteResult implementation
   547 function UserAutoCompleteResult (aSearchString, matchingLogins) {
   548     function loginSort(a,b) {
   549         var userA = a.username.toLowerCase();
   550         var userB = b.username.toLowerCase();
   552         if (userA < userB)
   553             return -1;
   555         if (userB > userA)
   556             return  1;
   558         return 0;
   559     };
   561     this.searchString = aSearchString;
   562     this.logins = matchingLogins.sort(loginSort);
   563     this.matchCount = matchingLogins.length;
   565     if (this.matchCount > 0) {
   566         this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
   567         this.defaultIndex = 0;
   568     }
   569 }
   571 UserAutoCompleteResult.prototype = {
   572     QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
   573                                             Ci.nsISupportsWeakReference]),
   575     // private
   576     logins : null,
   578     // Allow autoCompleteSearch to get at the JS object so it can
   579     // modify some readonly properties for internal use.
   580     get wrappedJSObject() {
   581         return this;
   582     },
   584     // Interfaces from idl...
   585     searchString : null,
   586     searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
   587     defaultIndex : -1,
   588     errorDescription : "",
   589     matchCount : 0,
   591     getValueAt : function (index) {
   592         if (index < 0 || index >= this.logins.length)
   593             throw "Index out of range.";
   595         return this.logins[index].username;
   596     },
   598     getLabelAt: function(index) {
   599         return this.getValueAt(index);
   600     },
   602     getCommentAt : function (index) {
   603         return "";
   604     },
   606     getStyleAt : function (index) {
   607         return "";
   608     },
   610     getImageAt : function (index) {
   611         return "";
   612     },
   614     getFinalCompleteValueAt : function (index) {
   615         return this.getValueAt(index);
   616     },
   618     removeValueAt : function (index, removeFromDB) {
   619         if (index < 0 || index >= this.logins.length)
   620             throw "Index out of range.";
   622         var [removedLogin] = this.logins.splice(index, 1);
   624         this.matchCount--;
   625         if (this.defaultIndex > this.logins.length)
   626             this.defaultIndex--;
   628         if (removeFromDB) {
   629             var pwmgr = Cc["@mozilla.org/login-manager;1"].
   630                         getService(Ci.nsILoginManager);
   631             pwmgr.removeLogin(removedLogin);
   632         }
   633     }
   634 };
   636 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);

mercurial