Dactyloidae/mailnews/base/src/msgOAuth2Module.js

174 lines
6 KiB
JavaScript

/* 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/. */
"use strict";
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cr = Components.results;
Components.utils.import("resource://gre/modules/OAuth2.jsm");
Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
Components.utils.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function OAuth2Module() {
this._refreshToken = '';
}
OAuth2Module.prototype = {
// XPCOM registration stuff
QueryInterface: XPCOMUtils.generateQI([Ci.msgIOAuth2Module]),
classID: Components.ID("{b63d8e4c-bf60-439b-be0e-7c9f67291042}"),
_loadOAuthClientDetails(aIssuer) {
let details = OAuth2Providers.getIssuerDetails(aIssuer);
if (details)
[this._appKey, this._appSecret, this._authURI, this._tokenURI] = details;
else
throw Cr.NS_ERROR_INVALID_ARGUMENT;
},
initFromSmtp(aServer) {
return this._initPrefs("mail.smtpserver." + aServer.key + ".",
aServer.username, aServer.hostname);
},
initFromMail(aServer) {
return this._initPrefs("mail.server." + aServer.key + ".",
aServer.username, aServer.realHostName);
},
_initPrefs(root, aUsername, aHostname) {
// Load all of the parameters from preferences.
let issuer = Preferences.get(root + "oauth2.issuer", "");
let scope = Preferences.get(root + "oauth2.scope", "");
// These properties are absolutely essential to OAuth2 support. If we don't
// have them, we don't support OAuth2.
if (!issuer || !scope) {
// Since we currently only support gmail, init values if server matches.
let details = OAuth2Providers.getHostnameDetails(aHostname);
if (details)
{
[issuer, scope] = details;
Preferences.set(root + "oauth2.issuer", issuer);
Preferences.set(root + "oauth2.scope", scope);
}
else
return false;
}
// Find the app key we need for the OAuth2 string. Eventually, this should
// be using dynamic client registration, but there are no current
// implementations that we can test this with.
this._loadOAuthClientDetails(issuer);
// Username is needed to generate the XOAUTH2 string.
this._username = aUsername;
// LoginURL is needed to save the refresh token in the password manager.
this._loginUrl = "oauth://" + issuer;
// We use the scope to indicate the realm.
this._scope = scope;
// Define the OAuth property and store it.
this._oauth = new OAuth2(this._authURI, scope, this._appKey,
this._appSecret);
this._oauth.authURI = this._authURI;
this._oauth.tokenURI = this._tokenURI;
// Try hinting the username...
this._oauth.extraAuthParams = [
["login_hint", aUsername]
];
// Set the window title to something more useful than "Unnamed"
this._oauth.requestWindowTitle =
Services.strings.createBundle("chrome://messenger/locale/messenger.properties")
.formatStringFromName("oauth2WindowTitle",
[aUsername, aHostname], 2);
// This stores the refresh token in the login manager.
Object.defineProperty(this._oauth, "refreshToken", {
get: () => this.refreshToken,
set: (token) => this.refreshToken = token
});
return true;
},
get refreshToken() {
let loginMgr = Cc["@mozilla.org/login-manager;1"]
.getService(Ci.nsILoginManager);
let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope);
for (let login of logins) {
if (login.username == this._username)
return login.password;
}
return '';
},
set refreshToken(token) {
let loginMgr = Cc["@mozilla.org/login-manager;1"]
.getService(Ci.nsILoginManager);
// Check if we already have a login with this username, and modify the
// password on that, if we do.
let logins = loginMgr.findLogins({}, this._loginUrl, null, this._scope);
for (let login of logins) {
if (login.username == this._username) {
if (token) {
let propBag = Cc["@mozilla.org/hash-property-bag;1"].
createInstance(Ci.nsIWritablePropertyBag);
propBag.setProperty("password", token);
loginMgr.modifyLogin(login, propBag);
}
else
loginMgr.removeLogin(login);
return token;
}
}
// Unless the token is null, we need to create and fill in a new login
if (token) {
let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
login.init(this._loginUrl, null, this._scope, this._username, token,
'', '');
loginMgr.addLogin(login);
}
return token;
},
connect(aWithUI, aListener) {
let oauth = this._oauth;
let promptlistener = {
onPromptStartAsync: function(callback) {
oauth.connect(() => {
this.onPromptAuthAvailable();
callback.onAuthResult(true);
}, (err) => {
this.onPromptCanceled();
callback.onAuthResult(false);
}, aWithUI, false);
},
onPromptAuthAvailable: function() {
aListener.onSuccess(oauth.accessToken);
},
onPromptCanceled: function() {
aListener.onFailure(Components.results.NS_ERROR_ABORT);
},
onPromptStart: function() {}
};
let asyncprompter = Components.classes["@mozilla.org/messenger/msgAsyncPrompter;1"]
.getService(Components.interfaces.nsIMsgAsyncPrompter);
let promptkey = this._loginUrl + "/" + this._username;
asyncprompter.queueAsyncAuthPrompt(promptkey, false, promptlistener);
},
buildXOAuth2String() {
return btoa("user=" + this._username + "\x01auth=Bearer " +
this._oauth.accessToken + "\x01\x01");
},
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([OAuth2Module]);