From 123cb71a1b498c8e6411550a6abc81edfe8ef040 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Tue, 03 May 2016 15:49:56 +0000
Subject: [PATCH] OPENDJ-2933 Create the opendj-openidm-account-change-notification-handler module
---
opendj-openidm-account-change-notification-handler/legal-notices/THIRDPARTYREADME.txt | 149 ++++
opendj-openidm-account-change-notification-handler/src/main/assembly/descriptor.xml | 67 +
opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/Package.xml | 21
pom.xml | 1
opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandler.java | 755 +++++++++++++++++++++
opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/package-info.java | 21
opendj-openidm-account-change-notification-handler/src/main/resources/org/forgerock/openidm/accountchange/openidm-account-status-notification-handler.properties | 69 +
opendj-openidm-account-change-notification-handler/pom.xml | 185 +++++
opendj-server-legacy/src/main/java/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java | 2
opendj-openidm-account-change-notification-handler/src/site/xdoc/index.xml.vm | 99 ++
opendj-openidm-account-change-notification-handler/src/main/assembly/config/openidm-accountchange-plugin-sample-config | 22
opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandlerConfiguration.xml | 440 ++++++++++++
opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/PersistedQueue.java | 181 +++++
opendj-openidm-account-change-notification-handler/.gitignore | 1
opendj-openidm-account-change-notification-handler/src/main/assembly/config/schema/90-openidm-accountchange-plugin.ldif | 79 ++
15 files changed, 2,091 insertions(+), 1 deletions(-)
diff --git a/opendj-openidm-account-change-notification-handler/.gitignore b/opendj-openidm-account-change-notification-handler/.gitignore
new file mode 100644
index 0000000..916e17c
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/.gitignore
@@ -0,0 +1 @@
+dependency-reduced-pom.xml
diff --git a/opendj-openidm-account-change-notification-handler/legal-notices/THIRDPARTYREADME.txt b/opendj-openidm-account-change-notification-handler/legal-notices/THIRDPARTYREADME.txt
new file mode 100644
index 0000000..2f54a12
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/legal-notices/THIRDPARTYREADME.txt
@@ -0,0 +1,149 @@
+DO NOT TRANSLATE OR LOCALIZE
+
+***************************************************************************
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
+***************************************************************************
+
+Version: chf-client-apache-sync.jar (3.0.1)
+Copyright: Copyright 2009 Sun Microsystems Inc.
+ Portions Copyright 2010-2011 ApexIdentity Inc.
+ Portions Copyright 2011-2015 ForgeRock AS.
+ Copyright 2015 ForgeRock AS.
+
+Version: json-crypto-core.jar (3.0.2)
+Copyright: Copyright 2011-2015 ForgeRock AS.
+
+
+==================
+Full license text:
+==================
+
+COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 (CDDL-1.0)
+
+1. Definitions.
+
+1.1. Contributor means each individual or entity that creates or contributes to the creation of Modifications.
+
+1.2. Contributor Version means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor.
+
+1.3. Covered Software means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof.
+
+1.4. Executable means the Covered Software in any form other than Source Code.
+
+1.5. Initial Developer means the individual or entity that first makes Original Software available under this License.
+
+1.6. Larger Work means a work which combines Covered Software or portions thereof with code not governed by the terms of this License.
+
+1.7. License means this document.
+
+1.8. Licensable means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein.
+
+1.9. Modifications means the Source Code and Executable form of any of the following:
+
+A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications;
+
+B. Any new file that contains any part of the Original Software or previous Modification; or
+
+C. Any new file that is contributed or otherwise made available under the terms of this License.
+
+1.10. Original Software means the Source Code and Executable form of computer software code that is originally released under this License.
+
+1.11. Patent Claims means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor.
+
+1.12. Source Code means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code.
+
+1.13. You (or Your) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, You includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, control means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.
+
+2. License Grants.
+
+2.1. The Initial Developer Grant.
+
+Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and
+
+(b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof).
+
+(c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License.
+
+(d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices.
+
+2.2. Contributor Grant.
+
+Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and
+
+(b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination).
+
+(c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party.
+
+(d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor.
+
+3. Distribution Obligations.
+
+3.1. Availability of Source Code.
+
+Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange.
+
+3.2. Modifications.
+
+The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License.
+
+3.3. Required Notices.
+
+You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer.
+
+3.4. Application of Additional Terms.
+
+You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer.
+
+3.5. Distribution of Executable Versions.
+
+You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipients rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer.
+
+3.6. Larger Works.
+
+You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software.
+
+4. Versions of the License.
+
+4.1. New Versions.
+
+Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License.
+
+4.2. Effect of New Versions.
+
+You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward.
+
+4.3. Modified Versions.
+
+When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License.
+
+5. DISCLAIMER OF WARRANTY.
+
+COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+6. TERMINATION.
+
+6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive.
+
+6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as Participant) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant.
+
+6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination.
+
+7. LIMITATION OF LIABILITY.
+
+UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTYS NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+8. U.S. GOVERNMENT END USERS.
+
+The Covered Software is a commercial item, as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of commercial computer software (as that term is defined at 48 C.F.R. 252.227-7014(a)(1)) and commercial computer software documentation as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License.
+
+9. MISCELLANEOUS.
+
+This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdictions conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software.
+
+10. RESPONSIBILITY FOR CLAIMS.
+
+As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability.
diff --git a/opendj-openidm-account-change-notification-handler/pom.xml b/opendj-openidm-account-change-notification-handler/pom.xml
new file mode 100644
index 0000000..fe5188c
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/pom.xml
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions Copyright [year] [name of copyright owner]".
+
+ Copyright 2014-2016 ForgeRock AS.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.forgerock.opendj</groupId>
+ <artifactId>opendj-parent</artifactId>
+ <version>4.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>opendj-openidm-account-change-notification-handler</artifactId>
+ <name>OpenDJ account change notification handler for OpenIDM</name>
+ <description>
+ An OpenDJ Server notification handler that notifies account changes to OpenIDM through REST.
+ </description>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.forgerock.opendj</groupId>
+ <artifactId>opendj-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.opendj</groupId>
+ <artifactId>opendj-config</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.commons</groupId>
+ <artifactId>i18n-slf4j</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.opendj</groupId>
+ <artifactId>opendj-server-legacy</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.commons</groupId>
+ <artifactId>json-crypto-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.http</groupId>
+ <artifactId>chf-http-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.forgerock.http</groupId>
+ <artifactId>chf-client-apache-async</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.forgerock.commons</groupId>
+ <artifactId>i18n-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>generate-messages</goal>
+ </goals>
+ <configuration>
+ <resourceDirectory>${basedir}/src/main/resources</resourceDirectory>
+ <messageFiles>
+ <messageFile>org/forgerock/openidm/accountchange/openidm-account-status-notification-handler.properties</messageFile>
+ </messageFiles>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.forgerock.opendj</groupId>
+ <artifactId>opendj-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-config</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>generate-config</goal>
+ </goals>
+ <configuration>
+ <packageName>org.forgerock.openidm.accountchange</packageName>
+ <isExtension>true</isExtension>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- Retrieve the SCM revision number and store it into the ${buildRevision}
+ property and retrieve the build timestamp and store it into the ${buildDateTime}
+ property -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>buildnumber-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <createDependencyReducedPom>false</createDependencyReducedPom>
+ <transformers>
+ <transformer
+ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <manifestEntries>
+ <Extension-Name>OpenidmAccountStatusNotificationHandler</Extension-Name>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Revision-Number>${buildRevision}</Revision-Number>
+ </manifestEntries>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+ <descriptor>src/main/assembly/descriptor.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-project-info-reports-plugin</artifactId>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>dependencies</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </reporting>
+
+</project>
diff --git a/opendj-openidm-account-change-notification-handler/src/main/assembly/config/openidm-accountchange-plugin-sample-config b/opendj-openidm-account-change-notification-handler/src/main/assembly/config/openidm-accountchange-plugin-sample-config
new file mode 100644
index 0000000..e400f20
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/assembly/config/openidm-accountchange-plugin-sample-config
@@ -0,0 +1,22 @@
+dn: cn=OpenIDM Notification Handler,cn=Account Status Notification Handlers,cn=config
+objectClass: top
+objectClass: ds-cfg-account-status-notification-handler
+objectClass: ds-cfg-openidm-account-status-notification-handler
+cn: OpenIDM Notification Handler
+ds-cfg-java-class: org.forgerock.openidm.accountchange.OpenidmAccountStatusNotificationHandler
+ds-cfg-enabled: true
+ds-cfg-attribute: password
+ds-cfg-query-id: for-userName
+ds-cfg-attribute-type: entryUUID
+ds-cfg-attribute-type: uid
+ds-cfg-log-file: logs/pwsync
+ds-cfg-update-interval: 0 seconds
+ds-cfg-openidm-url: https://localhost:8444/openidm/managed/user
+ds-cfg-private-key-alias: openidm-localhost
+ds-cfg-certificate-subject-dn: CN=localhost, O=OpenIDM Self-Signed Certificate, OU=None, L=None, ST=None, C=None
+ds-cfg-trust-manager-provider: cn=JKS,cn=Trust Manager Providers,cn=config
+ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config
+ds-cfg-ssl-cert-nickname: server-cert
+#ds-cfg-openidm-compat-mode: V3
+#ds-cfg-openidm-username: openidm-admin
+#ds-cfg-openidm-password: openidm-admin
diff --git a/opendj-openidm-account-change-notification-handler/src/main/assembly/config/schema/90-openidm-accountchange-plugin.ldif b/opendj-openidm-account-change-notification-handler/src/main/assembly/config/schema/90-openidm-accountchange-plugin.ldif
new file mode 100644
index 0000000..f2ddd06
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/assembly/config/schema/90-openidm-accountchange-plugin.ldif
@@ -0,0 +1,79 @@
+# The contents of this file are subject to the terms of the Common Development and
+# Distribution License (the License). You may not use this file except in compliance with the
+# License.
+#
+# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+# specific language governing permission and limitations under the License.
+#
+# When distributing Covered Software, include this CDDL Header Notice in each file and include
+# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+# Header, with the fields enclosed by brackets [] replaced by your own identifying
+# information: "Portions Copyright [year] [name of copyright owner]".
+#
+# Copyright 2011-2016 ForgeRock AS.
+
+dn: cn=schema
+objectClass: top
+objectClass: ldapSubentry
+objectClass: subschema
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.162
+ NAME 'ds-cfg-openidm-compat-mode'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.163
+ NAME 'ds-cfg-openidm-url'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.164
+ NAME 'ds-cfg-query-id'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.165
+ NAME 'ds-cfg-openidm-username'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.166
+ NAME 'ds-cfg-openidm-password'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.167
+ NAME 'ds-cfg-certificate-subject-dn'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+attributeTypes: ( 1.3.6.1.4.1.36733.2.1.1.168
+ NAME 'ds-cfg-private-key-alias'
+ EQUALITY caseIgnoreMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
+ X-ORIGIN 'OpenDJ Directory Server' )
+# This OID should be incremented with each non-compatible schema change
+objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.35
+ NAME 'ds-cfg-openidm-account-status-notification-handler'
+ SUP ds-cfg-account-status-notification-handler STRUCTURAL
+ MUST ( ds-cfg-log-file $
+ ds-cfg-update-interval $
+ ds-cfg-openidm-url $
+ ds-cfg-attribute $
+ ds-cfg-query-id $
+ ds-cfg-certificate-subject-dn $
+ ds-cfg-trust-manager-provider $
+ ds-cfg-private-key-alias )
+ MAY ( ds-cfg-openidm-compat-mode $
+ ds-cfg-attribute-type $
+ ds-cfg-openidm-username $
+ ds-cfg-openidm-password $
+ ds-cfg-key-manager-provider $
+ ds-cfg-ssl-cert-nickname)
+ X-ORIGIN 'OpenDJ Directory Server' )
\ No newline at end of file
diff --git a/opendj-openidm-account-change-notification-handler/src/main/assembly/descriptor.xml b/opendj-openidm-account-change-notification-handler/src/main/assembly/descriptor.xml
new file mode 100644
index 0000000..9e01c3b
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/assembly/descriptor.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<!--
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions Copyright [year] [name of copyright owner]".
+
+ Copyright 2014-2016 ForgeRock AS.
+ -->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
+ http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+ <id>openidm-account-change-plugin</id>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory></outputDirectory>
+ <directoryMode>755</directoryMode>
+ <fileMode>644</fileMode>
+ <includes>
+ <include>README</include>
+ <include>LICENSE</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/legal-notices</directory>
+ <outputDirectory>legal-notices/${artifactId}</outputDirectory>
+ <directoryMode>0755</directoryMode>
+ <fileMode>0644</fileMode>
+ </fileSet>
+ <fileSet>
+ <directory>${project.basedir}/../legal-notices</directory>
+ <outputDirectory>legal-notices/${artifactId}</outputDirectory>
+ <directoryMode>0755</directoryMode>
+ <fileMode>0644</fileMode>
+ <includes>
+ <include>CDDLv1_0.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>src/main/assembly/config</directory>
+ <outputDirectory>config</outputDirectory>
+ <directoryMode>0755</directoryMode>
+ <fileMode>0755</fileMode>
+ <lineEnding>unix</lineEnding>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}</directory>
+ <outputDirectory>lib/extensions</outputDirectory>
+ <includes>
+ <include>opendj-openidm*.jar</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandler.java b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandler.java
new file mode 100644
index 0000000..2e7f165
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandler.java
@@ -0,0 +1,755 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2013-2016 ForgeRock AS.
+ */
+package org.forgerock.openidm.accountchange;
+
+import static org.opends.server.types.AccountStatusNotificationType.PASSWORD_RESET;
+import static org.opends.server.types.AccountStatusNotificationType.PASSWORD_CHANGED;
+import static org.forgerock.openidm.accountchange.OpenidmAccountStatusNotificationHandlerMessages.*;
+import static org.opends.server.types.AccountStatusNotificationProperty.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+import org.forgerock.http.Client;
+import org.forgerock.http.HttpApplicationException;
+import org.forgerock.http.handler.HttpClientHandler;
+import org.forgerock.http.protocol.Form;
+import org.forgerock.http.protocol.Headers;
+import org.forgerock.http.protocol.Request;
+import org.forgerock.http.protocol.Response;
+import org.forgerock.http.protocol.Status;
+import org.forgerock.http.spi.Loader;
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.json.JsonPointer;
+import org.forgerock.json.JsonValue;
+import org.forgerock.json.crypto.JsonCrypto;
+import org.forgerock.json.crypto.JsonCryptoException;
+import org.forgerock.json.crypto.JsonEncryptor;
+import org.forgerock.json.crypto.simple.SimpleEncryptor;
+import org.forgerock.opendj.config.server.ConfigChangeResult;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.config.server.ConfigurationChangeListener;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.KeyManagers;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.schema.AttributeType;
+import org.forgerock.opendj.server.config.server.AccountStatusNotificationHandlerCfg;
+import org.forgerock.openidm.accountchange.meta.OpenidmAccountStatusNotificationHandlerCfgDefn.OpenidmCompatMode;
+import org.forgerock.openidm.accountchange.server.OpenidmAccountStatusNotificationHandlerCfg;
+import org.forgerock.util.Function;
+import org.forgerock.util.Options;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+import org.forgerock.util.promise.ResultHandler;
+import org.opends.server.api.AccountStatusNotificationHandler;
+import org.opends.server.api.DirectoryThread;
+import org.opends.server.api.KeyManagerProvider;
+import org.opends.server.api.ServerShutdownListener;
+import org.opends.server.api.TrustManagerProvider;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.AccountStatusNotification;
+import org.opends.server.types.AccountStatusNotificationType;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
+import org.opends.server.types.InitializationException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * An account status notification handler that listens to password reset and password change events
+ * in order to propagate them to OpenIDM.
+ * <p>
+ * The following information is retained for a change
+ * <ul>
+ * <li>the entry DN</li>
+ * <li>the encrypted password</li>
+ * <li>the kind of change (PASSWORD_CHANGE, PASSWORD_RESET)</li>
+ * <li>optionally, the values of some attributes (for any attribute listed in the "attribute-type" parameter
+ * in the config)</li>
+ * </ul>
+ * <p>
+ * There are two ways the changes can be handled, depending on the 'interval' parameter in the configuration:
+ * <ul>
+ * <li>If interval is set to zero, then the change is sent immediately to OpenIDM using a HTTP POST request</li>
+ * <li>If interval is strictly superior to zero, then the change is stored locally (currently in a JE database).
+ * At each interval period of time, the changes which are stored locally are read and sent to OpenIDM using
+ * a HTTP POST request></li>
+ * </ul>
+ * <p>
+ * The communication to OpenIDM can be done in one of three ways:
+ * <ul>
+ * <li>Using HTTP : authentication to OpenIDM is done using BASIC Auth, using the openidm-username and
+ * opendidm-password parameter values from the configuration</li>
+ * <li>Using HTTPS without SSL client authentication : authentication to OpenIDM is done using BASIC Auth, using the
+ * openidm-username and opendidm-password parameter values from the configuration</li>
+ * <li>Using HTTPS with SSL client authentication : ssl-cert-nickname parameter value from the configuration
+ * is used to retrieve the appropriate client certificate from the provided key manager</li>
+ * </ul>
+ */
+public class OpenidmAccountStatusNotificationHandler
+ extends AccountStatusNotificationHandler<OpenidmAccountStatusNotificationHandlerCfg>
+ implements ConfigurationChangeListener<OpenidmAccountStatusNotificationHandlerCfg>, ServerShutdownListener {
+
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+ private static final String THREADNAME = "OpenIDM AccountStatus Notification Handler Thread";
+ /** Cipher used for the JSON encryptor. */
+ private static final String ASYMMETRIC_CIPHER = "RSA/ECB/OAEPWithSHA1AndMGF1Padding";
+
+ private static final byte PWD_CHANGED = 1;
+ private static final byte PWD_RESET = 2;
+ /**
+ * The name of the logfile that the update thread uses to process change records. Defaults to "logs/pwsync", but can
+ * be changed in the configuration.
+ */
+ private String logFileName;
+ private File logFile;
+ /** The hostname of the server. */
+ private String hostname;
+ private OpenidmAccountStatusNotificationHandlerCfg currentConfig;
+ /**
+ * The update interval the background thread uses. If it is 0, then there is no background thread and
+ * the changes are processed in foreground.
+ */
+ private long interval;
+ /** The flag used by the background thread to check if it should exit. */
+ private boolean stopRequested;
+ private Thread backgroundThread;
+ /** Queue used to store changes when update interval is not equal to zero. */
+ private PersistedQueue queue;
+ /** Used to encrypt JSON values. */
+ private JsonEncryptor encryptor;
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private HttpClientHandler httpClientHandler;
+ private Client client;
+ private URI openidmURI;
+
+ /** OpenIDM compatibility mode. */
+ private OpenidmCompatMode compatMode;
+
+ /** A service loader using the class loader that loaded the plugin. */
+ private static Loader serviceLoader = new Loader() {
+ @Override
+ public <S> S load(final Class<S> service, final Options options) {
+ final ServiceLoader<S> loader = ServiceLoader.load(service,
+ OpenidmAccountStatusNotificationHandler.class.getClassLoader());
+ final Iterator<S> i = loader.iterator();
+ return i.hasNext() ? i.next() : null;
+ }
+ };
+
+ @Override
+ public void initializeStatusNotificationHandler(OpenidmAccountStatusNotificationHandlerCfg configuration)
+ throws ConfigException, InitializationException {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Start initialization of OpenIDM status notification handler");
+ }
+ currentConfig = configuration;
+ currentConfig.addOpenidmChangeListener(this);
+
+ // Fetch the local host name for the client host identification.
+ try {
+ hostname = java.net.InetAddress.getLocalHost().getCanonicalHostName();
+ } catch (UnknownHostException ex) {
+ hostname = "UnknownHost";
+ }
+
+ // Read configuration, check and initialize things here.
+ logFileName = configuration.getLogFile();
+ initializeLogFile(logFileName);
+ try {
+ initializeOpenIDMClient(configuration);
+ } catch (DirectoryException ex) {
+ throw new InitializationException(ex.getMessageObject(), ex);
+ }
+
+ // Update interval is applied only when server is restarted.
+ interval = configuration.getUpdateInterval();
+
+ // There are two possible ways to process the password changes
+ // 1. if interval is zero: send changes immediately to OpenIDM
+ // 2. if interval is strictly positive: persist changes locally and send them asynchronously to OpenIDM at
+ // given interval
+ if (interval > 0) {
+ queue = new PersistedQueue(getFileForPath(currentConfig.getLogFile()), "OpenIDMSyncQueue", 10);
+ initializeBackGroundProcessing();
+ }
+ if (logger.isTraceEnabled()) {
+ logger.trace("Successfully finished initialization of OpenIDM status notification handler, "
+ + "using update interval: %s ms", getInterval());
+ }
+ }
+
+ @Override
+ public boolean isConfigurationAcceptable(AccountStatusNotificationHandlerCfg configuration,
+ List<LocalizableMessage> unacceptableReasons) {
+ OpenidmAccountStatusNotificationHandlerCfg config = (OpenidmAccountStatusNotificationHandlerCfg) configuration;
+ return isConfigurationChangeAcceptable(config, unacceptableReasons);
+ }
+
+ @Override
+ public boolean isConfigurationChangeAcceptable(OpenidmAccountStatusNotificationHandlerCfg configuration,
+ List<LocalizableMessage> unacceptableReasons) {
+ try {
+ // ensure URI is valid
+ getOpenIDMURI(configuration);
+
+ // There are two possible ways to authenticate to OpenIDM
+ // 1. Basic authentication : use openidm-user and openidm-password
+ // 2. SSL client authentication : use ssl-cert-nickname (to retrieve the correct certificate in key manager)
+ if (configuration.getSSLCertNickname() == null
+ && (configuration.getOpenidmUsername() == null || configuration.getOpenidmPassword() == null)) {
+ unacceptableReasons.add(ERR_OPENIDM_PWSYNC_INVALID_AUTHENTICATION_CONFIG.get());
+ return false;
+ }
+ if (configuration.getSSLCertNickname() != null) {
+ String keyManagerProv = configuration.getKeyManagerProvider();
+ if (keyManagerProv == null || keyManagerProv.isEmpty()) {
+ unacceptableReasons.add(ERR_OPENIDM_PWSYNC_NO_KEYMANAGER_PROVIDER.get());
+ return false;
+ }
+ }
+ } catch (ConfigException ex) {
+ unacceptableReasons.add(ex.getMessageObject());
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public ConfigChangeResult applyConfigurationChange(OpenidmAccountStatusNotificationHandlerCfg configuration) {
+ // User is not allowed to change the logfile name, append a message that the
+ // server needs restarting for change to take effect.
+ ConfigChangeResult configChangeResult = new ConfigChangeResult();
+ String newLogFileName = configuration.getLogFile();
+ if (!logFileName.equals(newLogFileName)) {
+ configChangeResult.setAdminActionRequired(true);
+ configChangeResult.addMessage(INFO_OPENIDM_PWSYNC_LOGFILE_CHANGE_REQUIRES_RESTART.get(logFileName,
+ newLogFileName));
+ }
+
+ if ((currentConfig.getUpdateInterval() == 0) != (configuration.getUpdateInterval() == 0)) {
+ configChangeResult.setAdminActionRequired(true);
+ configChangeResult
+ .addMessage(INFO_OPENIDM_PWSYNC_UPDATE_INTERVAL_CHANGE_REQUIRES_RESTART.get(
+ Long.toString(currentConfig.getUpdateInterval()),
+ Long.toString(configuration.getUpdateInterval())));
+ } else {
+ interval = configuration.getUpdateInterval();
+ }
+ currentConfig = configuration;
+
+ try {
+ close(httpClientHandler);
+ initializeOpenIDMClient(configuration);
+ configChangeResult.setResultCode(ResultCode.SUCCESS);
+ } catch (DirectoryException | ConfigException | InitializationException ex) {
+ configChangeResult.setResultCode(ResultCode.UNDEFINED);
+ configChangeResult.setAdminActionRequired(true);
+ configChangeResult.addMessage(ex.getMessageObject());
+ }
+
+ return configChangeResult;
+ }
+
+ private void initializeOpenIDMClient(OpenidmAccountStatusNotificationHandlerCfg configuration)
+ throws DirectoryException, ConfigException, InitializationException {
+
+ String certNickname = configuration.getSSLCertNickname();
+ X509KeyManager x509KeyManager = (certNickname != null) ? getKeyManager(configuration) : null;
+ TrustManager[] trustMgrs = getTrustManagers(configuration);
+ X509Certificate serverCert = getServerCertificate(trustMgrs, configuration);
+
+ encryptor =
+ new SimpleEncryptor(ASYMMETRIC_CIPHER, serverCert.getPublicKey(), configuration.getPrivateKeyAlias());
+ initializeHttpClient(configuration, x509KeyManager, trustMgrs);
+ }
+
+ private void initializeHttpClient(OpenidmAccountStatusNotificationHandlerCfg configuration,
+ X509KeyManager keyManager, TrustManager[] trustManagers) throws ConfigException, InitializationException {
+ compatMode = configuration.getOpenidmCompatMode();
+ openidmURI = getOpenIDMURI(configuration);
+ Options options = Options.defaultOptions();
+ options.set(HttpClientHandler.OPTION_LOADER, serviceLoader);
+ options.set(HttpClientHandler.OPTION_MAX_CONNECTIONS, 16);
+ boolean isHTTPS = "https".equalsIgnoreCase(openidmURI.getScheme());
+ if (isHTTPS) {
+ options.set(HttpClientHandler.OPTION_KEY_MANAGERS, new KeyManager[] { keyManager });
+ options.set(HttpClientHandler.OPTION_TRUST_MANAGERS, trustManagers);
+ }
+ try {
+ httpClientHandler = new HttpClientHandler(options);
+ } catch (HttpApplicationException e) {
+ logger.traceException(e, "Error when creating HTTP client handler");
+ throw new InitializationException(ERR_OPENIDM_PWSYNC_INITIALIZATIONEXCEPTION.get(e.getMessage()));
+ }
+ client = new Client(httpClientHandler);
+ }
+
+ private void initializeLogFile(String logFileName) throws ConfigException {
+ this.logFileName = logFileName;
+ this.logFile = getFileForPath(logFileName);
+
+ if (!logFile.exists()) {
+ if (!logFile.mkdirs()) {
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_LOGFILE_UNABLE_TO_CREATE_DIRECTORY.get(logFileName));
+ }
+ } else if (!logFile.isDirectory()) {
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_LOGFILE_ALREADY_EXISTS.get(logFileName));
+ }
+ }
+
+ private X509Certificate getServerCertificate(TrustManager[] trustMgrs,
+ OpenidmAccountStatusNotificationHandlerCfg configuration) throws ConfigException {
+
+ X509TrustManager trustMgr = (X509TrustManager) trustMgrs[0];
+ String serverCertSubject = configuration.getCertificateSubjectDN().toString();
+ for (X509Certificate cert : trustMgr.getAcceptedIssuers()) {
+ String subjectX500Principal = cert.getSubjectX500Principal().getName(X500Principal.CANONICAL);
+ if (serverCertSubject.equalsIgnoreCase(subjectX500Principal)) {
+ return cert;
+ }
+ }
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_INVALID_SERVERKEYALIAS.get(serverCertSubject));
+ }
+
+ private TrustManager[] getTrustManagers(OpenidmAccountStatusNotificationHandlerCfg configuration)
+ throws DirectoryException {
+ DN trustMgrDN = configuration.getTrustManagerProviderDN();
+ TrustManagerProvider<?> trustManagerProvider = DirectoryServer.getTrustManagerProvider(trustMgrDN);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Trust Manager: %s, Server certificate subject: %s", trustMgrDN.toString(),
+ configuration.getCertificateSubjectDN().toString());
+ }
+ return trustManagerProvider.getTrustManagers();
+ }
+
+ private X509KeyManager getKeyManager(OpenidmAccountStatusNotificationHandlerCfg configuration)
+ throws DirectoryException, ConfigException {
+ DN keyMgrDN = configuration.getKeyManagerProviderDN();
+ if (keyMgrDN == null) {
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_NO_KEYMANAGER_PROVIDER.get());
+ }
+ KeyManagerProvider<?> keyManagerProvider = DirectoryServer.getKeyManagerProvider(keyMgrDN);
+ KeyManager[] keyManagers = keyManagerProvider.getKeyManagers();
+ X509KeyManager x509KeyManager = (X509KeyManager) keyManagers[0];
+
+ // Client certificate nickname must be present in the keystore to ensure client certificate
+ // will be retrieved.
+ String certNickname = configuration.getSSLCertNickname();
+ if (x509KeyManager.getPrivateKey(certNickname) == null) {
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_INVALID_CLIENT_CERT_NICKNAME.get(certNickname));
+ }
+ if (logger.isTraceEnabled()) {
+ logger.trace("Key Manager: %s, Client certificate nickname: %s", keyMgrDN, certNickname);
+ }
+ return KeyManagers.useSingleCertificate(certNickname, x509KeyManager);
+ }
+
+ /**
+ * Retrieves the URI of OpenIDM service.
+ * <p/>
+ * Example: https://localhost:8181/openidm/managed/user
+ *
+ * @param configuration
+ * Plugin configuration.
+ * @return URI corresponding to the OpenIDM service address.
+ * @throws ConfigException
+ * if the configuration value has invalid URL syntax.
+ */
+ private URI getOpenIDMURI(OpenidmAccountStatusNotificationHandlerCfg configuration) throws ConfigException {
+ try {
+ return new URI(configuration.getOpenidmUrl());
+ } catch (URISyntaxException ex) {
+ logger.traceException(ex);
+ throw new ConfigException(ERR_OPENIDM_PWSYNC_MALFORMEDURLEXCEPTION.get(configuration.getOpenidmUrl(),
+ ex.getMessage()), ex);
+ }
+ }
+
+ @Override
+ public void handleStatusNotification(AccountStatusNotification notification) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Received notification for user: " + notification.getUserDN());
+ }
+ OpenidmAccountStatusNotificationHandlerCfg config = currentConfig;
+ HashMap<String, List<String>> returnedData = new HashMap<String, List<String>>();
+
+ String userDN = String.valueOf(notification.getUserDN());
+ Entry userEntry = notification.getUserEntry();
+
+ Set<AttributeType> notificationAttrs = config.getAttributeType();
+ for (AttributeType t : notificationAttrs) {
+ List<Attribute> attrList = userEntry.getAttribute(t);
+ if (attrList != null) {
+ for (Attribute a : attrList) {
+ ArrayList<String> attrVals = new ArrayList<String>();
+ String attrName = a.getAttributeDescription().getAttributeType().getNameOrOID();
+ for (ByteString v : a) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Adding end user attribute value " + v + " from attr " + attrName
+ + "to notification");
+ }
+ attrVals.add(v.toString());
+ }
+ returnedData.put(attrName, attrVals);
+ }
+ }
+ }
+
+ AccountStatusNotificationType notifType = notification.getNotificationType();
+ if (PASSWORD_CHANGED != notifType && PASSWORD_RESET != notifType) {
+ return;
+ }
+ List<String> newPasswords = notification.getNotificationProperties().get(NEW_PASSWORD);
+ processOpenIDMNotification(notifType == PASSWORD_CHANGED ? PWD_CHANGED : PWD_RESET, userDN, newPasswords,
+ returnedData);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Finished to process the notification to IDM for user: " + notification.getUserDN());
+ }
+ }
+
+ /**
+ * Returns the patch value for provided passwords as a map of fields.
+ *
+ * @param newPasswords
+ * @return the patch as a map of fields
+ * @throws JsonCryptoException
+ * if encryption fails
+ */
+ private Map<String, Object> buildPatchForPasswords(final List<String> newPasswords) throws JsonCryptoException {
+ final Map<String, Object> patchFields = new HashMap<String, Object>();
+ JsonValue crypto = new JsonCrypto(encryptor.getType(), encryptor.encrypt(new JsonValue(newPasswords.get(0))))
+ .toJsonValue();
+
+ switch (compatMode) {
+ case V2:
+ // { "replace": "/password", "value": {"$crypto" :{}} }
+ patchFields.put("replace", new JsonPointer(currentConfig.getPasswordAttribute()).toString());
+ patchFields.put("value", crypto.asMap());
+ break;
+ case V3:
+ // { "operation": "replace", "field": "/password", "value": {"$crypto" :{}} }
+ patchFields.put("operation", "replace");
+ patchFields.put("field", new JsonPointer(currentConfig.getPasswordAttribute()).toString());
+ patchFields.put("value", crypto.asMap());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown compatibility mode: " + compatMode);
+ }
+ return patchFields;
+ }
+
+ /**
+ * Processes a password change notification and sends it to OpenIDM.
+ *
+ * @param passwordEvent
+ * A byte indicating if it's a change or reset.
+ * @param userDN
+ * The user distinguished name as a string.
+ * @param newPasswords
+ * the list of new passwords (there may be more than 1).
+ * @param returnedData
+ * the additional attributes and values of the user entry.
+ */
+ private void processOpenIDMNotification(byte passwordEvent, String userDN, List<String> newPasswords,
+ Map<String, List<String>> returnedData) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Process notification: user %s 's password %s. Additional data: %s", userDN,
+ (passwordEvent == PWD_CHANGED ? "changed" : "reset"), returnedData);
+ }
+
+ try {
+ String paramPrefix = compatMode == OpenidmCompatMode.V2 ? "_" : "";
+ Map<String, String> queryParameters =
+ buildQueryParameters(paramPrefix, userDN, passwordEvent, returnedData);
+ Map<String, Object> passwordsPatch = buildPatchForPasswords(newPasswords);
+
+ if (interval > 0) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Pushing modification to local storage for user: %s", userDN);
+ }
+ Map<String, Object> request = new HashMap<String, Object>(2);
+ request.put("queryParameter", queryParameters);
+ request.put("patch", passwordsPatch);
+ try {
+ StringWriter writer = new StringWriter();
+ mapper.writeValue(writer, request);
+ queue.push(userDN, writer.toString());
+ } catch (Exception ex) {
+ logger.traceException(ex, "Error when pushing modification to queue");
+ }
+ } else {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Posting REST request to IDM for user: %s", userDN);
+ }
+ postRequestToIDM(queryParameters, passwordsPatch);
+ }
+ } catch (Exception ex) {
+ logger.traceException(ex, "Error when processing modification for user: %s", userDN);
+ }
+
+ }
+
+ private Map<String, String> buildQueryParameters(String paramPrefix, String userDN, byte passwordEvent,
+ Map<String, List<String>> returnedData) {
+ Map<String, String> queryParameter = new HashMap<>(returnedData.size());
+ queryParameter.put(paramPrefix + "passwordEvent", Byte.toString(passwordEvent));
+ queryParameter.put(paramPrefix + "resourceHostname", hostname);
+ queryParameter.put(paramPrefix + "messageTimestamp", Long.toString(System.currentTimeMillis()));
+ queryParameter.put(paramPrefix + "resourceAccountDN", userDN);
+ for (Map.Entry<String, List<String>> e : returnedData.entrySet()) {
+ if (e.getValue().size() == 1) {
+ queryParameter.put(e.getKey(), e.getValue().get(0));
+ } else if (e.getValue().size() > 1) {
+ StringBuilder listString = new StringBuilder();
+ for (String s : e.getValue()) {
+ listString.append(s).append("\t");
+ }
+ queryParameter.put(e.getKey(), listString.toString());
+ }
+ }
+ return queryParameter;
+ }
+
+ /** Returns {@code true} if request is successful, {@code false} otherwise. */
+ @SuppressWarnings("resource")
+ private Promise<Boolean, NeverThrowsException> postRequestToIDM(Map<String, String> queryParameter,
+ Map<String, Object> passwordsPatch) {
+
+ final Request request = buildHttpRequest(queryParameter, passwordsPatch);
+
+ if (logger.isTraceEnabled()) {
+ try {
+ logger.trace("Posting to IDM url=[%s], query params=[%s], json patch=[%s]", request.getUri(),
+ request.getForm(), request.getEntity().getString());
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ return client.send(request).then(new Function<Response, Boolean, NeverThrowsException>() {
+ @Override
+ public Boolean apply(Response response) {
+ try {
+ Status status = response.getStatus();
+ if (status.isSuccessful()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Success when posting to IDM. Message received: %s", response.getEntity());
+ }
+ return true;
+ }
+ if (logger.isTraceEnabled()) {
+ final String message;
+ if (status.equals(Status.UNAUTHORIZED)) {
+ message = "Access non authorized by the server, check your credentials";
+ } else if (status.equals(Status.NOT_FOUND)) {
+ message = "HTTP response: object not found";
+ } else if (status.equals(Status.CONFLICT)) {
+ message = "HTTP response: conflict (matches multiple objects or patch failure)";
+ } else {
+ message = "Unexpected status";
+ }
+ Exception cause = response.getCause();
+ logger.trace("Failure when posting to IDM. Status: %s. Message: %s. Cause: %s", status,
+ message, cause != null ? cause : "N/A");
+ }
+ return false;
+ } finally {
+ request.close();
+ response.close();
+ }
+ }
+ });
+ }
+
+ private Request buildHttpRequest(Map<String, String> queryParameter, Map<String, Object> passwordsPatch) {
+ Request request = new Request();
+ request.setMethod("POST");
+ request.setUri(openidmURI);
+ Headers headers = request.getHeaders();
+ headers.add("X-Requested-With", "OpenDJPlugin");
+ if (currentConfig.getOpenidmUsername() != null && currentConfig.getOpenidmPassword() != null) {
+ headers.add("X-OpenIDM-Username", currentConfig.getOpenidmUsername());
+ headers.add("X-OpenIDM-Password", currentConfig.getOpenidmPassword());
+ }
+ final Form form = new Form();
+ form.add("_action", "patch");
+ form.add("_queryId", currentConfig.getQueryId());
+ for (Map.Entry<String, String> e : queryParameter.entrySet()) {
+ form.add(e.getKey(), e.getValue());
+ }
+ form.appendRequestQuery(request);
+
+ List<Object> finalPatch = new ArrayList<Object>(1);
+ finalPatch.add(passwordsPatch);
+ request.getEntity().setJson(finalPatch);
+ return request;
+ }
+
+ /**
+ * Returns the listener name.
+ *
+ * @return The name of the listener.
+ */
+ @Override
+ public String getShutdownListenerName() {
+ return THREADNAME;
+ }
+
+ /**
+ * Processes a server shutdown. If the background thread is running it needs to be interrupted so it can read the
+ * stop request variable and exit.
+ *
+ * @param reason
+ * The reason message for the shutdown.
+ */
+ @Override
+ public void processServerShutdown(LocalizableMessage reason) {
+ stopRequested = true;
+
+ // Wait for back ground thread to terminate
+ while (backgroundThread != null && backgroundThread.isAlive()) {
+ try {
+ // Interrupt if its sleeping
+ backgroundThread.interrupt();
+ backgroundThread.join();
+ } catch (InterruptedException ex) {
+ // Expected.
+ }
+ }
+ DirectoryServer.deregisterShutdownListener(this);
+ queue.close();
+ close(httpClientHandler);
+ backgroundThread = null;
+ }
+
+ /**
+ * Returns the interval time converted to milliseconds.
+ *
+ * @return The interval time for the background thread.
+ */
+ private long getInterval() {
+ return interval * 1000;
+ }
+
+ /**
+ * Starts a background thread to process locally stored changes asynchronously.
+ */
+ private void initializeBackGroundProcessing() {
+ if (backgroundThread == null) {
+ DirectoryServer.registerShutdownListener(this);
+ stopRequested = false;
+ backgroundThread = new BackGroundThread();
+ backgroundThread.start();
+ }
+ }
+
+ /**
+ * Used by the background thread to determine if it should exit.
+ *
+ * @return Returns <code>true</code> if the background thread should exit.
+ */
+ private boolean isShuttingDown() {
+ return stopRequested;
+ }
+
+ /**
+ * The background processing thread.
+ * <p>
+ * Wakes up after sleeping for a configurable interval and sends all changes stored locally to OpenIDM.
+ */
+ private class BackGroundThread extends DirectoryThread {
+
+ BackGroundThread() {
+ super(THREADNAME);
+ }
+
+ /**
+ * Run method for the background thread.
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void run() {
+ while (!isShuttingDown()) {
+ try {
+ long sleepInterval = getInterval();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Background thread - sleeping for " + sleepInterval + " milliseconds");
+ }
+ sleep(sleepInterval);
+ } catch (InterruptedException e) {
+ continue;
+ } catch (Exception e) {
+ logger.traceException(e, "Error when sleeping");
+ }
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("Queue size: " + queue.size() + " items");
+ }
+ if (queue.size() > 0) {
+ String[] request = null;
+ try {
+ while ((request = queue.poll()) != null) {
+ Map<String, Object> item = mapper.readValue(request[1], Map.class);
+ Map<String, String> queryParameter = (Map<String, String>) item.get("queryParameter");
+ Map<String, Object> passwordsPatch = (Map<String, Object>) item.get("patch");
+ final String[] currentRequest = request;
+ postRequestToIDM(queryParameter, passwordsPatch)
+ .thenOnResult(new ResultHandler<Boolean>() {
+ @Override
+ public void handleResult(Boolean isSuccess) {
+ if (!isSuccess) {
+ try {
+ queue.push(currentRequest[0], currentRequest[1]);
+ } catch (IOException ex) {
+ logger.traceException(ex, "Error when pushing back to queue a request");
+ }
+ }
+ }
+ });
+ }
+ } catch (Exception e) {
+ logger.traceException(e, "Error when processing a queue element");
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandlerConfiguration.xml b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandlerConfiguration.xml
new file mode 100644
index 0000000..dd34ac9
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/OpenidmAccountStatusNotificationHandlerConfiguration.xml
@@ -0,0 +1,440 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions Copyright [year] [name of copyright owner]".
+
+ Copyright 2011-2016 ForgeRock AS.
+ ! -->
+<adm:managed-object name="openidm-account-status-notification-handler"
+ plural-name="openidm-account-status-notification-handlers"
+ extends="account-status-notification-handler"
+ package="org.forgerock.openidm.accountchange"
+ parent-package="org.forgerock.opendj.server.config"
+ xmlns:adm="http://opendj.forgerock.org/admin"
+ xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+ <adm:synopsis>
+ The
+ <adm:user-friendly-name />
+ is an account status notification handler that listens to two kind of changes:
+ password change and password reset.
+ The changes are either immediately sent to OpenIDM or first stored locally and sent
+ later to OpenIDM at the provided interval.
+ The communication with OpenIDM is done through HTTP or HTTPS, with optional
+ SSL client authentication.
+ </adm:synopsis>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>
+ ds-cfg-openidm-account-status-notification-handler
+ </ldap:name>
+ <ldap:superior>
+ ds-cfg-account-status-notification-handler
+ </ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:property-override name="java-class" advanced="true">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>org.forgerock.openidm.accountchange.OpenidmAccountStatusNotificationHandler</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+ <adm:property name="update-interval"
+ mandatory="true">
+ <adm:synopsis>
+ Specifies the interval when passwords update notifications are sent.
+ </adm:synopsis>
+ <adm:description>
+ If this value is 0, then updates are sent synchronously.
+ If this value is strictly superior to zero, then updates are first stored locally,
+ then sent asynchronously by a background thread.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>0 seconds</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:duration base-unit="s" allow-unlimited="false" />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-update-interval</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="log-file" mandatory="true">
+ <adm:synopsis>
+ Specifies the log file location where the changed passwords are
+ written when the plug-in cannot contact OpenIDM.
+ </adm:synopsis>
+ <adm:description>
+ The default location is the logs directory of the server
+ instance, using the file name "pwsync".
+ Passwords in this file will be encrypted.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>logs/pwsync</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>.*</adm:regex>
+ <adm:usage>FILE</adm:usage>
+ <adm:synopsis>
+ A path to an existing directory that is readable and writable by the server.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-log-file</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="password-attribute" mandatory="true">
+ <adm:synopsis>
+ Specifies the attribute type used to hold user passwords in JSON returned to OpenIDM.
+ </adm:synopsis>
+ <adm:description>
+ This attribute type must be defined in the managed object schema in OpenIDM,
+ and it must have either the user password or auth password syntax.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>password</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>.*</adm:regex>
+ <adm:usage>STRING</adm:usage>
+ <adm:synopsis>
+ OpenIDM managed object attribute name.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-attribute</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="query-id" mandatory="true">
+ <adm:synopsis>
+ Specifies the query-id for the patch-by-query request.
+ </adm:synopsis>
+ <adm:description>
+ This must match the query ID defined in the managed object service in OpenIDM.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>for-userName</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <adm:regex>.*</adm:regex>
+ <adm:usage>STRING</adm:usage>
+ <adm:synopsis>
+ OpenIDM managed object query ID.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-query-id</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="attribute-type" multi-valued="true">
+ <adm:synopsis>
+ Specifies the attribute types that this plug-in will send along with
+ the password change.
+ </adm:synopsis>
+ <adm:description>
+ Zero or more attribute types can be specified.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:alias>
+ <adm:synopsis>
+ If no attribute types are specified, only the DN and the new
+ password of the user will be synchronized to OpenIDM.
+ </adm:synopsis>
+ </adm:alias>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:attribute-type />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-attribute-type</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="openidm-url" mandatory="true">
+ <adm:synopsis>
+ Specifies the URL to OpenIDM endpoint.
+ </adm:synopsis>
+ <adm:description>
+ The URL can be either HTTP or HTTPS.
+ </adm:description>
+ <adm:syntax>
+ <adm:string>
+ <adm:pattern>
+ <!--adm:regex>(http|https)://(w+:{0,1}w*@)?(S+)(:[0-9]+)?(/|/([w#!:.?+=&%@!-/]))?</adm:regex-->
+ <adm:regex>.*</adm:regex>
+ <adm:usage>URL</adm:usage>
+ <adm:synopsis>
+ OpenIDM sync service URL.
+ </adm:synopsis>
+ </adm:pattern>
+ </adm:string>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-openidm-url
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="openidm-compat-mode">
+ <adm:synopsis>
+ Specifies OpenIDM Compatibility Mode.
+ </adm:synopsis>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>V3</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:enumeration>
+ <adm:value name="V3">
+ <adm:synopsis>
+ Use version 3 OpenIDM Compatibility Mode.
+ </adm:synopsis>
+ </adm:value>
+ <adm:value name="V2">
+ <adm:synopsis>
+ Use version 2 OpenIDM Compatibility Mode.
+ </adm:synopsis>
+ </adm:value>
+ </adm:enumeration>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-openidm-compat-mode
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="ssl-cert-nickname" mandatory="false">
+ <adm:synopsis>
+ Specifies the SSL certificate nickname, which is the alias under which is stored
+ the client certificate in the keystore. It must be provided to
+ activate SSL client authentication when requesting OpenIDM.
+ </adm:synopsis>
+ <adm:description>
+ The SSL certificate nickname is necessary to ensure that the appropriate client certificate
+ is retrieved from the keystore when SSL client authentication is required and
+ multiples certificates are present in the keystore.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:undefined />
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string/>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-ssl-cert-nickname
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="openidm-username" mandatory="false">
+ <adm:synopsis>
+ Specifies the username to use for HTTP Basic Authentication.
+ </adm:synopsis>
+ <adm:description>
+ The username must be provided when client certification is not activated,
+ i.e. when no ssl-cert-nickname is provided.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:undefined />
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string/>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-openidm-username
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="openidm-password" mandatory="false">
+ <adm:synopsis>
+ Specifies the password to use for HTTP Basic Authentication.
+ </adm:synopsis>
+ <adm:description>
+ The password must be provided when client certification is not activated,
+ i.e. when no ssl-cert-nickname is provided.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:undefined />
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string/>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-openidm-password
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="private-key-alias" mandatory="true">
+ <adm:synopsis>
+ Specifies the alias of the private key that should be used by OpenIDM
+ to decrypt the encrypted JSON content of the requests.
+ </adm:synopsis>
+ <adm:description>
+ The encryption of the JSON content sent to OpenIDM requires this alias.
+ </adm:description>
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>openidm-localhost</adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:string/>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>
+ ds-cfg-private-key-alias
+ </ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="certificate-subject-dn" mandatory="true">
+ <adm:synopsis>
+ Specifies the subject DN of the certificate used by OpenIDM.
+ </adm:synopsis>
+ <adm:description>
+ The subject DN is used to retrieve the OpenIDM certificate
+ in the truststore. This certificate's public key is necessary
+ to encrypt the JSON content sent to OpenIDM.
+ </adm:description>
+ <adm:syntax>
+ <adm:dn />
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-certificate-subject-dn</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="key-manager-provider">
+ <adm:synopsis>
+ Specifies the name of the key manager that should be used with
+ this <adm:user-friendly-name />.
+ </adm:synopsis>
+ <adm:description>
+ It must be provided when ssl-cert-nickname is provided, and must
+ contain a certificate corresponding to the nickname.
+ </adm:description>
+ <adm:requires-admin-action>
+ <adm:none>
+ <adm:synopsis>
+ Changes to this property take effect immediately, but
+ only for subsequent attempts to access the key manager
+ provider for associated client connections.
+ </adm:synopsis>
+ </adm:none>
+ </adm:requires-admin-action>
+ <adm:default-behavior>
+ <adm:undefined />
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:aggregation relation-name="key-manager-provider"
+ parent-path="/" managed-object-package="org.forgerock.opendj.server.config">
+ <adm:constraint>
+ <adm:synopsis>
+ The referenced key manager provider must be enabled.
+ </adm:synopsis>
+ <adm:target-is-enabled-condition>
+ <adm:contains property="enabled" value="true" />
+ </adm:target-is-enabled-condition>
+ </adm:constraint>
+ </adm:aggregation>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-key-manager-provider</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+ <adm:property name="trust-manager-provider" mandatory="true">
+ <adm:synopsis>
+ Specifies the name of the trust manager that should be used with
+ the <adm:user-friendly-name />.
+ </adm:synopsis>
+ <adm:description>
+ It must contain the OpenIDM certificate with the subject DN equals
+ to the certificate-subject-dn property.
+ </adm:description>
+ <adm:requires-admin-action>
+ <adm:none>
+ <adm:synopsis>
+ Changes to this property take effect immediately, but
+ only for subsequent attempts to access the trust manager
+ provider for associated client connections.
+ </adm:synopsis>
+ </adm:none>
+ </adm:requires-admin-action>
+ <adm:default-behavior>
+ <adm:undefined />
+ </adm:default-behavior>
+ <adm:syntax>
+ <adm:aggregation relation-name="trust-manager-provider"
+ parent-path="/" managed-object-package="org.forgerock.opendj.server.config">
+ <adm:constraint>
+ <adm:synopsis>
+ The referenced trust manager provider must be enabled.
+ </adm:synopsis>
+ <adm:target-is-enabled-condition>
+ <adm:contains property="enabled" value="true" />
+ </adm:target-is-enabled-condition>
+ </adm:constraint>
+ </adm:aggregation>
+ </adm:syntax>
+ <adm:profile name="ldap">
+ <ldap:attribute>
+ <ldap:name>ds-cfg-trust-manager-provider</ldap:name>
+ </ldap:attribute>
+ </adm:profile>
+ </adm:property>
+</adm:managed-object>
diff --git a/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/Package.xml b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/Package.xml
new file mode 100644
index 0000000..3f520cb
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/Package.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions Copyright [year] [name of copyright owner]".
+
+ Copyright 2011-2016 ForgeRock AS.
+ ! -->
+<adm:package name="org.forgerock.openidm.accountchange"
+ xmlns:adm="http://opendj.forgerock.org/admin"
+ xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+ <adm:synopsis>OpenIDM account change plugin</adm:synopsis>
+</adm:package>
diff --git a/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/PersistedQueue.java b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/PersistedQueue.java
new file mode 100644
index 0000000..72b6ca4
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/PersistedQueue.java
@@ -0,0 +1,181 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2011-2016 ForgeRock AS.
+ */
+package org.forgerock.openidm.accountchange;
+
+import java.io.File;
+import java.io.IOException;
+
+import com.sleepycat.je.Cursor;
+import com.sleepycat.je.Database;
+import com.sleepycat.je.DatabaseConfig;
+import com.sleepycat.je.DatabaseEntry;
+import com.sleepycat.je.Environment;
+import com.sleepycat.je.EnvironmentConfig;
+import com.sleepycat.je.LockMode;
+import com.sleepycat.je.OperationStatus;
+
+import net.jcip.annotations.ThreadSafe;
+
+/**
+ * Fast request queue implementation on top of Berkley DB Java Edition.
+ * The queue uses the canonalised dn for key and store only
+ * the last password change for each dn.
+ * <p/>
+ */
+@ThreadSafe
+public class PersistedQueue {
+
+ /** JE DB environment. */
+ private final Environment dbEnv;
+ /** JE DB instance for the queue. */
+ private final Database queueDatabase;
+ /** Queue cache size - number of element operations it is allowed to loose in case of system crash. */
+ private final int cacheSize;
+ /** This queue name. */
+ private final String queueName;
+ /** Queue operation counter, which is used to sync the queue database to disk periodically. */
+ private int opsCounter;
+
+ /**
+ * Creates instance of persistent queue.
+ *
+ * @param queueEnvPath queue database environment directory path
+ * @param queueName descriptive queue name
+ * @param cacheSize how often to sync the queue to disk
+ */
+ public PersistedQueue(final File queueEnvPath,
+ final String queueName,
+ final int cacheSize) {
+ // Create parent dirs for queue environment directory
+ queueEnvPath.mkdirs();
+
+ // Setup database environment
+ final EnvironmentConfig dbEnvConfig = new EnvironmentConfig();
+ dbEnvConfig.setTransactional(false);
+ dbEnvConfig.setAllowCreate(true);
+ this.dbEnv = new Environment(queueEnvPath,
+ dbEnvConfig);
+
+ // Setup non-transactional deferred-write queue database
+ DatabaseConfig dbConfig = new DatabaseConfig();
+ dbConfig.setTransactional(false);
+ dbConfig.setAllowCreate(true);
+ dbConfig.setDeferredWrite(true);
+ this.queueDatabase = dbEnv.openDatabase(null,
+ queueName,
+ dbConfig);
+ this.queueName = queueName;
+ this.cacheSize = cacheSize;
+ this.opsCounter = 0;
+ }
+
+ /**
+ * Retrieves and returns element from the head of this queue.
+ *
+ * @return element from the head of the queue or null if queue is empty
+ * @throws IOException in case of disk IO failure
+ */
+ public String[] poll() throws IOException {
+ final DatabaseEntry key = new DatabaseEntry();
+ final DatabaseEntry data = new DatabaseEntry();
+ final Cursor cursor = queueDatabase.openCursor(null, null);
+ try {
+ cursor.getFirst(key, data, LockMode.RMW);
+ if (data.getData() == null) {
+ return null;
+ }
+ final String res = new String(data.getData(), "UTF-8");
+ final String dn = new String(key.getData(), "UTF-8");
+ cursor.delete();
+ opsCounter++;
+ if (opsCounter >= cacheSize) {
+ queueDatabase.sync();
+ opsCounter = 0;
+ }
+ return new String[]{dn, res};
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Pushes element to the queue.
+ * <p/>
+ * The entries are sorted in natural order and not in order of time when
+ * they were added.
+ *
+ * @param dn The entry DN
+ * @param element element
+ * @throws IOException in case of disk IO failure
+ */
+ public synchronized void push(final String dn, final String element) throws IOException {
+ Cursor cursor = queueDatabase.openCursor(null, null);
+ try {
+ // Open a cursor using a database handle
+ DatabaseEntry foundChange = new DatabaseEntry();
+ final DatabaseEntry key = new DatabaseEntry(dn.getBytes("UTF-8"));
+ final DatabaseEntry data = new DatabaseEntry(element.getBytes("UTF-8"));
+
+ //Find the data
+ OperationStatus retVal = cursor.getSearchKey(key, foundChange, LockMode.DEFAULT);
+
+ if (OperationStatus.SUCCESS.equals(retVal)) {
+ cursor.putCurrent(data);
+ opsCounter++;
+ } else if (OperationStatus.NOTFOUND.equals(retVal)) {
+ queueDatabase.put(null, key, data);
+ opsCounter++;
+ }
+ if (opsCounter >= cacheSize) {
+ queueDatabase.sync();
+ opsCounter = 0;
+ }
+ } catch (IOException willNeverOccur) {
+ //TODO test this catch
+ willNeverOccur.printStackTrace();
+ throw willNeverOccur;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Returns the size of this queue.
+ *
+ * @return the size of the queue
+ */
+ public long size() {
+ return queueDatabase.count();
+ }
+
+ /**
+ * Returns this queue name.
+ *
+ * @return this queue name
+ */
+ public String getQueueName() {
+ return queueName;
+ }
+
+ /**
+ * Closes this queue and frees up all resources associated to it.
+ */
+ public void close() {
+ queueDatabase.sync();
+ queueDatabase.close();
+ dbEnv.close();
+ }
+}
diff --git a/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/package-info.java b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/package-info.java
new file mode 100644
index 0000000..4d5ba0c
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/java/org/forgerock/openidm/accountchange/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2011-2016 ForgeRock AS.
+ */
+
+/**
+ * OpenIDM password synchronization extension's implementation classes.
+ */
+package org.forgerock.openidm.accountchange;
+
diff --git a/opendj-openidm-account-change-notification-handler/src/main/resources/org/forgerock/openidm/accountchange/openidm-account-status-notification-handler.properties b/opendj-openidm-account-change-notification-handler/src/main/resources/org/forgerock/openidm/accountchange/openidm-account-status-notification-handler.properties
new file mode 100644
index 0000000..57d587d
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/main/resources/org/forgerock/openidm/accountchange/openidm-account-status-notification-handler.properties
@@ -0,0 +1,69 @@
+# The contents of this file are subject to the terms of the Common Development and
+# Distribution License (the License). You may not use this file except in compliance with the
+# License.
+#
+# You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+# specific language governing permission and limitations under the License.
+#
+# When distributing Covered Software, include this CDDL Header Notice in each file and include
+# the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+# Header, with the fields enclosed by brackets [] replaced by your own identifying
+# information: "Portions Copyright [year] [name of copyright owner]".
+#
+# Copyright 2011-2016 ForgeRock AS.
+
+#
+# Format string definitions
+#
+# Keys must be formatted as follows:
+#
+# [SEVERITY]_[DESCRIPTION]_[ORDINAL]
+#
+# where:
+#
+# SEVERITY is one of:
+# [INFO, WARN, ERR, DEBUG, NOTICE]
+#
+# DESCRIPTION is an upper case string providing a hint as to the context of
+# the message in upper case with the underscore ('_') character serving as
+# word separator
+#
+# ORDINAL is an integer unique among other ordinals in this file
+#
+ERR_OPENIDM_PWSYNC_LOGFILE_UNABLE_TO_CREATE_DIRECTORY_1=An error occurred during \
+ OpenIDM Password Sync plugin initialization because log file creation \
+ failed : could not create directory %s.
+INFO_OPENIDM_PWSYNC_LOGFILE_CHANGE_REQUIRES_RESTART_2=The file name that \
+ the OpenIDM Password Sync plugin logs changes has been changed from %s \
+ to %s, but this change will not take effect until the server is restarted
+ERR_OPENIDM_PWSYNC_MALFORMEDURLEXCEPTION_3=An error occurred during \
+ OpenIDM Password Sync plugin initialization because the url '%s' is not \
+ a valid OpenIDM-URL: %s.
+ERR_OPENIDM_PWSYNC_INVALID_CLIENT_CERT_NICKNAME_4=An error occurred during \
+ OpenIDM Password Sync plugin initialization because the ssl-cert-nickname \
+ '%s' is not found in provided keystore.
+ERR_OPENIDM_PWSYNC_INVALID_SERVERKEYALIAS_5=An error occurred during \
+ OpenIDM Password Sync plugin initialization because the \
+ certificate-subject-dn '%s' is not found in provided truststore.
+ERR_OPENIDM_PWSYNC_NO_KEYMANAGER_PROVIDER_6=An error occurred during \
+ OpenIDM Password Sync plugin initialization because a key manager is required \
+ for SSL client authentication, but no key manager was provided.
+ERR_OPENIDM_PWSYNC_INVALID_TRUSTMANAGERPROVIDER_7=An error occurred during \
+ OpenIDM Password Sync plugin initialization because the trust-manager-provider: \
+ '%s' does not exist.
+ERR_OPENIDM_PWSYNC_INITIALIZATIONEXCEPTION_8=An error occurred during \
+ OpenIDM Password Sync plugin initialization: %s.
+INFO_OPENIDM_PWSYNC_UPDATE_INTERVAL_CHANGE_REQUIRES_RESTART_9=The update interval of \
+ the OpenIDM Password Sync plugin executes the background thread has been changed from %s \
+ to %s, but this change will not take effect until the server is restarted
+ERR_OPENIDM_PWSYNC_UNAUTHORISED_REQUEST_10=Unable to send password update request because \
+ Client can not authorise.
+ERR_OPENIDM_PWSYNC_UNEXPECTED_RETURN_STATUS_11=Unable to send password update request because \
+ unexpected error happend. Return status code: %s, Error message: %s
+ERR_OPENIDM_PWSYNC_INVALID_AUTHENTICATION_CONFIG_12=Authentication to OpenIDM requires either \
+ ssl-cert-nickname to be set or openidm-user and openidm-password to be set but neither were set. \
+ For SSL client authentication, ssl-cert-nickname must be provided. For basic authentication, \
+ openidm-user and openidm-password must be provided.
+ERR_OPENIDM_PWSYNC_LOGFILE_ALREADY_EXISTS_14=An error occurred during \
+ OpenIDM Password Sync plugin initialization because log file creation \
+ failed: %s exists and is not a directory.
diff --git a/opendj-openidm-account-change-notification-handler/src/site/xdoc/index.xml.vm b/opendj-openidm-account-change-notification-handler/src/site/xdoc/index.xml.vm
new file mode 100644
index 0000000..bdd3f10
--- /dev/null
+++ b/opendj-openidm-account-change-notification-handler/src/site/xdoc/index.xml.vm
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ The contents of this file are subject to the terms of the Common Development and
+ Distribution License (the License). You may not use this file except in compliance with the
+ License.
+
+ You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ specific language governing permission and limitations under the License.
+
+ When distributing Covered Software, include this CDDL Header Notice in each file and include
+ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ Header, with the fields enclosed by brackets [] replaced by your own identifying
+ information: "Portions Copyright [year] [name of copyright owner]".
+
+ Copyright 2014-2016 ForgeRock AS.
+-->
+<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+ <properties>
+ <title>About ${project.name}</title>
+ <author email="opendj-dev@forgerock.org">${project.organization.name}</author>
+ </properties>
+ <body>
+ <section name="About ${project.name}">
+ <p>
+ ${project.description}
+ </p>
+ </section>
+ <section name="Documentation for ${project.name}">
+ <p>
+ Javadoc for this module can be found <a href="apidocs/index.html">here</a>.
+ </p>
+ </section>
+ <section name="Get ${project.name}">
+ <p>
+ Start developing your applications by obtaining ${project.name}
+ using any of the following methods:
+ </p>
+ <subsection name="Maven">
+ <p>
+ By far the simplest method is to develop your application using Maven
+ and add the following settings to your <b>pom.xml</b>:
+ </p>
+ <source><repositories>
+ <repository>
+ <id>forgerock-staging-repository</id>
+ <name>ForgeRock Release Repository</name>
+ <url>${mavenRepoReleases}</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>forgerock-snapshots-repository</id>
+ <name>ForgeRock Snapshot Repository</name>
+ <url>${mavenRepoSnapshots}</url>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ </repository>
+</repositories>
+
+...
+
+<dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>${project.artifactId}</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+</dependencies></source>
+ </subsection>
+ <subsection name="Download">
+ <p>
+ If you are not using Maven then you will need to download a pre-built
+ binary from the ForgeRock Maven repository, along with any compile
+ time <a href="dependencies.html">dependencies</a>:
+ </p>
+ <ul>
+ <li><a href="${mavenRepoReleases}/org/forgerock/opendj/${project.artifactId}">Stable releases</a></li>
+ <li><a href="${mavenRepoSnapshots}/org/forgerock/opendj/${project.artifactId}/${project.version}">Latest development snapshot</a></li>
+ </ul>
+ </subsection>
+ <subsection name="Build">
+ <p>
+ For the DIY enthusiasts you can build it yourself by checking out the
+ latest code using <a href="source-repository.html">Subversion</a>
+ and building it with Maven 3.
+ </p>
+ </subsection>
+ </section>
+ <section name="Getting started">
+ <p>
+ The following example shows how ${project.name} may be used:
+ </p>
+ <source>TODO</source>
+ </section>
+ </body>
+</document>
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java b/opendj-server-legacy/src/main/java/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java
index c7a55ee..8bda1cd 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/core/AccountStatusNotificationHandlerConfigManager.java
@@ -129,7 +129,7 @@
try
{
// Load the class but don't initialize it.
- loadNotificationHandler(className, configuration, true);
+ loadNotificationHandler(className, configuration, false);
}
catch (InitializationException ie)
{
diff --git a/pom.xml b/pom.xml
index 11b4ce9..f08767e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -228,6 +228,7 @@
<module>opendj-legacy</module>
<module>opendj-server-legacy</module>
<module>opendj-dsml-servlet</module>
+ <module>opendj-openidm-account-change-notification-handler</module>
</modules>
<build>
--
Gitblit v1.10.0