mobile/android/components/LoginManagerPrompter.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;
     8 const Cr = Components.results;
    10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    11 Components.utils.import("resource://gre/modules/Services.jsm");
    13 /* ==================== LoginManagerPrompter ==================== */
    14 /*
    15  * LoginManagerPrompter
    16  *
    17  * Implements interfaces for prompting the user to enter/save/change auth info.
    18  *
    19  * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
    20  * found in HTML forms.
    21  */
    22 function LoginManagerPrompter() {
    23 }
    25 LoginManagerPrompter.prototype = {
    27     classID : Components.ID("97d12931-abe2-11df-94e2-0800200c9a66"),
    28     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerPrompter]),
    30     _factory       : null,
    31     _window       : null,
    32     _debug         : false, // mirrors signon.debug
    34     __pwmgr : null, // Password Manager service
    35     get _pwmgr() {
    36         if (!this.__pwmgr)
    37             this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
    38                            getService(Ci.nsILoginManager);
    39         return this.__pwmgr;
    40     },
    42     __promptService : null, // Prompt service for user interaction
    43     get _promptService() {
    44         if (!this.__promptService)
    45             this.__promptService =
    46                 Cc["@mozilla.org/embedcomp/prompt-service;1"].
    47                 getService(Ci.nsIPromptService2);
    48         return this.__promptService;
    49     },
    51     __strBundle : null, // String bundle for L10N
    52     get _strBundle() {
    53         if (!this.__strBundle) {
    54             var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
    55                              getService(Ci.nsIStringBundleService);
    56             this.__strBundle = bunService.createBundle(
    57                         "chrome://passwordmgr/locale/passwordmgr.properties");
    58             if (!this.__strBundle)
    59                 throw "String bundle for Login Manager not present!";
    60         }
    62         return this.__strBundle;
    63     },
    66     __ellipsis : null,
    67     get _ellipsis() {
    68         if (!this.__ellipsis) {
    69             this.__ellipsis = "\u2026";
    70             try {
    71                 this.__ellipsis = Services.prefs.getComplexValue(
    72                                     "intl.ellipsis", Ci.nsIPrefLocalizedString).data;
    73             } catch (e) { }
    74         }
    75         return this.__ellipsis;
    76     },
    79     /*
    80      * log
    81      *
    82      * Internal function for logging debug messages to the Error Console window.
    83      */
    84     log : function (message) {
    85         if (!this._debug)
    86             return;
    88         dump("Pwmgr Prompter: " + message + "\n");
    89         Services.console.logStringMessage("Pwmgr Prompter: " + message);
    90     },
    93     /* ---------- nsILoginManagerPrompter prompts ---------- */
    98     /*
    99      * init
   100      *
   101      */
   102     init : function (aWindow, aFactory) {
   103         this._window = aWindow;
   104         this._factory = aFactory || null;
   106         var prefBranch = Services.prefs.getBranch("signon.");
   107         this._debug = prefBranch.getBoolPref("debug");
   108         this.log("===== initialized =====");
   109     },
   112     /*
   113      * promptToSavePassword
   114      *
   115      */
   116     promptToSavePassword : function (aLogin) {
   117         this._showSaveLoginNotification(aLogin);
   118     },
   121     /*
   122      * _showLoginNotification
   123      *
   124      * Displays a notification doorhanger.
   125      *
   126      */
   127     _showLoginNotification : function (aName, aText, aButtons) {
   128         this.log("Adding new " + aName + " notification bar");
   129         let notifyWin = this._window.top;
   130         let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
   131         let browser = chromeWin.BrowserApp.getBrowserForWindow(notifyWin);
   132         let tabID = chromeWin.BrowserApp.getTabForBrowser(browser).id;
   134         // The page we're going to hasn't loaded yet, so we want to persist
   135         // across the first location change.
   137         // Sites like Gmail perform a funky redirect dance before you end up
   138         // at the post-authentication page. I don't see a good way to
   139         // heuristically determine when to ignore such location changes, so
   140         // we'll try ignoring location changes based on a time interval.
   142         let options = {
   143             persistWhileVisible: true,
   144             timeout: Date.now() + 10000
   145         }
   147         var nativeWindow = this._getNativeWindow();
   148         if (nativeWindow)
   149             nativeWindow.doorhanger.show(aText, aName, aButtons, tabID, options);
   150     },
   153     /*
   154      * _showSaveLoginNotification
   155      *
   156      * Displays a notification doorhanger (rather than a popup), to allow the user to
   157      * save the specified login. This allows the user to see the results of
   158      * their login, and only save a login which they know worked.
   159      *
   160      */
   161     _showSaveLoginNotification : function (aLogin) {
   162         var displayHost = this._getShortDisplayHost(aLogin.hostname);
   163         var notificationText;
   164         if (aLogin.username) {
   165             var displayUser = this._sanitizeUsername(aLogin.username);
   166             notificationText  = this._getLocalizedString("savePassword", [displayUser, displayHost]);
   167         } else {
   168             notificationText  = this._getLocalizedString("savePasswordNoUser", [displayHost]);
   169         }
   171         // The callbacks in |buttons| have a closure to access the variables
   172         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
   173         // without a getService() call.
   174         var pwmgr = this._pwmgr;
   176         var buttons = [
   177             {
   178                 label: this._getLocalizedString("saveButton"),
   179                 callback: function() {
   180                     pwmgr.addLogin(aLogin);
   181                 }
   182             },
   183             {
   184                 label: this._getLocalizedString("dontSaveButton"),
   185                 callback: function() {
   186                     // Don't set a permanent exception
   187                 }
   188             }
   189         ];
   191         this._showLoginNotification("password-save", notificationText, buttons);
   192     },
   194     /*
   195      * promptToChangePassword
   196      *
   197      * Called when we think we detect a password change for an existing
   198      * login, when the form being submitted contains multiple password
   199      * fields.
   200      *
   201      */
   202     promptToChangePassword : function (aOldLogin, aNewLogin) {
   203         this._showChangeLoginNotification(aOldLogin, aNewLogin.password);
   204     },
   206     /*
   207      * _showChangeLoginNotification
   208      *
   209      * Shows the Change Password notification doorhanger.
   210      *
   211      */
   212     _showChangeLoginNotification : function (aOldLogin, aNewPassword) {
   213         var notificationText;
   214         if (aOldLogin.username) {
   215             let displayUser = this._sanitizeUsername(aOldLogin.username);
   216             notificationText  = this._getLocalizedString("updatePassword", [displayUser]);
   217         } else {
   218             notificationText  = this._getLocalizedString("updatePasswordNoUser");
   219         }
   221         // The callbacks in |buttons| have a closure to access the variables
   222         // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
   223         // without a getService() call.
   224         var self = this;
   226         var buttons = [
   227             {
   228                 label: this._getLocalizedString("updateButton"),
   229                 callback:  function() {
   230                     self._updateLogin(aOldLogin, aNewPassword);
   231                 }
   232             },
   233             {
   234                 label: this._getLocalizedString("dontUpdateButton"),
   235                 callback:  function() {
   236                     // do nothing
   237                 }
   238             }
   239         ];
   241         this._showLoginNotification("password-change", notificationText, buttons);
   242     },
   245     /*
   246      * promptToChangePasswordWithUsernames
   247      *
   248      * Called when we detect a password change in a form submission, but we
   249      * don't know which existing login (username) it's for. Asks the user
   250      * to select a username and confirm the password change.
   251      *
   252      * Note: The caller doesn't know the username for aNewLogin, so this
   253      *       function fills in .username and .usernameField with the values
   254      *       from the login selected by the user.
   255      * 
   256      * Note; XPCOM stupidity: |count| is just |logins.length|.
   257      */
   258     promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
   259         const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
   261         var usernames = logins.map(function (l) l.username);
   262         var dialogText  = this._getLocalizedString("userSelectText");
   263         var dialogTitle = this._getLocalizedString("passwordChangeTitle");
   264         var selectedIndex = { value: null };
   266         // If user selects ok, outparam.value is set to the index
   267         // of the selected username.
   268         var ok = this._promptService.select(null,
   269                                 dialogTitle, dialogText,
   270                                 usernames.length, usernames,
   271                                 selectedIndex);
   272         if (ok) {
   273             // Now that we know which login to use, modify its password.
   274             var selectedLogin = logins[selectedIndex.value];
   275             this.log("Updating password for user " + selectedLogin.username);
   276             this._updateLogin(selectedLogin, aNewLogin.password);
   277         }
   278     },
   283     /* ---------- Internal Methods ---------- */
   288     /*
   289      * _updateLogin
   290      */
   291     _updateLogin : function (login, newPassword) {
   292         var now = Date.now();
   293         var propBag = Cc["@mozilla.org/hash-property-bag;1"].
   294                       createInstance(Ci.nsIWritablePropertyBag);
   295         if (newPassword) {
   296             propBag.setProperty("password", newPassword);
   297             // Explicitly set the password change time here (even though it would
   298             // be changed automatically), to ensure that it's exactly the same
   299             // value as timeLastUsed.
   300             propBag.setProperty("timePasswordChanged", now);
   301         }
   302         propBag.setProperty("timeLastUsed", now);
   303         propBag.setProperty("timesUsedIncrement", 1);
   304         this._pwmgr.modifyLogin(login, propBag);
   305     },
   307     /*
   308      * _getChromeWindow
   309      *
   310      * Given a content DOM window, returns the chrome window it's in.
   311      */
   312     _getChromeWindow: function (aWindow) {
   313         var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   314                                .getInterface(Ci.nsIWebNavigation)
   315                                .QueryInterface(Ci.nsIDocShell)
   316                                .chromeEventHandler.ownerDocument.defaultView;
   317         return chromeWin;
   318     },
   320     /*
   321      * _getNativeWindow
   322      *
   323      * Returns the NativeWindow to this prompter, or null if there isn't
   324      * a NativeWindow available (w/ error sent to logcat).
   325      */
   326     _getNativeWindow : function () {
   327         let nativeWindow = null;
   328         try {
   329             let notifyWin = this._window.top;
   330             let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
   331             if (chromeWin.NativeWindow) {
   332                 nativeWindow = chromeWin.NativeWindow;
   333             } else {
   334                 Cu.reportError("NativeWindow not available on window");
   335             }
   337         } catch (e) {
   338             // If any errors happen, just assume no native window helper.
   339             Cu.reportError("No NativeWindow available: " + e);
   340         }
   341         return nativeWindow;
   342     },
   344     /*
   345      * _getLocalizedString
   346      *
   347      * Can be called as:
   348      *   _getLocalizedString("key1");
   349      *   _getLocalizedString("key2", ["arg1"]);
   350      *   _getLocalizedString("key3", ["arg1", "arg2"]);
   351      *   (etc)
   352      *
   353      * Returns the localized string for the specified key,
   354      * formatted if required.
   355      *
   356      */ 
   357     _getLocalizedString : function (key, formatArgs) {
   358         if (formatArgs)
   359             return this._strBundle.formatStringFromName(
   360                                         key, formatArgs, formatArgs.length);
   361         else
   362             return this._strBundle.GetStringFromName(key);
   363     },
   366     /*
   367      * _sanitizeUsername
   368      *
   369      * Sanitizes the specified username, by stripping quotes and truncating if
   370      * it's too long. This helps prevent an evil site from messing with the
   371      * "save password?" prompt too much.
   372      */
   373     _sanitizeUsername : function (username) {
   374         if (username.length > 30) {
   375             username = username.substring(0, 30);
   376             username += this._ellipsis;
   377         }
   378         return username.replace(/['"]/g, "");
   379     },
   382     /*
   383      * _getFormattedHostname
   384      *
   385      * The aURI parameter may either be a string uri, or an nsIURI instance.
   386      *
   387      * Returns the hostname to use in a nsILoginInfo object (for example,
   388      * "http://example.com").
   389      */
   390     _getFormattedHostname : function (aURI) {
   391         var uri;
   392         if (aURI instanceof Ci.nsIURI) {
   393             uri = aURI;
   394         } else {
   395             uri = Services.io.newURI(aURI, null, null);
   396         }
   397         var scheme = uri.scheme;
   399         var hostname = scheme + "://" + uri.host;
   401         // If the URI explicitly specified a port, only include it when
   402         // it's not the default. (We never want "http://foo.com:80")
   403         port = uri.port;
   404         if (port != -1) {
   405             var handler = Services.io.getProtocolHandler(scheme);
   406             if (port != handler.defaultPort)
   407                 hostname += ":" + port;
   408         }
   410         return hostname;
   411     },
   414     /*
   415      * _getShortDisplayHost
   416      *
   417      * Converts a login's hostname field (a URL) to a short string for
   418      * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
   419      * "ftp://www.site.co.uk" --> "site.co.uk".
   420      */
   421     _getShortDisplayHost: function (aURIString) {
   422         var displayHost;
   424         var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
   425                           getService(Ci.nsIEffectiveTLDService);
   426         var idnService = Cc["@mozilla.org/network/idn-service;1"].
   427                          getService(Ci.nsIIDNService);
   428         try {
   429             var uri = Services.io.newURI(aURIString, null, null);
   430             var baseDomain = eTLDService.getBaseDomain(uri);
   431             displayHost = idnService.convertToDisplayIDN(baseDomain, {});
   432         } catch (e) {
   433             this.log("_getShortDisplayHost couldn't process " + aURIString);
   434         }
   436         if (!displayHost)
   437             displayHost = aURIString;
   439         return displayHost;
   440     },
   442 }; // end of LoginManagerPrompter implementation
   445 var component = [LoginManagerPrompter];
   446 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);

mercurial