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:
Родитель
a34fb418db
Коммит
07a04c6aed
|
@ -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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче