Bug 1708349 - Implement nsIAbDirectoryQuery in JS. r=darktrojan

Depends on D116805.

Differential Revision: https://phabricator.services.mozilla.com/D116966

--HG--
extra : amend_source : a501bf9b43320ed6cdd1873b1bc82ce83164ec87
This commit is contained in:
Ping Chen 2021-06-08 13:24:20 +03:00
Родитель a34fb418db
Коммит 07a04c6aed
6 изменённых файлов: 339 добавлений и 77 удалений

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

@ -164,12 +164,15 @@ class SearchRequest extends LDAPMessage {
this._convertFilterToBlock(filter),
// attributes
new asn1js.Sequence({
value: attributes.split(",").map(
attr =>
new asn1js.OctetString({
valueHex: new TextEncoder().encode(attr),
})
),
value: attributes
.split(",")
.filter(Boolean)
.map(
attr =>
new asn1js.OctetString({
valueHex: new TextEncoder().encode(attr),
})
),
}),
],
});

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

@ -0,0 +1,193 @@
/* 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/. */
const EXPORTED_SYMBOLS = ["LDAPDirectoryQuery"];
const { LDAPListenerBase } = ChromeUtils.import(
"resource:///modules/LDAPListenerBase.jsm"
);
/**
* Convert a nsIAbBooleanExpression to a filter string.
* @param {nsIAbLDAPAttributeMap} attrMap - A mapping between address book
* properties and ldap attributes.
* @param {nsIAbBooleanExpression} exp - The expression to convert.
* @returns {string}
*/
function boolExpressionToFilter(attrMap, exp) {
let filter = "(";
filter +=
{
[Ci.nsIAbBooleanOperationTypes.AND]: "&",
[Ci.nsIAbBooleanOperationTypes.OR]: "|",
[Ci.nsIAbBooleanOperationTypes.NOT]: "!",
}[exp.operation] || "";
if (exp.expressions) {
for (let childExp of exp.expressions) {
if (childExp instanceof Ci.nsIAbBooleanExpression) {
filter += boolExpressionToFilter(attrMap, childExp);
} else if (childExp instanceof Ci.nsIAbBooleanConditionString) {
filter += boolConditionToFilter(attrMap, childExp);
}
}
}
filter += ")";
return filter;
}
/**
* Convert a nsIAbBooleanConditionString to a filter string.
* @param {nsIAbLDAPAttributeMap} attrMap - A mapping between addressbook
* properties and ldap attributes.
* @param {nsIAbBooleanConditionString} exp - The expression to convert.
* @returns {string}
*/
function boolConditionToFilter(attrMap, exp) {
let attr = attrMap.getFirstAttribute(exp.name);
switch (exp.condition) {
case Ci.nsIAbBooleanConditionTypes.DoesNotExist:
return `(!(${attr}=*))`;
case Ci.nsIAbBooleanConditionTypes.Exists:
return `(${attr}=*)`;
case Ci.nsIAbBooleanConditionTypes.Contains:
return `(${attr}=*${exp.value}*)`;
case Ci.nsIAbBooleanConditionTypes.DoesNotContain:
return `(!(${attr}=*${exp.value}*))`;
case Ci.nsIAbBooleanConditionTypes.Is:
return `(${attr}=${exp.value})`;
case Ci.nsIAbBooleanConditionTypes.IsNot:
return `(!(${attr}=${exp.value}))`;
case Ci.nsIAbBooleanConditionTypes.BeginsWith:
return `(${attr}=${exp.value}*)`;
case Ci.nsIAbBooleanConditionTypes.EndsWith:
return `(${attr}=*${exp.value})`;
case Ci.nsIAbBooleanConditionTypes.LessThan:
return `(${attr}<=${exp.value})`;
case Ci.nsIAbBooleanConditionTypes.GreaterThan:
return `(${attr}>=${exp.value})`;
case Ci.nsIAbBooleanConditionTypes.SoundsLike:
return `(${attr}~=${exp.value})`;
default:
return "";
}
}
/**
* @implements {nsIAbDirectoryQuery}
*/
class LDAPDirectoryQuery extends LDAPListenerBase {
QueryInterface = ChromeUtils.generateQI(["nsIAbDirectoryQuery"]);
i = 0;
doQuery(directory, args, listener, limit, timeout) {
this._directory = directory.QueryInterface(Ci.nsIAbLDAPDirectory);
this._listener = listener;
this._attrMap = args.typeSpecificArg;
this._filter =
args.filter || boolExpressionToFilter(this._attrMap, args.expression);
this._limit = limit;
this._timeout = timeout;
this._connection = Cc[
"@mozilla.org/network/ldap-connection;1"
].createInstance(Ci.nsILDAPConnection);
this._operation = Cc[
"@mozilla.org/network/ldap-operation;1"
].createInstance(Ci.nsILDAPOperation);
this._connection.init(
directory.lDAPURL,
directory.authDn,
this,
null,
directory.protocolVersion
);
return this.i++;
}
stopQuery(contextId) {
this._operation?.abandonExt();
}
/**
* @see nsILDAPMessageListener
*/
onLDAPMessage(msg) {
switch (msg.type) {
case Ci.nsILDAPMessage.RES_BIND:
this._onLDAPBind(msg);
break;
case Ci.nsILDAPMessage.RES_SEARCH_ENTRY:
this._onLDAPSearchEntry(msg);
break;
case Ci.nsILDAPMessage.RES_SEARCH_RESULT:
this._onLDAPSearchResult(msg);
break;
default:
break;
}
}
/**
* @see nsILDAPMessageListener
*/
onLDAPError(status, secInfo, location) {
this._onSearchFinished(status, secInfo, location);
}
/**
* @see LDAPListenerBase
*/
_actionOnBindSuccess() {
let ldapUrl = this._directory.lDAPURL;
this._operation.searchExt(
ldapUrl.dn,
ldapUrl.scope,
this._filter,
ldapUrl.attributes,
this._timeout,
this._limit
);
}
/**
* @see LDAPListenerBase
*/
_actionOnBindFailure() {
this._onSearchFinished(Cr.NS_ERROR_FAILURE);
}
/**
* Handler of nsILDAPMessage.RES_SEARCH_ENTRY message.
* @param {nsILDAPMessage} msg - The received LDAP message.
*/
_onLDAPSearchEntry(msg) {
let newCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
Ci.nsIAbCard
);
this._attrMap.setCardPropertiesFromLDAPMessage(msg, newCard);
this._listener.onSearchFoundCard(newCard);
}
/**
* Handler of nsILDAPMessage.RES_SEARCH_RESULT message.
* @param {nsILDAPMessage} msg - The received LDAP message.
*/
_onLDAPSearchResult(msg) {
this._onSearchFinished(
msg.errorCode == Ci.nsILDAPErrors.SUCCESS ? Cr.NS_OK : Cr.NS_ERROR_FAILURE
);
}
_onSearchFinished(status, secInfo, localtion) {
this._listener.onSearchFinished(status, secInfo, localtion);
}
}
LDAPDirectoryQuery.prototype.classID = Components.ID(
"{5ad5d311-1a50-43db-a03c-63d45f443903}"
);

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

@ -0,0 +1,117 @@
/* -*- Mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
const EXPORTED_SYMBOLS = ["LDAPListenerBase"];
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
);
class LDAPListenerBase {
/**
* @see nsILDAPMessageListener
*/
onLDAPInit() {
let outPassword = {};
if (this._directory.authDn && this._directory.saslMechanism != "GSSAPI") {
// If authDn is set, we're expected to use it to get a password.
let bundle = Services.strings.createBundle(
"chrome://mozldap/locale/ldap.properties"
);
let authPrompt = Services.ww.getNewAuthPrompter(
Services.wm.getMostRecentWindow(null)
);
authPrompt.promptPassword(
bundle.GetStringFromName("authPromptTitle"),
bundle.formatStringFromName("authPromptText", [
this._directory.lDAPURL.host,
]),
this._directory.lDAPURL.spec,
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
outPassword
);
}
this._operation.init(this._connection, this, null);
if (this._directory.saslMechanism != "GSSAPI") {
this._operation.simpleBind(outPassword.value);
return;
}
// Handle GSSAPI now.
this._operation.saslBind(
`ldap@${this._directory.lDAPURL.host}`,
"GSSAPI",
"sasl-gssapi"
);
}
/**
* Handler of nsILDAPMessage.RES_BIND message.
* @param {nsILDAPMessage} msg - The received LDAP message.
*/
_onLDAPBind(msg) {
let errCode = msg.errorCode;
if (
errCode == Ci.nsILDAPErrors.INAPPROPRIATE_AUTH ||
errCode == Ci.nsILDAPErrors.INVALID_CREDENTIALS
) {
// Login failed, remove any existing login(s).
let ldapUrl = this._directory.lDAPURL;
let logins = Services.logins.findLogins(
ldapUrl.prePath,
"",
ldapUrl.spec
);
for (let login of logins) {
Services.logins.removeLogin(login);
}
// Trigger the auth prompt.
this.onLDAPInit();
return;
}
if (errCode != Ci.nsILDAPErrors.SUCCESS) {
this._actionOnBindFailure();
return;
}
this._actionOnBindSuccess();
}
/**
* @see nsILDAPMessageListener
* @abstract
*/
onLDAPMessage() {
throw new Components.Exception(
`${this.constructor.name} does not implement onLDAPMessage.`,
Cr.NS_ERROR_NOT_IMPLEMENTED
);
}
/**
* Callback when BindResponse succeeded.
* @abstract
*/
_actionOnBindSuccess() {
throw new Components.Exception(
`${this.constructor.name} does not implement _actionOnBindSuccess.`,
Cr.NS_ERROR_NOT_IMPLEMENTED
);
}
/**
* Callback when BindResponse failed.
* @abstract
*/
_actionOnBindFailure() {
throw new Components.Exception(
`${this.constructor.name} does not implement _actionOnBindFailure.`,
Cr.NS_ERROR_NOT_IMPLEMENTED
);
}
}

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

@ -38,6 +38,11 @@ var ldapJSModules = [
"{8683e821-f1b0-476d-ac15-07771c79bb11}",
"@mozilla.org/addressbook/directory;1?type=moz-abldapdirectory",
],
[
"LDAPDirectoryQuery",
"{5ad5d311-1a50-43db-a03c-63d45f443903}",
"@mozilla.org/addressbook/ldap-directory-query;1",
],
[
"LDAPReplicationService",
"{dbe204e8-ae09-11eb-b4c8-a7e4b3e6e82e}",

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

@ -5,21 +5,21 @@
const EXPORTED_SYMBOLS = ["LDAPReplicationService"];
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { LDAPListenerBase } = ChromeUtils.import(
"resource:///modules/LDAPListenerBase.jsm"
);
/**
* A service to replicate a LDAP directory to a local SQLite db.
* @implements {nsIAbLDAPReplicationService}
* @implements {nsILDAPMessageListener}
*/
class LDAPReplicationService {
class LDAPReplicationService extends LDAPListenerBase {
QueryInterface = ChromeUtils.generateQI([
"nsIAbLDAPReplicationService",
"nsILDAPMessageListener",
]);
_requestNum = 0;
/**
* @see nsIAbLDAPReplicationService
*/
@ -59,46 +59,6 @@ class LDAPReplicationService {
this._done(success);
}
/**
* @see nsILDAPMessageListener
*/
onLDAPInit() {
let outPassword = {};
if (this._directory.authDn && this._directory.saslMechanism != "GSSAPI") {
// If authDn is set, we're expected to use it to get a password.
let bundle = Services.strings.createBundle(
"chrome://mozldap/locale/ldap.properties"
);
let authPrompt = Services.ww.getNewAuthPrompter(
Services.wm.getMostRecentWindow(null)
);
authPrompt.promptPassword(
bundle.GetStringFromName("authPromptTitle"),
bundle.formatStringFromName("authPromptText", [
this._directory.lDAPURL.host,
]),
this._directory.lDAPURL.spec,
Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
outPassword
);
}
this._operation.init(this._connection, this, null);
this._operation.requestNum = ++this._requestNum;
if (this._directory.saslMechanism != "GSSAPI") {
this._operation.simpleBind(outPassword.value);
return;
}
// Handle GSSAPI now.
this._operation.saslBind(
`ldap@${this._directory.lDAPURL.host}`,
"GSSAPI",
"sasl-gssapi"
);
}
/**
* @see nsILDAPMessageListener
*/
@ -126,37 +86,12 @@ class LDAPReplicationService {
}
/**
* Handler of nsILDAPMessage.RES_BIND message.
* @param {nsILDAPMessage} msg - The received LDAP message.
* @see LDAPListenerBase
*/
_onLDAPBind(msg) {
let errCode = msg.errorCode;
if (
errCode == Ci.nsILDAPErrors.INAPPROPRIATE_AUTH ||
errCode == Ci.nsILDAPErrors.INVALID_CREDENTIALS
) {
// Login failed, remove any existing login(s).
let ldapUrl = this._directory.lDAPURL;
let logins = Services.logins.findLogins(
ldapUrl.prePath,
"",
ldapUrl.spec
);
for (let login of logins) {
Services.logins.removeLogin(login);
}
// Trigger the auth prompt.
this.onLDAPInit();
return;
}
if (errCode != Ci.nsILDAPErrors.SUCCESS) {
this.done(false);
return;
}
_actionOnBindSuccess() {
this._openABForReplicationDir();
let ldapUrl = this._directory.lDAPURL;
this._operation.init(this._connection, this, null);
this._operation.requestNum = ++this._requestNum;
this._listener.onStateChange(
null,
null,
@ -173,6 +108,13 @@ class LDAPReplicationService {
);
}
/**
* @see LDAPListenerBase
*/
_actionOnBindFailure() {
this._done(false);
}
/**
* Handler of nsILDAPMessage.RES_SEARCH_ENTRY message.
* @param {nsILDAPMessage} msg - The received LDAP message.

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

@ -11,6 +11,8 @@ EXTRA_JS_MODULES += [
"CardDAVDirectory.jsm",
"CardDAVUtils.jsm",
"LDAPDirectory.jsm",
"LDAPDirectoryQuery.jsm",
"LDAPListenerBase.jsm",
"LDAPModuleLoader.jsm",
"LDAPReplicationService.jsm",
"QueryStringToExpression.jsm",