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:
Ping Chen 2021-09-29 12:30:39 +03:00
Родитель 0bc654a66d
Коммит 3240fc42b3
6 изменённых файлов: 255 добавлений и 24 удалений

Просмотреть файл

@ -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.onOpen();
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]