Bug 1719414 - Support auth in NntpClient.jsm. r=mkmelin
- Implement nsINntpIncomingServer.loadNewsUrl - Enable auth related tests Differential Revision: https://phabricator.services.mozilla.com/D126756 --HG-- extra : amend_source : 8c37983a79b76730dc4155c3c720b9cc5d99a7e3
This commit is contained in:
Родитель
0bc654a66d
Коммит
3240fc42b3
|
@ -2,7 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["SmtpAuthenticator"];
|
||||
const EXPORTED_SYMBOLS = ["SmtpAuthenticator", "NntpAuthenticator"];
|
||||
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var { MailServices } = ChromeUtils.import(
|
||||
|
@ -276,3 +276,35 @@ class SmtpAuthenticator extends MailAuthenticator {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of helper functions for authenticating an incoming server.
|
||||
* @extends MailAuthenticator
|
||||
*/
|
||||
class IncomingServerAuthenticator extends MailAuthenticator {
|
||||
/**
|
||||
* @param {nsIMsgIncomingServer} server - The associated server instance.
|
||||
*/
|
||||
constructor(server) {
|
||||
super();
|
||||
this._server = server;
|
||||
}
|
||||
|
||||
get hostname() {
|
||||
return this._server.hostname;
|
||||
}
|
||||
|
||||
get username() {
|
||||
return this._server.username;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of helper functions for authenticating a NNTP connection.
|
||||
* @extends IncomingServerAuthenticator
|
||||
*/
|
||||
class NntpAuthenticator extends IncomingServerAuthenticator {
|
||||
promptAuthFailed() {
|
||||
return this._promptAuthFailed(null, this._server.prettyName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class MsgIncomingServer {
|
|||
|
||||
constructor() {
|
||||
// nsIMsgIncomingServer attributes that map directly to pref values.
|
||||
[
|
||||
this._mapAttrsToPrefs([
|
||||
["Unichar", "username", "userName"],
|
||||
["Char", "type"],
|
||||
["Char", "clientid"],
|
||||
|
@ -45,7 +45,19 @@ class MsgIncomingServer {
|
|||
["Bool", "canFileMessagesOnServer", "canFileMessages"],
|
||||
["Bool", "limitOfflineMessageSize", "limie_offline_message_size"],
|
||||
["Bool", "hidden"],
|
||||
].forEach(([type, attrName, prefName]) => {
|
||||
]);
|
||||
|
||||
// nsIMsgIncomingServer attributes.
|
||||
this.performingBiff = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up getters/setters for attributes that map directly to pref values.
|
||||
* @param {string[]} - An array of attributes, each attribute is defined by
|
||||
* its type, name and corresponding prefName.
|
||||
*/
|
||||
_mapAttrsToPrefs(attributes) {
|
||||
for (let [type, attrName, prefName] of attributes) {
|
||||
prefName = prefName || attrName;
|
||||
Object.defineProperty(this, attrName, {
|
||||
get: () => this[`get${type}Value`](prefName),
|
||||
|
@ -53,10 +65,7 @@ class MsgIncomingServer {
|
|||
this[`set${type}Value`](prefName, value);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// nsIMsgIncomingServer attributes.
|
||||
this.performingBiff = false;
|
||||
}
|
||||
}
|
||||
|
||||
get key() {
|
||||
|
@ -350,7 +359,17 @@ class MsgIncomingServer {
|
|||
this.clientid = "";
|
||||
}
|
||||
|
||||
forgetPassword() {}
|
||||
forgetPassword() {
|
||||
let serverURI = `${this.localStoreType}://${encodeURIComponent(
|
||||
this.hostName
|
||||
)}`;
|
||||
let logins = Services.logins.findLogins(serverURI, "", serverURI);
|
||||
for (let login of logins) {
|
||||
if (login.username == this.username) {
|
||||
Services.logins.removeLogin(login);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeCachedConnections() {}
|
||||
|
||||
|
|
|
@ -6,6 +6,16 @@ const EXPORTED_SYMBOLS = ["NntpClient"];
|
|||
|
||||
var { CommonUtils } = ChromeUtils.import("resource://services-common/utils.js");
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
var { NntpNewsGroup } = ChromeUtils.import(
|
||||
"resource:///modules/NntpNewsGroup.jsm"
|
||||
);
|
||||
|
||||
// Server response code.
|
||||
const AUTH_ACCEPTED = 281;
|
||||
const AUTH_PASSWORD_REQUIRED = 381;
|
||||
const AUTH_REQUIRED = 480;
|
||||
const AUTH_FAILED = 481;
|
||||
const SERVICE_UNAVAILABLE = 502;
|
||||
|
||||
/**
|
||||
* A structure to represent a response received from the server. A response can
|
||||
|
@ -46,6 +56,19 @@ class NntpClient {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {NntpAuthenticator} - An authentication helper.
|
||||
*/
|
||||
get _authenticator() {
|
||||
if (!this._nntpAuthenticator) {
|
||||
var { NntpAuthenticator } = ChromeUtils.import(
|
||||
"resource:///modules/MailAuthenticator.jsm"
|
||||
);
|
||||
this._nntpAuthenticator = new NntpAuthenticator(this._server);
|
||||
}
|
||||
return this._nntpAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a connection to the server
|
||||
*/
|
||||
|
@ -72,7 +95,13 @@ class NntpClient {
|
|||
this._logger.debug("Connected");
|
||||
this._socket.ondata = this._onData;
|
||||
this._socket.onclose = this._onClose;
|
||||
this.runningUri.SetUrlState(true, Cr.NS_OK);
|
||||
this._nextAction = ({ status }) => {
|
||||
if (status == 200) {
|
||||
this._nextAction = null;
|
||||
this.onOpen();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -87,6 +116,15 @@ class NntpClient {
|
|||
|
||||
let res = this._parse(stringPayload);
|
||||
|
||||
switch (res.status) {
|
||||
case AUTH_REQUIRED:
|
||||
this._actionAuthUser();
|
||||
return;
|
||||
case SERVICE_UNAVAILABLE:
|
||||
this._actionDone();
|
||||
return;
|
||||
}
|
||||
|
||||
this._nextAction?.(res);
|
||||
};
|
||||
|
||||
|
@ -148,8 +186,7 @@ class NntpClient {
|
|||
* Send a LIST command to get all the groups in the current server.
|
||||
*/
|
||||
getListOfGroups() {
|
||||
this._sendCommand("LIST");
|
||||
this._nextAction = this._actionReadData;
|
||||
this._actionModeReader(this._actionList);
|
||||
this._urlListener = this._server.QueryInterface(Ci.nsIUrlListener);
|
||||
}
|
||||
|
||||
|
@ -166,7 +203,7 @@ class NntpClient {
|
|||
this._urlListener = urlListener;
|
||||
this._msgWindow = msgWindow;
|
||||
this.runningUri.updatingFolder = true;
|
||||
this._nextAction = this._actionModeReader;
|
||||
this._actionModeReader(this._actionGroup);
|
||||
this._firstCommand = this._actionXOver;
|
||||
}
|
||||
|
||||
|
@ -178,7 +215,7 @@ class NntpClient {
|
|||
getArticleByArticleNumber(groupName, articleNumber) {
|
||||
this._groupName = groupName;
|
||||
this._articleNumber = articleNumber;
|
||||
this._nextAction = this._actionModeReader;
|
||||
this._actionModeReader(this._actionGroup);
|
||||
this._firstCommand = this._actionArticle;
|
||||
}
|
||||
|
||||
|
@ -188,7 +225,50 @@ class NntpClient {
|
|||
*/
|
||||
getArticleByMessageId(messageId) {
|
||||
this._articleNumber = `<${messageId}>`;
|
||||
this._nextAction = this._actionArticle;
|
||||
this._actionModeReader(this._actionArticle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a news uri directly, see rfc5538 about supported news uri.
|
||||
* @param {string} uir - The news uri to load.
|
||||
* @param {nsIMsgWindow} msgWindow - The associated msg window.
|
||||
* @param {nsIStreamListener} streamListener - The listener for the request.
|
||||
*/
|
||||
loadNewsUrl(uri, msgWindow, streamListener) {
|
||||
this._logger.debug(`Loading ${uri}`);
|
||||
let url = new URL(uri);
|
||||
let path = url.pathname.slice(1);
|
||||
let action;
|
||||
if (path == "*") {
|
||||
action = () => this.getListOfGroups();
|
||||
} else if (path.includes("@")) {
|
||||
action = () => this.getArticleByMessageId(path);
|
||||
} else {
|
||||
this._groupName = path;
|
||||
this._newsGroup = new NntpNewsGroup(this._server, this._groupName);
|
||||
this._newsFolder = this._server.findGroup(this._groupName);
|
||||
action = () => this._actionModeReader(this._actionGroup);
|
||||
}
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
this._msgWindow = msgWindow;
|
||||
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
|
||||
pipe.init(true, true, 0, 0);
|
||||
let inputStream = pipe.inputStream;
|
||||
let outputStream = pipe.outputStream;
|
||||
this.connect();
|
||||
this.onOpen = () => {
|
||||
streamListener.onStartRequest(null, Cr.NS_OK);
|
||||
action();
|
||||
};
|
||||
this.onData = data => {
|
||||
outputStream.write(data, data.length);
|
||||
streamListener.onDataAvailable(null, inputStream, 0, data.length);
|
||||
};
|
||||
this.onDone = () => {
|
||||
streamListener.onStopRequest(null, Cr.NS_OK);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,9 +282,18 @@ class NntpClient {
|
|||
/**
|
||||
* Send `MODE READER` request to the server.
|
||||
*/
|
||||
_actionModeReader() {
|
||||
_actionModeReader(nextAction) {
|
||||
this._sendCommand("MODE READER");
|
||||
this._nextAction = this._actionGroup;
|
||||
this._nextAction = nextAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send `LIST` request to the server.
|
||||
*/
|
||||
_actionList() {
|
||||
this._sendCommand("LIST");
|
||||
this._currentAction = this._actionList;
|
||||
this._nextAction = this._actionReadData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,7 +301,8 @@ class NntpClient {
|
|||
*/
|
||||
_actionGroup() {
|
||||
this._sendCommand(`GROUP ${this._groupName}`);
|
||||
this._nextAction = this._firstCommand;
|
||||
this._currentAction = this._actionGroup;
|
||||
this._nextAction = this._firstCommand || this._actionXOver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -365,14 +455,72 @@ class NntpClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send `AUTHINFO user <name>` to the server.
|
||||
* @param {boolean} [forcePrompt=false] - Whether to force showing an auth prompt.
|
||||
*/
|
||||
_actionAuthUser(forcePrompt = false) {
|
||||
if (!this._newsFolder) {
|
||||
this._newsFolder = this._server.rootFolder.QueryInterface(
|
||||
Ci.nsIMsgNewsFolder
|
||||
);
|
||||
}
|
||||
if (!this._newsFolder.groupUsername) {
|
||||
this._newsFolder.getAuthenticationCredentials(
|
||||
this._msgWindow,
|
||||
true,
|
||||
forcePrompt
|
||||
);
|
||||
}
|
||||
this._sendCommand(`AUTHINFO user ${this._newsFolder.groupUsername}`);
|
||||
this._nextAction = this._actionAuthResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send `AUTHINFO pass <password>` to the server.
|
||||
*/
|
||||
_actionAuthPassword() {
|
||||
this._sendCommand(`AUTHINFO pass ${this._newsFolder.groupPassword}`);
|
||||
this._nextAction = this._actionAuthResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide the next step according to the auth response.
|
||||
* @param {NntpResponse} res - Auth response received from the server.
|
||||
*/
|
||||
_actionAuthResult({ status }) {
|
||||
switch (status) {
|
||||
case AUTH_ACCEPTED:
|
||||
this._currentAction?.();
|
||||
return;
|
||||
case AUTH_PASSWORD_REQUIRED:
|
||||
this._actionAuthPassword();
|
||||
return;
|
||||
case AUTH_FAILED:
|
||||
let action = this._authenticator.promptAuthFailed();
|
||||
if (action == 1) {
|
||||
// Cancel button pressed.
|
||||
this._actionDone();
|
||||
return;
|
||||
}
|
||||
if (action == 2) {
|
||||
// 'New password' button pressed.
|
||||
this._newsFolder.forgetAuthenticationCredentials();
|
||||
}
|
||||
// Retry.
|
||||
this._actionAuthUser();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection and do necessary cleanup.
|
||||
*/
|
||||
_actionDone() {
|
||||
this.onDone();
|
||||
this._newsGroup?.cleanUp();
|
||||
this._newsFolder?.OnStopRunningUrl(this.runningUri, 0);
|
||||
this._newsFolder?.OnStopRunningUrl?.(this.runningUri, 0);
|
||||
this._urlListener?.OnStopRunningUrl(this.runningUri, 0);
|
||||
this.runningUri.SetUrlState(false, Cr.NS_OK);
|
||||
this._socket.close();
|
||||
this._nextAction = null;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
MailServices: "resource:///modules/MailServices.jsm",
|
||||
NntpClient: "resource:///modules/NntpClient.jsm",
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -51,6 +52,16 @@ class NntpIncomingServer extends MsgIncomingServer {
|
|||
|
||||
// nsINntpIncomingServer attributes.
|
||||
this.newsrcHasChanged = false;
|
||||
|
||||
// nsINntpIncomingServer attributes that map directly to pref values.
|
||||
this._mapAttrsToPrefs([
|
||||
["Bool", "notifyOn", "notify.on"],
|
||||
["Bool", "markOldRead", "mark_old_read"],
|
||||
["Bool", "abbreviate", "abbreviate"],
|
||||
["Bool", "pushAuth", "always_authenticate"],
|
||||
["Bool", "singleSignon"],
|
||||
["Int", "maxArticles", "max_articles"],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -315,7 +326,28 @@ class NntpIncomingServer extends MsgIncomingServer {
|
|||
}
|
||||
|
||||
findGroup(name) {
|
||||
return this.rootMsgFolder.findSubFolder(name);
|
||||
return this.rootMsgFolder
|
||||
.findSubFolder(name)
|
||||
.QueryInterface(Ci.nsIMsgNewsFolder);
|
||||
}
|
||||
|
||||
loadNewsUrl(uri, msgWindow, consumer) {
|
||||
if (consumer instanceof Ci.nsIStreamListener) {
|
||||
let client = new NntpClient(this);
|
||||
client.loadNewsUrl(uri.spec, msgWindow, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
forgetPassword() {
|
||||
let newsFolder = this.rootFolder.QueryInterface(Ci.nsIMsgNewsFolder);
|
||||
// Clear password of root folder.
|
||||
newsFolder.forgetAuthenticationCredentials();
|
||||
|
||||
// Clear password of all sub folders.
|
||||
for (let folder of newsFolder.subFolders) {
|
||||
folder.QueryInterface(Ci.nsIMsgNewsFolder);
|
||||
folder.forgetAuthenticationCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
_lineSeparator = AppConstants.platform == "win" ? "\r\n" : "\n";
|
||||
|
|
|
@ -14,11 +14,6 @@ prefs =
|
|||
# The server doesn't support returning sizes! (bug 782629)
|
||||
skip-if = true
|
||||
[test_newsAutocomplete.js]
|
||||
[test_nntpGroupPassword.js]
|
||||
[test_nntpPassword.js]
|
||||
[test_nntpPassword2.js]
|
||||
[test_nntpPassword3.js]
|
||||
[test_nntpPasswordFailure.js]
|
||||
[test_nntpProxy.js]
|
||||
[test_nntpUrl.js]
|
||||
[test_server.js]
|
||||
|
|
|
@ -5,5 +5,10 @@ run-sequentially = Restarts server twice--may work but dangerous
|
|||
[test_bug403242.js]
|
||||
[test_bug540288.js]
|
||||
[test_getNewsMessage.js]
|
||||
[test_nntpGroupPassword.js]
|
||||
[test_nntpPassword.js]
|
||||
[test_nntpPassword2.js]
|
||||
[test_nntpPassword3.js]
|
||||
[test_nntpPasswordFailure.js]
|
||||
[test_nntpPost.js]
|
||||
[test_nntpProtocols.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче