[#462] RFC5805 Lightweight Directory Access Protocol (LDAP) Transactions (#469)
12 files added
10 files modified
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package com.forgerock.opendj.ldap.extensions; |
| | | |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.responses.AbstractExtendedResult; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | /* |
| | | The Aborted Transaction Notice is an Unsolicited Notification message |
| | | where the responseName is 1.3.6.1.1.21.4 and responseValue is present |
| | | and contains a transaction identifier. |
| | | */ |
| | | public class AbortedTransactionExtendedResult extends AbstractExtendedResult<AbortedTransactionExtendedResult> { |
| | | @Override |
| | | public String getOID() { |
| | | return "1.3.6.1.1.21.4"; |
| | | } |
| | | |
| | | private AbortedTransactionExtendedResult(final ResultCode resultCode) { |
| | | super(resultCode); |
| | | } |
| | | |
| | | public static AbortedTransactionExtendedResult newResult(final ResultCode resultCode) { |
| | | Reject.ifNull(resultCode); |
| | | return new AbortedTransactionExtendedResult(resultCode); |
| | | } |
| | | |
| | | private String transactionID = null; |
| | | |
| | | public AbortedTransactionExtendedResult setTransactionID(final String transactionID) { |
| | | this.transactionID = transactionID; |
| | | return this; |
| | | } |
| | | |
| | | public String getTransactionID() { |
| | | return transactionID; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString getValue() { |
| | | final ByteStringBuilder buffer = new ByteStringBuilder(); |
| | | final ASN1Writer writer = ASN1.getWriter(buffer); |
| | | try { |
| | | writer.writeOctetString(transactionID); |
| | | } catch (final IOException ioe) { |
| | | throw new RuntimeException(ioe); |
| | | } |
| | | return buffer.toByteString(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "AbortedTransactionExtendedResult(resultCode=" + |
| | | getResultCode() + |
| | | ", matchedDN=" + |
| | | getMatchedDN() + |
| | | ", diagnosticMessage=" + |
| | | getDiagnosticMessage() + |
| | | ", referrals=" + |
| | | getReferralURIs() + |
| | | ", responseName=" + |
| | | getOID() + |
| | | ", transactionID=" + |
| | | transactionID + |
| | | ", controls=" + |
| | | getControls() + |
| | | ")"; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package com.forgerock.opendj.ldap.extensions; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.ldap.*; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.requests.*; |
| | | import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST; |
| | | import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; |
| | | |
| | | /* |
| | | An End Transaction Request is an LDAPMessage of CHOICE extendedReq |
| | | where the requestName is 1.3.6.1.1.21.3 and the requestValue is |
| | | present and contains a BER-encoded txnEndReq. |
| | | |
| | | txnEndReq ::= SEQUENCE { |
| | | commit BOOLEAN DEFAULT TRUE, |
| | | identifier OCTET STRING } |
| | | |
| | | A commit value of TRUE indicates a request to commit the transaction |
| | | identified by the identifier. A commit value of FALSE indicates a |
| | | request to abort the identified transaction. |
| | | */ |
| | | public class EndTransactionExtendedRequest extends AbstractExtendedRequest<EndTransactionExtendedRequest, EndTransactionExtendedResult> { |
| | | |
| | | public static final String END_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.3"; |
| | | |
| | | @Override |
| | | public String getOID() { |
| | | return END_TRANSACTION_REQUEST_OID; |
| | | } |
| | | |
| | | @Override |
| | | public ExtendedResultDecoder<EndTransactionExtendedResult> getResultDecoder() { |
| | | return RESULT_DECODER; |
| | | } |
| | | |
| | | Boolean commit=true; |
| | | public EndTransactionExtendedRequest setCommit(final Boolean commit) { |
| | | this.commit = commit; |
| | | return this; |
| | | } |
| | | |
| | | String transactionID; |
| | | public EndTransactionExtendedRequest setTransactionID(final String transactionID) { |
| | | Reject.ifNull(transactionID); |
| | | this.transactionID = transactionID; |
| | | return this; |
| | | } |
| | | public boolean isCommit() { |
| | | return commit; |
| | | } |
| | | |
| | | public String getTransactionID() { |
| | | return transactionID; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString getValue() { |
| | | Reject.ifNull(transactionID); |
| | | final ByteStringBuilder buffer = new ByteStringBuilder(); |
| | | final ASN1Writer writer = ASN1.getWriter(buffer); |
| | | try { |
| | | writer.writeStartSequence(); |
| | | if (commit!=null) { |
| | | writer.writeBoolean(commit); |
| | | } |
| | | writer.writeOctetString(transactionID); |
| | | writer.writeEndSequence(); |
| | | } catch (final IOException ioe) { |
| | | throw new RuntimeException(ioe); |
| | | } |
| | | return buffer.toByteString(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return true; |
| | | } |
| | | |
| | | private static final EndTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new EndTransactionExtendedRequest.ResultDecoder(); |
| | | |
| | | private static final class ResultDecoder extends AbstractExtendedResultDecoder<EndTransactionExtendedResult> { |
| | | @Override |
| | | public EndTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) { |
| | | if (!resultCode.isExceptional()) { |
| | | throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue()); |
| | | } |
| | | return EndTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage); |
| | | } |
| | | |
| | | /* |
| | | txnEndRes ::= SEQUENCE { |
| | | messageID MessageID OPTIONAL, |
| | | -- msgid associated with non-success resultCode |
| | | updatesControls SEQUENCE OF updateControls SEQUENCE { |
| | | messageID MessageID, |
| | | -- msgid associated with controls |
| | | controls Controls |
| | | } OPTIONAL |
| | | } |
| | | */ |
| | | @Override |
| | | public EndTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException { |
| | | if (result instanceof EndTransactionExtendedResult) { |
| | | return (EndTransactionExtendedResult) result; |
| | | } |
| | | |
| | | final ResultCode resultCode = result.getResultCode(); |
| | | final EndTransactionExtendedResult newResult = |
| | | EndTransactionExtendedResult.newResult(resultCode) |
| | | .setMatchedDN(result.getMatchedDN()) |
| | | .setDiagnosticMessage(result.getDiagnosticMessage()); |
| | | |
| | | final ByteString responseValue = result.getValue(); |
| | | if (!resultCode.isExceptional() && responseValue == null) { |
| | | throw DecodeException.error(LocalizableMessage.raw("Empty response value")); |
| | | } |
| | | if (responseValue != null) { |
| | | try { |
| | | final ASN1Reader reader = ASN1.getReader(responseValue); |
| | | if (reader.hasNextElement()) { |
| | | reader.readStartSequence(); |
| | | if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_INTEGER_TYPE) { |
| | | newResult.setFailedMessageID(Math.toIntExact(reader.readInteger())); |
| | | } else if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) { |
| | | reader.readStartSequence(); |
| | | while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) { |
| | | reader.readStartSequence(); |
| | | final long messageId = reader.readInteger(); |
| | | final List<Control> controls = new ArrayList<>(); |
| | | reader.readStartSequence(); |
| | | while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_OCTET_STRING_TYPE) { |
| | | final ByteString controlEncoded = reader.readOctetString(); |
| | | //TODO decode Control |
| | | } |
| | | reader.readEndSequence(); |
| | | //newResult.success(messageId, controls.toArray(new Control[]{})); |
| | | reader.readEndSequence(); |
| | | } |
| | | reader.readEndSequence(); |
| | | } |
| | | reader.readEndSequence(); |
| | | } |
| | | } catch (final IOException e) { |
| | | throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e); |
| | | } |
| | | } |
| | | for (final Control control : result.getControls()) { |
| | | newResult.addControl(control); |
| | | } |
| | | return newResult; |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package com.forgerock.opendj.ldap.extensions; |
| | | |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.responses.AbstractExtendedResult; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.TreeMap; |
| | | |
| | | /* |
| | | An End Transaction Response is an LDAPMessage sent in response to a |
| | | End Transaction Request. Its response name is absent. The |
| | | responseValue when present contains a BER-encoded txnEndRes. |
| | | |
| | | txnEndRes ::= SEQUENCE { |
| | | messageID MessageID OPTIONAL, |
| | | -- msgid associated with non-success resultCode |
| | | updatesControls SEQUENCE OF updateControls SEQUENCE { |
| | | messageID MessageID, |
| | | -- msgid associated with controls |
| | | controls Controls |
| | | } OPTIONAL |
| | | } |
| | | -- where MessageID and Controls are as specified in RFC 4511 |
| | | |
| | | The txnEndRes.messageID provides the message id of the update request |
| | | associated with a non-success response. txnEndRes.messageID is |
| | | absent when resultCode of the End Transaction Response is success |
| | | |
| | | The txnEndRes.updatesControls provides a facility for returning |
| | | response controls that normally (i.e., in the absence of |
| | | transactions) would be returned in an update response. The |
| | | updateControls.messageID provides the message id of the update |
| | | request associated with the response controls provided in |
| | | updateControls.controls. |
| | | |
| | | The txnEndRes.updatesControls is absent when there are no update |
| | | response controls to return. |
| | | |
| | | If both txnEndRes.messageID and txnEndRes.updatesControl are absent, |
| | | the responseValue of the End Transaction Response is absent. |
| | | */ |
| | | public class EndTransactionExtendedResult extends AbstractExtendedResult<EndTransactionExtendedResult> { |
| | | @Override |
| | | public String getOID() { |
| | | return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID; |
| | | } |
| | | |
| | | private EndTransactionExtendedResult(final ResultCode resultCode) { |
| | | super(resultCode); |
| | | } |
| | | |
| | | public static EndTransactionExtendedResult newResult(final ResultCode resultCode) { |
| | | Reject.ifNull(resultCode); |
| | | return new EndTransactionExtendedResult(resultCode); |
| | | } |
| | | |
| | | // The message ID for the operation that failed, if applicable. |
| | | Integer failedOpMessageID=null; |
| | | |
| | | public EndTransactionExtendedResult setFailedMessageID(final Integer failedOpMessageID) { |
| | | Reject.ifNull(failedOpMessageID); |
| | | this.failedOpMessageID = failedOpMessageID; |
| | | return this; |
| | | } |
| | | |
| | | // A mapping of the response controls for the operations performed as part of |
| | | // the transaction. |
| | | Map<Integer, List<Control>> opResponseControls= new TreeMap<>(); |
| | | |
| | | public EndTransactionExtendedResult success(Integer messageID, List<Control> responses) { |
| | | Reject.ifNull(messageID); |
| | | Reject.ifNull(responses); |
| | | opResponseControls.put(messageID,responses); |
| | | return this; |
| | | } |
| | | |
| | | /* |
| | | txnEndRes ::= SEQUENCE { |
| | | messageID MessageID OPTIONAL, |
| | | -- msgid associated with non-success resultCode |
| | | updatesControls SEQUENCE OF updateControls SEQUENCE { |
| | | messageID MessageID, |
| | | -- msgid associated with controls |
| | | controls Controls |
| | | } OPTIONAL |
| | | } |
| | | */ |
| | | @Override |
| | | public ByteString getValue() { |
| | | final ByteStringBuilder buffer = new ByteStringBuilder(); |
| | | final ASN1Writer writer = ASN1.getWriter(buffer); |
| | | try { |
| | | if (failedOpMessageID!=null || (opResponseControls!=null && !opResponseControls.isEmpty()) ) { |
| | | writer.writeStartSequence(); |
| | | if (failedOpMessageID != null) { |
| | | writer.writeInteger(failedOpMessageID); |
| | | } |
| | | if (opResponseControls != null && !opResponseControls.isEmpty()) { |
| | | writer.writeStartSequence(); |
| | | for (Map.Entry<Integer, List<Control>> entry : opResponseControls.entrySet()) { |
| | | writer.writeStartSequence(); |
| | | writer.writeInteger(entry.getKey()); |
| | | writer.writeStartSequence(); |
| | | for (Control control : entry.getValue()) { |
| | | writer.writeOctetString(control.getValue()); |
| | | } |
| | | writer.writeEndSequence(); |
| | | writer.writeEndSequence(); |
| | | } |
| | | writer.writeEndSequence(); |
| | | } |
| | | writer.writeEndSequence(); |
| | | } |
| | | } catch (final IOException ioe) { |
| | | throw new RuntimeException(ioe); |
| | | } |
| | | return buffer.toByteString(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "EndTransactionExtendedResult(resultCode=" + |
| | | getResultCode() + |
| | | ", matchedDN=" + |
| | | getMatchedDN() + |
| | | ", diagnosticMessage=" + |
| | | getDiagnosticMessage() + |
| | | ", referrals=" + |
| | | getReferralURIs() + |
| | | ", responseName=" + |
| | | getOID() + |
| | | ", failedOpMessageID=" + |
| | | failedOpMessageID + |
| | | ", opResponseControls=" + |
| | | opResponseControls + |
| | | ", controls=" + |
| | | getControls() + |
| | | ")"; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package com.forgerock.opendj.ldap.extensions; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.Control; |
| | | import org.forgerock.opendj.ldap.requests.AbstractExtendedRequest; |
| | | import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | /* |
| | | A Start Transaction Request is an LDAPMessage of CHOICE extendedReq |
| | | where the requestName is 1.3.6.1.1.21.1 and the requestValue is |
| | | absent. |
| | | */ |
| | | public class StartTransactionExtendedRequest extends AbstractExtendedRequest<StartTransactionExtendedRequest, StartTransactionExtendedResult> { |
| | | public static final String START_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.1"; |
| | | |
| | | @Override |
| | | public String getOID() { |
| | | return START_TRANSACTION_REQUEST_OID; |
| | | } |
| | | @Override |
| | | public ExtendedResultDecoder<StartTransactionExtendedResult> getResultDecoder() { |
| | | return RESULT_DECODER; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString getValue() { |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return false; |
| | | } |
| | | |
| | | private static final StartTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new StartTransactionExtendedRequest.ResultDecoder(); |
| | | |
| | | private static final class ResultDecoder extends AbstractExtendedResultDecoder<StartTransactionExtendedResult> { |
| | | @Override |
| | | public StartTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) { |
| | | if (!resultCode.isExceptional()) { |
| | | throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue()); |
| | | } |
| | | return StartTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage); |
| | | } |
| | | |
| | | @Override |
| | | public StartTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException { |
| | | if (result instanceof StartTransactionExtendedResult) { |
| | | return (StartTransactionExtendedResult) result; |
| | | } |
| | | |
| | | final ResultCode resultCode = result.getResultCode(); |
| | | final StartTransactionExtendedResult newResult = |
| | | StartTransactionExtendedResult.newResult(resultCode) |
| | | .setMatchedDN(result.getMatchedDN()) |
| | | .setDiagnosticMessage(result.getDiagnosticMessage()); |
| | | |
| | | final ByteString responseValue = result.getValue(); |
| | | if (!resultCode.isExceptional() && responseValue == null) { |
| | | throw DecodeException.error(LocalizableMessage.raw("Empty response value")); |
| | | } |
| | | if (responseValue != null) { |
| | | try { |
| | | final ASN1Reader reader = ASN1.getReader(responseValue); |
| | | newResult.setTransactionID(reader.readOctetStringAsString()); |
| | | } catch (final IOException e) { |
| | | throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e); |
| | | } |
| | | } |
| | | for (final Control control : result.getControls()) { |
| | | newResult.addControl(control); |
| | | } |
| | | return newResult; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package com.forgerock.opendj.ldap.extensions; |
| | | |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Writer; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.responses.AbstractExtendedResult; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import java.io.IOException; |
| | | |
| | | /* |
| | | A Start Transaction Response is an LDAPMessage of CHOICE extendedRes |
| | | sent in response to a Start Transaction Request. Its responseName is |
| | | absent. When the resultCode is success (0), responseValue is present |
| | | and contains a transaction identifier. Otherwise, the responseValue |
| | | is absent. |
| | | */ |
| | | public class StartTransactionExtendedResult extends AbstractExtendedResult<StartTransactionExtendedResult> { |
| | | @Override |
| | | public String getOID() { |
| | | return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID; |
| | | } |
| | | |
| | | private StartTransactionExtendedResult(final ResultCode resultCode) { |
| | | super(resultCode); |
| | | } |
| | | |
| | | public static StartTransactionExtendedResult newResult(final ResultCode resultCode) { |
| | | Reject.ifNull(resultCode); |
| | | return new StartTransactionExtendedResult(resultCode); |
| | | } |
| | | |
| | | private String transactionID = null; |
| | | |
| | | public StartTransactionExtendedResult setTransactionID(final String transactionID) { |
| | | this.transactionID = transactionID; |
| | | return this; |
| | | } |
| | | |
| | | public String getTransactionID() { |
| | | return transactionID; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString getValue() { |
| | | final ByteStringBuilder buffer = new ByteStringBuilder(); |
| | | final ASN1Writer writer = ASN1.getWriter(buffer); |
| | | try { |
| | | writer.writeOctetString(transactionID); |
| | | } catch (final IOException ioe) { |
| | | throw new RuntimeException(ioe); |
| | | } |
| | | return buffer.toByteString(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "StartTransactionExtendedResult(resultCode=" + |
| | | getResultCode() + |
| | | ", matchedDN=" + |
| | | getMatchedDN() + |
| | | ", diagnosticMessage=" + |
| | | getDiagnosticMessage() + |
| | | ", referrals=" + |
| | | getReferralURIs() + |
| | | ", responseName=" + |
| | | getOID() + |
| | | ", transactionID=" + |
| | | transactionID + |
| | | ", controls=" + |
| | | getControls() + |
| | | ")"; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems,LLC. |
| | | */ |
| | | package org.forgerock.opendj.ldap.controls; |
| | | |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.DecodeOptions; |
| | | import org.forgerock.util.Reject; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_BAD_OID; |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_DECODE_NULL; |
| | | |
| | | public class TransactionSpecificationRequestControl implements Control{ |
| | | |
| | | public final static String OID="1.3.6.1.1.21.2"; |
| | | |
| | | final String transactionId; |
| | | public TransactionSpecificationRequestControl(String transactionId) { |
| | | Reject.ifNull(transactionId); |
| | | this.transactionId = transactionId; |
| | | } |
| | | |
| | | @Override |
| | | public String getOID() { |
| | | return OID; |
| | | } |
| | | |
| | | @Override |
| | | public ByteString getValue() { |
| | | return ByteString.valueOfUtf8(transactionId); |
| | | } |
| | | |
| | | @Override |
| | | public boolean hasValue() { |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public boolean isCritical() { |
| | | return true; |
| | | } |
| | | |
| | | public static final ControlDecoder<TransactionSpecificationRequestControl> DECODER = new ControlDecoder<TransactionSpecificationRequestControl>() { |
| | | @Override |
| | | public TransactionSpecificationRequestControl decodeControl(final Control control, final DecodeOptions options) |
| | | throws DecodeException { |
| | | Reject.ifNull(control); |
| | | |
| | | if (control instanceof TransactionSpecificationRequestControl) { |
| | | return (TransactionSpecificationRequestControl) control; |
| | | } |
| | | |
| | | if (!control.getOID().equals(OID)) { |
| | | throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_BAD_OID.get(control.getOID(), OID)); |
| | | } |
| | | |
| | | if (!control.hasValue()) { |
| | | throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_DECODE_NULL.get()); |
| | | } |
| | | |
| | | return new TransactionSpecificationRequestControl(control.getValue().toString()); |
| | | } |
| | | |
| | | @Override |
| | | public String getOID() { |
| | | return OID; |
| | | } |
| | | }; |
| | | } |
| | |
| | | [#appendix-standards] |
| | | == Standards, RFCs, & Internet-Drafts |
| | | |
| | | OpenDJ 3.5 software implements the following RFCs, Internet-Drafts, and standards: |
| | | OpenDJ software implements the following RFCs, Internet-Drafts, and standards: |
| | | -- |
| | | |
| | | [#rfc1274] |
| | |
| | | + |
| | | Describes the Lightweight Directory Access Protocol (LDAP) / X.500 'entryDN' operational attribute, that provides a copy of the entry's distinguished name for use in attribute value assertions. |
| | | |
| | | [#rfc5805] |
| | | link:http://tools.ietf.org/html/rfc5805[RFC 5805: Lightweight Directory Access Protocol (LDAP) Transactions, window=\_top]:: |
| | | + |
| | | Lightweight Directory Access Protocol (LDAP) update operations, such as Add, Delete, and Modify operations, have atomic, consistency, |
| | | isolation, durability (ACID) properties. Each of these update operations act upon an entry. It is often desirable to update two or |
| | | more entries in a single unit of interaction, a transaction. |
| | | |
| | | [#fips180-1] |
| | | link:http://www.itl.nist.gov/fipspubs/fip180-1.htm[FIPS 180-1: Secure Hash Standard (SHA-1), window=\_top]:: |
| | | + |
| New file |
| | |
| | | <?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 2025 3A Systems,LLC. |
| | | ! --> |
| | | <adm:managed-object name="end-transaction-extended-operation-handler" |
| | | plural-name="end-transaction-extended-operation-handlers" |
| | | package="org.forgerock.opendj.server.config" |
| | | extends="extended-operation-handler" |
| | | xmlns:adm="http://opendj.forgerock.org/admin" |
| | | xmlns:ldap="http://opendj.forgerock.org/admin-ldap"> |
| | | <adm:synopsis> |
| | | The |
| | | <adm:user-friendly-name /> |
| | | An End Transaction Request is an LDAPMessage of CHOICE extendedReq |
| | | where the requestName is 1.3.6.1.1.21.3 and the requestValue is |
| | | present and contains a BER-encoded txnEndReq. |
| | | |
| | | txnEndReq ::= SEQUENCE { |
| | | commit BOOLEAN DEFAULT TRUE, |
| | | identifier OCTET STRING } |
| | | |
| | | A commit value of TRUE indicates a request to commit the transaction |
| | | identified by the identifier. A commit value of FALSE indicates a |
| | | request to abort the identified transaction. |
| | | |
| | | An End Transaction Response is an LDAPMessage sent in response to a |
| | | End Transaction Request. Its response name is absent. The |
| | | responseValue when present contains a BER-encoded txnEndRes. |
| | | |
| | | txnEndRes ::= SEQUENCE { |
| | | messageID MessageID OPTIONAL, |
| | | -- msgid associated with non-success resultCode |
| | | updatesControls SEQUENCE OF updateControls SEQUENCE { |
| | | messageID MessageID, |
| | | -- msgid associated with controls |
| | | controls Controls |
| | | } OPTIONAL |
| | | } |
| | | -- where MessageID and Controls are as specified in RFC 4511 |
| | | |
| | | The txnEndRes.messageID provides the message id of the update request |
| | | associated with a non-success response. txnEndRes.messageID is |
| | | absent when resultCode of the End Transaction Response is success |
| | | (0). |
| | | </adm:synopsis> |
| | | <adm:profile name="ldap"> |
| | | <ldap:object-class> |
| | | <ldap:name> |
| | | ds-cfg-end-transaction-extended-operation-handler |
| | | </ldap:name> |
| | | <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior> |
| | | </ldap:object-class> |
| | | </adm:profile> |
| | | <adm:property-override name="java-class" advanced="true"> |
| | | <adm:default-behavior> |
| | | <adm:defined> |
| | | <adm:value> |
| | | org.opends.server.extensions.EndTransactionExtendedOperation |
| | | </adm:value> |
| | | </adm:defined> |
| | | </adm:default-behavior> |
| | | </adm:property-override> |
| | | </adm:managed-object> |
| New file |
| | |
| | | <?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 2025 3A Systems,LLC. |
| | | ! --> |
| | | <adm:managed-object name="start-transaction-extended-operation-handler" |
| | | plural-name="start-transaction-extended-operation-handlers" |
| | | package="org.forgerock.opendj.server.config" |
| | | extends="extended-operation-handler" |
| | | xmlns:adm="http://opendj.forgerock.org/admin" |
| | | xmlns:ldap="http://opendj.forgerock.org/admin-ldap"> |
| | | <adm:synopsis> |
| | | The |
| | | <adm:user-friendly-name /> |
| | | A client wishing to perform a sequence of directory updates as a |
| | | transaction issues a Start Transaction Request. A server that is |
| | | willing and able to support transactions responds to this request |
| | | with a Start Transaction Response providing a transaction identifier |
| | | and with a resultCode of success (0). Otherwise, the server responds |
| | | with a Start Transaction Response with a resultCode other than |
| | | success indicating the nature of the failure. |
| | | |
| | | The transaction identifier provided upon successful start of a |
| | | transaction is used in subsequent protocol messages to identify this |
| | | transaction. |
| | | </adm:synopsis> |
| | | <adm:profile name="ldap"> |
| | | <ldap:object-class> |
| | | <ldap:name> |
| | | ds-cfg-start-transaction-extended-operation-handler |
| | | </ldap:name> |
| | | <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior> |
| | | </ldap:object-class> |
| | | </adm:profile> |
| | | <adm:property-override name="java-class" advanced="true"> |
| | | <adm:default-behavior> |
| | | <adm:defined> |
| | | <adm:value> |
| | | org.opends.server.extensions.StartTransactionExtendedOperation |
| | | </adm:value> |
| | | </adm:defined> |
| | | </adm:default-behavior> |
| | | </adm:property-override> |
| | | </adm:managed-object> |
| | |
| | | # Portions Copyright 2012-2014 Manuel Gaupp |
| | | # Portions Copyright 2010-2016 ForgeRock AS. |
| | | # Portions copyright 2015 Edan Idzerda |
| | | # Portions Copyright 2025 3A Systems, LLC. |
| | | |
| | | # This file contains the primary Directory Server configuration. It must not |
| | | # be directly edited while the server is online. The server configuration |
| | |
| | | ds-cfg-java-class: org.opends.server.extensions.WhoAmIExtendedOperation |
| | | ds-cfg-enabled: true |
| | | |
| | | dn: cn=Start Transaction,cn=Extended Operations,cn=config |
| | | objectClass: top |
| | | objectClass: ds-cfg-extended-operation-handler |
| | | objectClass: ds-cfg-start-transaction-extended-operation-handler |
| | | cn: Start Transaction |
| | | ds-cfg-java-class: org.opends.server.extensions.StartTransactionExtendedOperation |
| | | ds-cfg-enabled: true |
| | | |
| | | dn: cn=End Transaction,cn=Extended Operations,cn=config |
| | | objectClass: top |
| | | objectClass: ds-cfg-extended-operation-handler |
| | | objectClass: ds-cfg-end-transaction-extended-operation-handler |
| | | cn: End Transaction |
| | | ds-cfg-java-class: org.opends.server.extensions.EndTransactionExtendedOperation |
| | | ds-cfg-enabled: true |
| | | |
| | | dn: cn=Group Implementations,cn=config |
| | | objectClass: top |
| | | objectClass: ds-cfg-branch |
| | |
| | | STRUCTURAL |
| | | MAY ds-cfg-pbkdf2-iterations |
| | | X-ORIGIN 'OpenDJ Directory Server' ) |
| | | |
| | | objectClasses: ( 1.3.6.1.4.1.60142.1.2.1 |
| | | NAME 'ds-cfg-start-transaction-extended-operation-handler' |
| | | SUP ds-cfg-extended-operation-handler |
| | | STRUCTURAL |
| | | X-ORIGIN 'OpenDS Directory Server' ) |
| | | objectClasses: ( 1.3.6.1.4.1.60142.1.2.2 |
| | | NAME 'ds-cfg-end-transaction-extended-operation-handler' |
| | | SUP ds-cfg-extended-operation-handler |
| | | STRUCTURAL |
| | | X-ORIGIN 'OpenDS Directory Server' ) |
| | |
| | | * |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2025 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.api; |
| | | |
| | |
| | | import java.nio.channels.ByteChannel; |
| | | import java.nio.channels.Selector; |
| | | import java.nio.channels.SocketChannel; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.*; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | |
| | | import org.opends.server.types.Privilege; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchResultReference; |
| | | import org.opends.server.types.operation.RollbackOperation; |
| | | import org.opends.server.util.TimeThread; |
| | | |
| | | import static org.opends.messages.CoreMessages.*; |
| | |
| | | { |
| | | return getConnectionID() < 0; |
| | | } |
| | | |
| | | public class Transaction { |
| | | final String transactionId=UUID.randomUUID().toString().toLowerCase(); |
| | | |
| | | public Transaction() { |
| | | transactions.put(getTransactionId(),this); |
| | | } |
| | | |
| | | public String getTransactionId() { |
| | | return transactionId; |
| | | } |
| | | |
| | | final Queue<Operation> waiting=new LinkedList<>(); |
| | | public void add(Operation operation) { |
| | | waiting.add(operation); |
| | | } |
| | | |
| | | public Queue<Operation> getWaiting() { |
| | | return waiting; |
| | | } |
| | | |
| | | public void clear() { |
| | | transactions.remove(getTransactionId()); |
| | | } |
| | | final Deque<RollbackOperation> completed =new ArrayDeque<>(); |
| | | public void success(RollbackOperation operation) { |
| | | completed.add(operation); |
| | | } |
| | | |
| | | public Deque<RollbackOperation> getCompleted() { |
| | | return completed; |
| | | } |
| | | } |
| | | |
| | | Map<String,Transaction> transactions=new ConcurrentHashMap<>(); |
| | | |
| | | public Transaction startTransaction() { |
| | | return new Transaction(); |
| | | } |
| | | |
| | | public Transaction getTransaction(String id) { |
| | | return transactions.get(id); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest; |
| | | import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult; |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.forgerock.opendj.io.ASN1; |
| | | import org.forgerock.opendj.io.ASN1Reader; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.server.config.server.EndTransactionExtendedOperationHandlerCfg; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.ExtendedOperationHandler; |
| | | import org.opends.server.core.ExtendedOperation; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.types.Operation; |
| | | import org.opends.server.types.operation.RollbackOperation; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.ArrayDeque; |
| | | import java.util.Deque; |
| | | import java.util.LinkedList; |
| | | import java.util.Queue; |
| | | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST; |
| | | import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage; |
| | | |
| | | |
| | | public class EndTransactionExtendedOperation extends ExtendedOperationHandler<EndTransactionExtendedOperationHandlerCfg> |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | public EndTransactionExtendedOperation() |
| | | { |
| | | super(); |
| | | } |
| | | |
| | | @Override |
| | | public void initializeExtendedOperationHandler(EndTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException |
| | | { |
| | | super.initializeExtendedOperationHandler(config); |
| | | } |
| | | |
| | | @Override |
| | | public void processExtendedOperation(ExtendedOperation operation) |
| | | { |
| | | final EndTransactionExtendedRequest request =new EndTransactionExtendedRequest(); |
| | | if (operation.getRequestValue()!= null) { |
| | | final ASN1Reader reader = ASN1.getReader(operation.getRequestValue()); |
| | | try { |
| | | reader.readStartSequence(); |
| | | if (reader.hasNextElement()&& (reader.peekType() == ASN1.UNIVERSAL_BOOLEAN_TYPE)) { |
| | | request.setCommit(reader.readBoolean()); |
| | | } |
| | | request.setTransactionID(reader.readOctetStringAsString()); |
| | | reader.readEndSequence(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | final ClientConnection.Transaction trn=operation.getClientConnection().getTransaction(request.getTransactionID()); |
| | | if (trn==null) { |
| | | operation.setResultCode(ResultCode.CANCELLED); |
| | | operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+request.getTransactionID())); |
| | | return; |
| | | } |
| | | |
| | | final EndTransactionExtendedResult res=EndTransactionExtendedResult.newResult(ResultCode.SUCCESS); |
| | | operation.setResultCode(res.getResultCode()); |
| | | Operation currentOperation=null; |
| | | try { |
| | | while((currentOperation=trn.getWaiting().poll())!=null) { |
| | | if (request.isCommit()) { |
| | | currentOperation.run(); |
| | | if (!ResultCode.SUCCESS.equals(currentOperation.getResultCode())) { |
| | | throw new InterruptedException(); |
| | | } |
| | | currentOperation.operationCompleted(); |
| | | //res.success(currentOperation.getMessageID(),currentOperation.getResponseControls()); |
| | | } |
| | | } |
| | | }catch (Throwable e){ |
| | | res.setFailedMessageID(currentOperation.getMessageID()); |
| | | operation.setResultCode(currentOperation.getResultCode()); |
| | | operation.setErrorMessage(currentOperation.getErrorMessage()); |
| | | //rollback |
| | | RollbackOperation cancelOperation=null; |
| | | while((cancelOperation=trn.getCompleted().pollLast())!=null) { |
| | | try { |
| | | cancelOperation.rollback(); |
| | | }catch (Throwable e2){ |
| | | throw new RuntimeException("rollback error",e2); |
| | | } |
| | | } |
| | | }finally { |
| | | trn.clear(); |
| | | } |
| | | operation.setResponseOID(res.getOID()); |
| | | operation.setResponseValue(res.getValue()); |
| | | } |
| | | |
| | | @Override |
| | | public String getExtendedOperationOID() |
| | | { |
| | | return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID; |
| | | } |
| | | |
| | | @Override |
| | | public String getExtendedOperationName() |
| | | { |
| | | return "End Transaction"; |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest; |
| | | import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.server.config.server.StartTransactionExtendedOperationHandlerCfg; |
| | | import org.opends.server.api.ExtendedOperationHandler; |
| | | import org.opends.server.core.ExtendedOperation; |
| | | import org.opends.server.types.InitializationException; |
| | | |
| | | public class StartTransactionExtendedOperation extends ExtendedOperationHandler<StartTransactionExtendedOperationHandlerCfg> |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | public StartTransactionExtendedOperation() |
| | | { |
| | | super(); |
| | | } |
| | | |
| | | @Override |
| | | public void initializeExtendedOperationHandler(StartTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException |
| | | { |
| | | super.initializeExtendedOperationHandler(config); |
| | | } |
| | | |
| | | @Override |
| | | public void processExtendedOperation(ExtendedOperation operation) |
| | | { |
| | | final StartTransactionExtendedResult res=StartTransactionExtendedResult |
| | | .newResult(ResultCode.SUCCESS) |
| | | .setTransactionID(operation.getClientConnection().startTransaction().getTransactionId()); |
| | | |
| | | operation.setResponseOID(res.getOID()); |
| | | operation.setResponseValue(res.getValue()); |
| | | operation.setResultCode(res.getResultCode()); |
| | | } |
| | | |
| | | @Override |
| | | public String getExtendedOperationOID() |
| | | { |
| | | return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID; |
| | | } |
| | | |
| | | @Override |
| | | public String getExtendedOperationName() |
| | | { |
| | | return "Start Transaction"; |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2025 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.extensions; |
| | | |
| | | import java.util.Map; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.DirectoryThread; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.opends.server.types.AbstractOperation; |
| | | import org.opends.server.types.CancelRequest; |
| | | import org.opends.server.types.DisconnectReason; |
| | | import org.opends.server.types.Operation; |
| | |
| | | { |
| | | // The operation is not null, so process it. Make sure that when |
| | | // processing is complete. |
| | | operation.run(); |
| | | operation.operationCompleted(); |
| | | |
| | | //check has transactionId control |
| | | ClientConnection.Transaction transaction=null; |
| | | if (operation instanceof AbstractOperation) { |
| | | String transactionId = ((AbstractOperation) operation).getTransactionId(); |
| | | if (transactionId!=null){ |
| | | transaction=operation.getClientConnection().getTransaction(transactionId); |
| | | if (transaction==null){ //unknown transactionId |
| | | operation.setResultCode(ResultCode.CANCELLED); |
| | | operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+transactionId)); |
| | | operation.getClientConnection().sendResponse(operation); |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | if (transaction==null) { //run |
| | | operation.run(); |
| | | operation.operationCompleted(); |
| | | }else { //suspend for commit |
| | | transaction.add(operation); |
| | | operation.setResultCode(ResultCode.SUCCESS); |
| | | operation.getClientConnection().sendResponse(operation); |
| | | } |
| | | } |
| | | } |
| | | catch (Throwable t) |
| | |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2023-2025 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.types; |
| | | |
| | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.forgerock.util.Reject; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.plugin.PluginResult.OperationResult; |
| | |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | public String getTransactionId() { |
| | | for (Control control : getRequestControls()) { |
| | | if (control.getOID().equals(TransactionSpecificationRequestControl.OID)) { |
| | | if ((control instanceof LDAPControl) && ((LDAPControl)control).getValue()!=null){ |
| | | return ((LDAPControl) control).getValue().toString(); |
| | | } |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems,LLC. |
| | | */ |
| | | package org.opends.server.types.operation; |
| | | |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.types.CanceledOperationException; |
| | | import org.opends.server.types.DirectoryException; |
| | | |
| | | public interface RollbackOperation |
| | | { |
| | | void rollback() throws CanceledOperationException, DirectoryException; |
| | | } |
| | | |
| | |
| | | * |
| | | * Copyright 2008-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions copyright 2024 3A Systems,LLC. |
| | | * Portions Copyright 2024-2025 3A Systems,LLC. |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | |
| | | import org.forgerock.opendj.ldap.DN; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.RelaxRulesControl; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.forgerock.opendj.ldap.schema.AttributeType; |
| | | import org.forgerock.opendj.ldap.schema.ObjectClass; |
| | | import org.forgerock.opendj.ldap.schema.Syntax; |
| | |
| | | import org.opends.server.core.PasswordPolicy; |
| | | import org.opends.server.core.PersistentSearch; |
| | | import org.opends.server.core.ServerContext; |
| | | import org.opends.server.protocols.ldap.LDAPControl; |
| | | import org.opends.server.schema.AuthPasswordSyntax; |
| | | import org.opends.server.schema.UserPasswordSyntax; |
| | | import org.opends.server.types.Attribute; |
| | |
| | | import org.opends.server.types.LockManager.DNLock; |
| | | import org.opends.server.types.Privilege; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.operation.PostOperationAddOperation; |
| | | import org.opends.server.types.operation.PostResponseAddOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationAddOperation; |
| | | import org.opends.server.types.operation.PreOperationAddOperation; |
| | | import org.opends.server.types.operation.*; |
| | | import org.opends.server.util.TimeThread; |
| | | |
| | | /** |
| | |
| | | public class LocalBackendAddOperation |
| | | extends AddOperationWrapper |
| | | implements PreOperationAddOperation, PostOperationAddOperation, |
| | | PostResponseAddOperation, PostSynchronizationAddOperation |
| | | PostResponseAddOperation, PostSynchronizationAddOperation,RollbackOperation |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | |
| | | } |
| | | |
| | | backend.addEntry(entry, this); |
| | | if (trx!=null) { |
| | | trx.success(this); |
| | | } |
| | | } |
| | | |
| | | LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest, |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public void rollback() throws CanceledOperationException, DirectoryException { |
| | | backend.deleteEntry(entryDN,null); |
| | | } |
| | | |
| | | private void processSynchPostOperationPlugins() |
| | | { |
| | |
| | | { |
| | | RelaxRulesControlRequested = true; |
| | | } |
| | | else if (TransactionSpecificationRequestControl.OID.equals(oid)) |
| | | { |
| | | trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString()); |
| | | } |
| | | else if (c.isCritical() && !backend.supportsControl(oid)) |
| | | { |
| | | throw newDirectoryException(entryDN, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, |
| | |
| | | } |
| | | } |
| | | } |
| | | ClientConnection.Transaction trx=null; |
| | | |
| | | private AccessControlHandler<?> getAccessControlHandler() |
| | | { |
| | | return AccessControlConfigManager.getInstance().getAccessControlHandler(); |
| | | } |
| | | |
| | | } |
| | |
| | | * |
| | | * Copyright 2008-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2022-2024 3A Systems, LLC. |
| | | * Portions Copyright 2022-2025 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.SearchScope; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.opends.server.api.AccessControlHandler; |
| | | import org.opends.server.api.LocalBackend; |
| | | import org.opends.server.api.ClientConnection; |
| | |
| | | import org.opends.server.core.DeleteOperationWrapper; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.PersistentSearch; |
| | | import org.opends.server.protocols.ldap.LDAPControl; |
| | | import org.opends.server.types.CanceledOperationException; |
| | | import org.opends.server.types.Control; |
| | | import org.forgerock.opendj.ldap.DN; |
| | |
| | | import org.opends.server.types.LockManager.DNLock; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SynchronizationProviderResult; |
| | | import org.opends.server.types.operation.PostOperationDeleteOperation; |
| | | import org.opends.server.types.operation.PostResponseDeleteOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationDeleteOperation; |
| | | import org.opends.server.types.operation.PreOperationDeleteOperation; |
| | | import org.opends.server.types.operation.*; |
| | | |
| | | import static org.opends.messages.CoreMessages.*; |
| | | import static org.opends.server.core.DirectoryServer.*; |
| | |
| | | extends DeleteOperationWrapper |
| | | implements PreOperationDeleteOperation, PostOperationDeleteOperation, |
| | | PostResponseDeleteOperation, |
| | | PostSynchronizationDeleteOperation |
| | | PostSynchronizationDeleteOperation, RollbackOperation |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | |
| | | return; |
| | | } |
| | | backend.deleteEntry(entryDN, this); |
| | | if (trx!=null) { |
| | | trx.success(this); |
| | | } |
| | | } |
| | | |
| | | LocalBackendWorkflowElement.addPreReadResponse(this, preReadRequest, entry); |
| | |
| | | { |
| | | continue; |
| | | } |
| | | else if (TransactionSpecificationRequestControl.OID.equals(oid)) |
| | | { |
| | | trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString()); |
| | | } |
| | | else if (c.isCritical() && !backend.supportsControl(oid)) |
| | | { |
| | | throw newDirectoryException(entry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, |
| | |
| | | } |
| | | } |
| | | } |
| | | ClientConnection.Transaction trx=null; |
| | | |
| | | /** |
| | | * Handle conflict resolution. |
| | |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | public void rollback() throws CanceledOperationException, DirectoryException { |
| | | backend.addEntry(entry,null); |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2008-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions copyright 2024 3A Systems,LLC. |
| | | * Portions copyright 2024-2025 3A Systems,LLC. |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ModificationType; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.forgerock.opendj.ldap.schema.AttributeType; |
| | | import org.opends.server.api.AccessControlHandler; |
| | | import org.opends.server.api.LocalBackend; |
| | |
| | | import org.opends.server.core.ModifyDNOperation; |
| | | import org.opends.server.core.ModifyDNOperationWrapper; |
| | | import org.opends.server.core.PersistentSearch; |
| | | import org.opends.server.protocols.ldap.LDAPControl; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.Attributes; |
| | | import org.opends.server.types.CanceledOperationException; |
| | |
| | | import org.opends.server.types.Modification; |
| | | import org.forgerock.opendj.ldap.RDN; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.operation.PostOperationModifyDNOperation; |
| | | import org.opends.server.types.operation.PostResponseModifyDNOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; |
| | | import org.opends.server.types.operation.PreOperationModifyDNOperation; |
| | | import org.opends.server.types.operation.*; |
| | | |
| | | import static org.opends.messages.CoreMessages.*; |
| | | import static org.opends.server.core.DirectoryServer.*; |
| | |
| | | implements PreOperationModifyDNOperation, |
| | | PostOperationModifyDNOperation, |
| | | PostResponseModifyDNOperation, |
| | | PostSynchronizationModifyDNOperation |
| | | PostSynchronizationModifyDNOperation, RollbackOperation |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | |
| | | return; |
| | | } |
| | | currentBackend.renameEntry(entryDN, newEntry, this); |
| | | if (trx!=null) { |
| | | trx.success(this); |
| | | } |
| | | } |
| | | |
| | | // Attach the pre-read and/or post-read controls to the response if |
| | |
| | | { |
| | | continue; |
| | | } |
| | | else if (TransactionSpecificationRequestControl.OID.equals(oid)) |
| | | { |
| | | trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString()); |
| | | } |
| | | else if (c.isCritical() && !backend.supportsControl(oid)) |
| | | { |
| | | throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, |
| | |
| | | } |
| | | } |
| | | } |
| | | ClientConnection.Transaction trx=null; |
| | | |
| | | private AccessControlHandler<?> getAccessControlHandler() |
| | | { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void rollback() throws CanceledOperationException, DirectoryException { |
| | | backend.renameEntry(newEntry.getName(), currentEntry, this); |
| | | } |
| | | } |
| | |
| | | * |
| | | * Copyright 2008-2011 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions copyright 2024 3A Systems,LLC. |
| | | * Portions Copyright 2024-2025 3A Systems,LLC. |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | |
| | | import org.forgerock.opendj.ldap.RDN; |
| | | import org.forgerock.opendj.ldap.ResultCode; |
| | | import org.forgerock.opendj.ldap.controls.RelaxRulesControl; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.forgerock.opendj.ldap.schema.AttributeType; |
| | | import org.forgerock.opendj.ldap.schema.MatchingRule; |
| | | import org.forgerock.opendj.ldap.schema.ObjectClass; |
| | |
| | | import org.opends.server.core.PasswordPolicy; |
| | | import org.opends.server.core.PasswordPolicyState; |
| | | import org.opends.server.core.PersistentSearch; |
| | | import org.opends.server.protocols.ldap.LDAPControl; |
| | | import org.opends.server.schema.AuthPasswordSyntax; |
| | | import org.opends.server.schema.UserPasswordSyntax; |
| | | import org.opends.server.types.AcceptRejectWarn; |
| | |
| | | import org.opends.server.types.Privilege; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SynchronizationProviderResult; |
| | | import org.opends.server.types.operation.PostOperationModifyOperation; |
| | | import org.opends.server.types.operation.PostResponseModifyOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationModifyOperation; |
| | | import org.opends.server.types.operation.PreOperationModifyOperation; |
| | | import org.opends.server.types.operation.*; |
| | | |
| | | import static org.opends.messages.CoreMessages.*; |
| | | import static org.opends.server.config.ConfigConstants.*; |
| | |
| | | extends ModifyOperationWrapper |
| | | implements PreOperationModifyOperation, PostOperationModifyOperation, |
| | | PostResponseModifyOperation, |
| | | PostSynchronizationModifyOperation |
| | | PostSynchronizationModifyOperation, RollbackOperation |
| | | { |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | |
| | | } |
| | | |
| | | backend.replaceEntry(currentEntry, modifiedEntry, this); |
| | | if (trx!=null) { |
| | | trx.success(this); |
| | | } |
| | | |
| | | if (isAuthnManagedLocally()) |
| | | { |
| | |
| | | { |
| | | RelaxRulesControlRequested = true; |
| | | } |
| | | else if (TransactionSpecificationRequestControl.OID.equals(oid)) |
| | | { |
| | | trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString()); |
| | | } |
| | | else if (c.isCritical() && !backend.supportsControl(oid)) |
| | | { |
| | | throw newDirectoryException(currentEntry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, |
| | |
| | | } |
| | | } |
| | | } |
| | | ClientConnection.Transaction trx=null; |
| | | |
| | | private void processNonPasswordModifications() throws DirectoryException |
| | | { |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void rollback() throws CanceledOperationException, DirectoryException { |
| | | backend.replaceEntry(modifiedEntry,currentEntry, this); |
| | | } |
| | | } |
| New file |
| | |
| | | /* |
| | | * 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 2025 3A Systems, LLC. |
| | | */ |
| | | package org.openidentityplatform.opendj; |
| | | |
| | | |
| | | import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest; |
| | | import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult; |
| | | import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest; |
| | | import org.forgerock.opendj.ldap.*; |
| | | import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl; |
| | | import org.forgerock.opendj.ldap.requests.*; |
| | | import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult; |
| | | import org.forgerock.opendj.ldap.responses.Result; |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.testng.Assert; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import static org.assertj.core.api.Assertions.assertThat; |
| | | import static org.assertj.core.api.Assertions.assertThatThrownBy; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest; |
| | | import static org.testng.Assert.assertThrows; |
| | | |
| | | @Test(sequential = true) |
| | | public class Rfc5808TestCase extends DirectoryServerTestCase { |
| | | Connection connection; |
| | | |
| | | @BeforeClass |
| | | public void startServer() throws Exception { |
| | | TestCaseUtils.startServer(); |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | final LDAPConnectionFactory factory =new LDAPConnectionFactory("localhost", TestCaseUtils.getServerLdapPort()); |
| | | connection = factory.getConnection(); |
| | | connection.bind("cn=Directory Manager", "password".toCharArray()); |
| | | assertThat(connection.isValid()).isTrue(); |
| | | } |
| | | |
| | | @Test |
| | | public void test() throws LdapException { |
| | | //unknown transaction in TransactionSpecificationRequestControl |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | AddRequest add=Requests |
| | | .newAddRequest("ou=People,o=test") |
| | | .addAttribute("objectClass", "top", "organizationalUnit") |
| | | .addAttribute("ou", "People") |
| | | .addControl(new TransactionSpecificationRequestControl("bad")) |
| | | ; |
| | | Result result = connection.add(add); |
| | | } |
| | | }); |
| | | |
| | | //unknown transaction in EndTransactionExtendedRequest |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown").setCommit(true)); |
| | | } |
| | | }); |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown")); |
| | | } |
| | | }); |
| | | |
| | | //commit |
| | | StartTransactionExtendedResult resStart=connection.extendedRequest(new StartTransactionExtendedRequest()); |
| | | assertThat(resStart.isSuccess()).isTrue(); |
| | | assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1"); |
| | | String transactionID=resStart.getTransactionID(); |
| | | assertThat(transactionID).isNotEmpty(); |
| | | |
| | | AddRequest add=Requests |
| | | .newAddRequest("ou=People,o=test") |
| | | .addAttribute("objectClass", "top", "organizationalUnit") |
| | | .addAttribute("ou", "People") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | Result result = connection.add(add); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | add= Requests.newAddRequest("sn=bjensen,ou=People,o=test") |
| | | .addAttribute("objectClass","top","person") |
| | | .addAttribute("cn","bjensen") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.add(add); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | ModifyDNRequest mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.modifyDN(mdn); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | ModifyRequest edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test") |
| | | .addModification(ModificationType.REPLACE,"cn","bjensen2") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.modify(edit); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | DeleteRequest delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.delete(delete); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)"); |
| | | } |
| | | }); |
| | | |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID)); |
| | | assertThat(resEnd.isSuccess()).isTrue(); |
| | | assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3"); |
| | | |
| | | //check commit successfully |
| | | assertThat(connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)")).isNotNull(); |
| | | |
| | | //check transaction finished |
| | | String finalTransactionID = transactionID; |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID).setCommit(true)); |
| | | } |
| | | }); |
| | | |
| | | //rollback by EndTransactionExtendedRequest |
| | | resStart=connection.extendedRequest(new StartTransactionExtendedRequest()); |
| | | assertThat(resStart.isSuccess()).isTrue(); |
| | | assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1"); |
| | | transactionID=resStart.getTransactionID(); |
| | | assertThat(transactionID).isNotEmpty(); |
| | | |
| | | add=Requests |
| | | .newAddRequest("ou=People2,o=test") |
| | | .addAttribute("objectClass", "top", "organizationalUnit") |
| | | .addAttribute("ou", "People2") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.add(add); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID).setCommit(false)); |
| | | assertThat(resEnd.isSuccess()).isTrue(); |
| | | assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3"); |
| | | |
| | | //check transaction finished |
| | | String finalTransactionID1 = transactionID; |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID1).setCommit(false)); |
| | | } |
| | | }); |
| | | |
| | | //check rollback successfully |
| | | assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People2)"); |
| | | } |
| | | }); |
| | | |
| | | //rollback by error |
| | | resStart=connection.extendedRequest(new StartTransactionExtendedRequest()); |
| | | assertThat(resStart.isSuccess()).isTrue(); |
| | | assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1"); |
| | | transactionID=resStart.getTransactionID(); |
| | | assertThat(transactionID).isNotEmpty(); |
| | | |
| | | add= Requests.newAddRequest("sn=bjensen0,ou=People,o=test") |
| | | .addAttribute("objectClass","top","person") |
| | | .addAttribute("cn","bjensen0") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.add(add); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | add= Requests.newAddRequest("sn=bjensen,ou=People,o=test") |
| | | .addAttribute("objectClass","top","person") |
| | | .addAttribute("cn","bjensen") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.add(add); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.modifyDN(mdn); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test") |
| | | .addModification(ModificationType.REPLACE,"cn","bjensen2") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.modify(edit); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.delete(delete); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | delete=Requests.newDeleteRequest("sn=bjensen3,ou=People,o=test") |
| | | .addControl(new TransactionSpecificationRequestControl(transactionID)) |
| | | ; |
| | | result = connection.delete(delete); |
| | | assertThat(result.isSuccess()).isTrue(); |
| | | |
| | | String finalTransactionID3 = transactionID; |
| | | assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID3).setCommit(true)); |
| | | } |
| | | }); |
| | | |
| | | //check transaction finished |
| | | String finalTransactionID2 = transactionID; |
| | | assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID2).setCommit(false)); |
| | | } |
| | | }); |
| | | |
| | | //check rollback successfully |
| | | assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() { |
| | | @Override |
| | | public void run() throws Throwable { |
| | | connection.searchSingleEntry("ou=People,o=test",SearchScope.SINGLE_LEVEL,"(cn=bjensen0)"); |
| | | } |
| | | }); |
| | | |
| | | } |
| | | } |