Coding Styles
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,166 +1,165 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// This class provides LBER decoding routines for ASN.1 Types. LBER is a
|
||||
/// subset of BER as described in the following taken from 5.1 of RFC 2251:
|
||||
/// 5.1. Mapping Onto BER-based Transport Services
|
||||
/// The protocol elements of Ldap are encoded for exchange using the
|
||||
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
|
||||
/// high overhead involved in using certain elements of the BER, the
|
||||
/// following additional restrictions are placed on BER-encodings of Ldap
|
||||
/// protocol elements:
|
||||
/// <li>(1) Only the definite form of length encoding will be used.</li>
|
||||
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
|
||||
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
|
||||
/// its contents octets set to hex "FF".
|
||||
/// </li><li>
|
||||
/// (4) If a value of a type is its default value, it MUST be absent.
|
||||
/// Only some BOOLEAN and INTEGER types have default values in this
|
||||
/// protocol definition.
|
||||
/// These restrictions do not apply to ASN.1 types encapsulated inside of
|
||||
/// OCTET STRING values, such as attribute values, unless otherwise
|
||||
/// noted.
|
||||
/// </li>
|
||||
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
|
||||
/// Specification of Basic Notation", 1994.
|
||||
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
|
||||
/// Canonical, and Distinguished Encoding Rules", 1994.
|
||||
/// </summary>
|
||||
internal static class LberDecoder {
|
||||
/// <summary>
|
||||
/// This class provides LBER decoding routines for ASN.1 Types. LBER is a
|
||||
/// subset of BER as described in the following taken from 5.1 of RFC 2251:
|
||||
/// 5.1. Mapping Onto BER-based Transport Services
|
||||
/// The protocol elements of Ldap are encoded for exchange using the
|
||||
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
|
||||
/// high overhead involved in using certain elements of the BER, the
|
||||
/// following additional restrictions are placed on BER-encodings of Ldap
|
||||
/// protocol elements:
|
||||
/// <li>(1) Only the definite form of length encoding will be used.</li>
|
||||
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
|
||||
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
|
||||
/// its contents octets set to hex "FF".
|
||||
/// </li><li>
|
||||
/// (4) If a value of a type is its default value, it MUST be absent.
|
||||
/// Only some BOOLEAN and INTEGER types have default values in this
|
||||
/// protocol definition.
|
||||
/// These restrictions do not apply to ASN.1 types encapsulated inside of
|
||||
/// OCTET STRING values, such as attribute values, unless otherwise
|
||||
/// noted.
|
||||
/// </li>
|
||||
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
|
||||
/// Specification of Basic Notation", 1994.
|
||||
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
|
||||
/// Canonical, and Distinguished Encoding Rules", 1994.
|
||||
/// Decode an LBER encoded value into an Asn1Object from an InputStream.
|
||||
/// This method also returns the total length of this encoded
|
||||
/// Asn1Object (length of type + length of length + length of content)
|
||||
/// in the parameter len. This information is helpful when decoding
|
||||
/// structured types.
|
||||
/// </summary>
|
||||
internal static class LberDecoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Decode an LBER encoded value into an Asn1Object from an InputStream.
|
||||
/// This method also returns the total length of this encoded
|
||||
/// Asn1Object (length of type + length of length + length of content)
|
||||
/// in the parameter len. This information is helpful when decoding
|
||||
/// structured types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <returns>
|
||||
/// Decoded Asn1Obect.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">Unknown tag.</exception>
|
||||
public static Asn1Object Decode(Stream stream, int[] len)
|
||||
{
|
||||
var asn1Id = new Asn1Identifier(stream);
|
||||
var asn1Len = new Asn1Length(stream);
|
||||
|
||||
var length = asn1Len.Length;
|
||||
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
|
||||
|
||||
if (asn1Id.Universal == false)
|
||||
return new Asn1Tagged(stream, length, (Asn1Identifier) asn1Id.Clone());
|
||||
|
||||
switch (asn1Id.Tag)
|
||||
{
|
||||
case Asn1Sequence.Tag:
|
||||
return new Asn1Sequence(stream, length);
|
||||
|
||||
case Asn1Set.Tag:
|
||||
return new Asn1Set(stream, length);
|
||||
|
||||
case Asn1Boolean.Tag:
|
||||
return new Asn1Boolean(stream, length);
|
||||
|
||||
case Asn1Integer.Tag:
|
||||
return new Asn1Integer(stream, length);
|
||||
|
||||
case Asn1OctetString.Tag:
|
||||
return new Asn1OctetString(stream, length);
|
||||
|
||||
case Asn1Enumerated.Tag:
|
||||
return new Asn1Enumerated(stream, length);
|
||||
|
||||
case Asn1Null.Tag:
|
||||
return new Asn1Null(); // has no content to decode.
|
||||
|
||||
default:
|
||||
throw new EndOfStreamException("Unknown tag");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a boolean directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded boolean object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
|
||||
public static bool DecodeBoolean(Stream stream, int len)
|
||||
{
|
||||
var lber = new sbyte[len];
|
||||
|
||||
if (stream.ReadInput(ref lber, 0, lber.Length) != len)
|
||||
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
|
||||
|
||||
return lber[0] != 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a Numeric type directly from a stream. Decodes INTEGER
|
||||
/// and ENUMERATED types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded numeric object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">
|
||||
/// LBER: NUMERIC: decode error: EOF
|
||||
/// or
|
||||
/// LBER: NUMERIC: decode error: EOF.
|
||||
/// </exception>
|
||||
public static long DecodeNumeric(Stream stream, int len)
|
||||
{
|
||||
long l = 0;
|
||||
var r = stream.ReadByte();
|
||||
|
||||
if (r < 0)
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
|
||||
if ((r & 0x80) != 0)
|
||||
{
|
||||
// check for negative number
|
||||
l = -1;
|
||||
}
|
||||
|
||||
l = (l << 8) | r;
|
||||
|
||||
for (var i = 1; i < len; i++)
|
||||
{
|
||||
r = stream.ReadByte();
|
||||
if (r < 0)
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
|
||||
l = (l << 8) | r;
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an OctetString directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>Decoded octet. </returns>
|
||||
public static object DecodeOctetString(Stream stream, int len)
|
||||
{
|
||||
var octets = new sbyte[len];
|
||||
var totalLen = 0;
|
||||
|
||||
while (totalLen < len)
|
||||
{
|
||||
// Make sure we have read all the data
|
||||
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
|
||||
}
|
||||
|
||||
return octets;
|
||||
}
|
||||
}
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <returns>
|
||||
/// Decoded Asn1Obect.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">Unknown tag.</exception>
|
||||
public static Asn1Object Decode(Stream stream, Int32[] len) {
|
||||
Asn1Identifier asn1Id = new Asn1Identifier(stream);
|
||||
Asn1Length asn1Len = new Asn1Length(stream);
|
||||
|
||||
Int32 length = asn1Len.Length;
|
||||
len[0] = asn1Id.EncodedLength + asn1Len.EncodedLength + length;
|
||||
|
||||
if(asn1Id.Universal == false) {
|
||||
return new Asn1Tagged(stream, length, (Asn1Identifier)asn1Id.Clone());
|
||||
}
|
||||
|
||||
switch(asn1Id.Tag) {
|
||||
case Asn1Sequence.Tag:
|
||||
return new Asn1Sequence(stream, length);
|
||||
|
||||
case Asn1Set.Tag:
|
||||
return new Asn1Set(stream, length);
|
||||
|
||||
case Asn1Boolean.Tag:
|
||||
return new Asn1Boolean(stream, length);
|
||||
|
||||
case Asn1Integer.Tag:
|
||||
return new Asn1Integer(stream, length);
|
||||
|
||||
case Asn1OctetString.Tag:
|
||||
return new Asn1OctetString(stream, length);
|
||||
|
||||
case Asn1Enumerated.Tag:
|
||||
return new Asn1Enumerated(stream, length);
|
||||
|
||||
case Asn1Null.Tag:
|
||||
return new Asn1Null(); // has no content to decode.
|
||||
|
||||
default:
|
||||
throw new EndOfStreamException("Unknown tag");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a boolean directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded boolean object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">LBER: BOOLEAN: decode error: EOF.</exception>
|
||||
public static Boolean DecodeBoolean(Stream stream, Int32 len) {
|
||||
SByte[] lber = new SByte[len];
|
||||
|
||||
if(stream.ReadInput(ref lber, 0, lber.Length) != len) {
|
||||
throw new EndOfStreamException("LBER: BOOLEAN: decode error: EOF");
|
||||
}
|
||||
|
||||
return lber[0] != 0x00;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a Numeric type directly from a stream. Decodes INTEGER
|
||||
/// and ENUMERATED types.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>
|
||||
/// Decoded numeric object.
|
||||
/// </returns>
|
||||
/// <exception cref="EndOfStreamException">
|
||||
/// LBER: NUMERIC: decode error: EOF
|
||||
/// or
|
||||
/// LBER: NUMERIC: decode error: EOF.
|
||||
/// </exception>
|
||||
public static Int64 DecodeNumeric(Stream stream, Int32 len) {
|
||||
Int64 l = 0;
|
||||
Int32 r = stream.ReadByte();
|
||||
|
||||
if(r < 0) {
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
}
|
||||
|
||||
if((r & 0x80) != 0) {
|
||||
// check for negative number
|
||||
l = -1;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
l = (l << 8) | r;
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
|
||||
for(Int32 i = 1; i < len; i++) {
|
||||
r = stream.ReadByte();
|
||||
if(r < 0) {
|
||||
throw new EndOfStreamException("LBER: NUMERIC: decode error: EOF");
|
||||
}
|
||||
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
l = (l << 8) | r;
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode an OctetString directly from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">Length in bytes.</param>
|
||||
/// <returns>Decoded octet. </returns>
|
||||
public static Object DecodeOctetString(Stream stream, Int32 len) {
|
||||
SByte[] octets = new SByte[len];
|
||||
Int32 totalLen = 0;
|
||||
|
||||
while(totalLen < len) {
|
||||
// Make sure we have read all the data
|
||||
totalLen += stream.ReadInput(ref octets, totalLen, len - totalLen);
|
||||
}
|
||||
|
||||
return octets;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +1,223 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// This class provides LBER encoding routines for ASN.1 Types. LBER is a
|
||||
/// subset of BER as described in the following taken from 5.1 of RFC 2251:
|
||||
/// 5.1. Mapping Onto BER-based Transport Services
|
||||
/// The protocol elements of Ldap are encoded for exchange using the
|
||||
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
|
||||
/// high overhead involved in using certain elements of the BER, the
|
||||
/// following additional restrictions are placed on BER-encodings of Ldap
|
||||
/// protocol elements:
|
||||
/// <li>(1) Only the definite form of length encoding will be used.</li>
|
||||
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
|
||||
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
|
||||
/// its contents octets set to hex "FF".
|
||||
/// </li><li>
|
||||
/// (4) If a value of a type is its default value, it MUST be absent.
|
||||
/// Only some BOOLEAN and INTEGER types have default values in this
|
||||
/// protocol definition.
|
||||
/// These restrictions do not apply to ASN.1 types encapsulated inside of
|
||||
/// OCTET STRING values, such as attribute values, unless otherwise
|
||||
/// noted.
|
||||
/// </li>
|
||||
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
|
||||
/// Specification of Basic Notation", 1994.
|
||||
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
|
||||
/// Canonical, and Distinguished Encoding Rules", 1994.
|
||||
/// </summary>
|
||||
internal static class LberEncoder {
|
||||
/// <summary>
|
||||
/// This class provides LBER encoding routines for ASN.1 Types. LBER is a
|
||||
/// subset of BER as described in the following taken from 5.1 of RFC 2251:
|
||||
/// 5.1. Mapping Onto BER-based Transport Services
|
||||
/// The protocol elements of Ldap are encoded for exchange using the
|
||||
/// Basic Encoding Rules (BER) [11] of ASN.1 [3]. However, due to the
|
||||
/// high overhead involved in using certain elements of the BER, the
|
||||
/// following additional restrictions are placed on BER-encodings of Ldap
|
||||
/// protocol elements:
|
||||
/// <li>(1) Only the definite form of length encoding will be used.</li>
|
||||
/// <li>(2) OCTET STRING values will be encoded in the primitive form only.</li><li>
|
||||
/// (3) If the value of a BOOLEAN type is true, the encoding MUST have
|
||||
/// its contents octets set to hex "FF".
|
||||
/// </li><li>
|
||||
/// (4) If a value of a type is its default value, it MUST be absent.
|
||||
/// Only some BOOLEAN and INTEGER types have default values in this
|
||||
/// protocol definition.
|
||||
/// These restrictions do not apply to ASN.1 types encapsulated inside of
|
||||
/// OCTET STRING values, such as attribute values, unless otherwise
|
||||
/// noted.
|
||||
/// </li>
|
||||
/// [3] ITU-T Rec. X.680, "Abstract Syntax Notation One (ASN.1) -
|
||||
/// Specification of Basic Notation", 1994.
|
||||
/// [11] ITU-T Rec. X.690, "Specification of ASN.1 encoding rules: Basic,
|
||||
/// Canonical, and Distinguished Encoding Rules", 1994.
|
||||
/// BER Encode an Asn1Boolean directly into the specified output stream.
|
||||
/// </summary>
|
||||
internal static class LberEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// BER Encode an Asn1Boolean directly into the specified output stream.
|
||||
/// </summary>
|
||||
/// <param name="b">The Asn1Boolean object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Boolean b, Stream stream)
|
||||
{
|
||||
Encode(b.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x01);
|
||||
stream.WriteByte((byte) (b.BooleanValue() ? 0xff : 0x00));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Numeric directly into the specified outputstream.
|
||||
/// Use a two's complement representation in the fewest number of octets
|
||||
/// possible.
|
||||
/// Can be used to encode INTEGER and ENUMERATED values.
|
||||
/// </summary>
|
||||
/// <param name="n">The Asn1Numeric object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Numeric n, Stream stream)
|
||||
{
|
||||
var octets = new sbyte[8];
|
||||
sbyte len;
|
||||
var longValue = n.LongValue();
|
||||
long endValue = longValue < 0 ? -1 : 0;
|
||||
var endSign = endValue & 0x80;
|
||||
|
||||
for (len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++)
|
||||
{
|
||||
octets[len] = (sbyte)(longValue & 0xFF);
|
||||
longValue >>= 8;
|
||||
}
|
||||
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte((byte)len);
|
||||
|
||||
for (var i = len - 1; i >= 0; i--)
|
||||
{
|
||||
stream.WriteByte((byte) octets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1OctetString directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="os">The Asn1OctetString object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1OctetString os, Stream stream)
|
||||
{
|
||||
Encode(os.GetIdentifier(), stream);
|
||||
EncodeLength(os.ByteValue().Length, stream);
|
||||
var tempSbyteArray = os.ByteValue();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
|
||||
public static void Encode(Asn1Object obj, Stream stream)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case Asn1Boolean b:
|
||||
Encode(b, stream);
|
||||
break;
|
||||
case Asn1Numeric n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Null n:
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x00); // Length (with no Content)
|
||||
break;
|
||||
case Asn1OctetString n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Structured n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Tagged n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Choice n:
|
||||
Encode(n.ChoiceValue, stream);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Structured into the specified outputstream. This method
|
||||
/// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF.
|
||||
/// </summary>
|
||||
/// <param name="c">The Asn1Structured object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Structured c, Stream stream)
|
||||
{
|
||||
Encode(c.GetIdentifier(), stream);
|
||||
|
||||
var arrayValue = c.ToArray();
|
||||
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
foreach (var obj in arrayValue)
|
||||
{
|
||||
Encode(obj, output);
|
||||
}
|
||||
|
||||
EncodeLength((int) output.Length, stream);
|
||||
|
||||
var tempSbyteArray = output.ToArray();
|
||||
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Tagged directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="t">The Asn1Tagged object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Tagged t, Stream stream)
|
||||
{
|
||||
if (!t.Explicit)
|
||||
{
|
||||
Encode(t.TaggedValue, stream);
|
||||
return;
|
||||
}
|
||||
|
||||
Encode(t.GetIdentifier(), stream);
|
||||
|
||||
// determine the encoded length of the base type.
|
||||
using (var encodedContent = new MemoryStream())
|
||||
{
|
||||
Encode(t.TaggedValue, encodedContent);
|
||||
|
||||
EncodeLength((int) encodedContent.Length, stream);
|
||||
var tempSbyteArray = encodedContent.ToArray().ToSByteArray();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Identifier directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="id">The Asn1Identifier object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Identifier id, Stream stream)
|
||||
{
|
||||
var c = (int) id.Asn1Class;
|
||||
var t = id.Tag;
|
||||
var ccf = (sbyte)((c << 6) | (id.Constructed ? 0x20 : 0));
|
||||
|
||||
if (t < 30)
|
||||
{
|
||||
stream.WriteByte((byte)(ccf | t));
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteByte((byte)(ccf | 0x1F));
|
||||
EncodeTagInteger(t, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeLength(int length, Stream stream)
|
||||
{
|
||||
if (length < 0x80)
|
||||
{
|
||||
stream.WriteByte((byte)length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var octets = new sbyte[4]; // 4 bytes sufficient for 32 bit int.
|
||||
sbyte n;
|
||||
for (n = 0; length != 0; n++)
|
||||
{
|
||||
octets[n] = (sbyte)(length & 0xFF);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
stream.WriteByte((byte)(0x80 | n));
|
||||
|
||||
for (var i = n - 1; i >= 0; i--)
|
||||
stream.WriteByte((byte)octets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the provided tag into the stream.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeTagInteger(int val, Stream stream)
|
||||
{
|
||||
var octets = new sbyte[5];
|
||||
int n;
|
||||
|
||||
for (n = 0; val != 0; n++)
|
||||
{
|
||||
octets[n] = (sbyte)(val & 0x7F);
|
||||
val = val >> 7;
|
||||
}
|
||||
|
||||
for (var i = n - 1; i > 0; i--)
|
||||
{
|
||||
stream.WriteByte((byte)(octets[i] | 0x80));
|
||||
}
|
||||
|
||||
stream.WriteByte((byte)octets[0]);
|
||||
}
|
||||
}
|
||||
/// <param name="b">The Asn1Boolean object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Boolean b, Stream stream) {
|
||||
Encode(b.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x01);
|
||||
stream.WriteByte((Byte)(b.BooleanValue() ? 0xff : 0x00));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Numeric directly into the specified outputstream.
|
||||
/// Use a two's complement representation in the fewest number of octets
|
||||
/// possible.
|
||||
/// Can be used to encode INTEGER and ENUMERATED values.
|
||||
/// </summary>
|
||||
/// <param name="n">The Asn1Numeric object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Numeric n, Stream stream) {
|
||||
SByte[] octets = new SByte[8];
|
||||
SByte len;
|
||||
Int64 longValue = n.LongValue();
|
||||
Int64 endValue = longValue < 0 ? -1 : 0;
|
||||
Int64 endSign = endValue & 0x80;
|
||||
|
||||
for(len = 0; len == 0 || longValue != endValue || (octets[len - 1] & 0x80) != endSign; len++) {
|
||||
octets[len] = (SByte)(longValue & 0xFF);
|
||||
longValue >>= 8;
|
||||
}
|
||||
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte((Byte)len);
|
||||
|
||||
for(Int32 i = len - 1; i >= 0; i--) {
|
||||
stream.WriteByte((Byte)octets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1OctetString directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="os">The Asn1OctetString object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1OctetString os, Stream stream) {
|
||||
Encode(os.GetIdentifier(), stream);
|
||||
EncodeLength(os.ByteValue().Length, stream);
|
||||
SByte[] tempSbyteArray = os.ByteValue();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
|
||||
public static void Encode(Asn1Object obj, Stream stream) {
|
||||
switch(obj) {
|
||||
case Asn1Boolean b:
|
||||
Encode(b, stream);
|
||||
break;
|
||||
case Asn1Numeric n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Null n:
|
||||
Encode(n.GetIdentifier(), stream);
|
||||
stream.WriteByte(0x00); // Length (with no Content)
|
||||
break;
|
||||
case Asn1OctetString n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Structured n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Tagged n:
|
||||
Encode(n, stream);
|
||||
break;
|
||||
case Asn1Choice n:
|
||||
Encode(n.ChoiceValue, stream);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Structured into the specified outputstream. This method
|
||||
/// can be used to encode SET, SET_OF, SEQUENCE, SEQUENCE_OF.
|
||||
/// </summary>
|
||||
/// <param name="c">The Asn1Structured object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Structured c, Stream stream) {
|
||||
Encode(c.GetIdentifier(), stream);
|
||||
|
||||
Asn1Object[] arrayValue = c.ToArray();
|
||||
|
||||
using(MemoryStream output = new MemoryStream()) {
|
||||
foreach(Asn1Object obj in arrayValue) {
|
||||
Encode(obj, output);
|
||||
}
|
||||
|
||||
EncodeLength((Int32)output.Length, stream);
|
||||
|
||||
Byte[] tempSbyteArray = output.ToArray();
|
||||
stream.Write(tempSbyteArray, 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Tagged directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="t">The Asn1Tagged object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Tagged t, Stream stream) {
|
||||
if(!t.Explicit) {
|
||||
Encode(t.TaggedValue, stream);
|
||||
return;
|
||||
}
|
||||
|
||||
Encode(t.GetIdentifier(), stream);
|
||||
|
||||
// determine the encoded length of the base type.
|
||||
using(MemoryStream encodedContent = new MemoryStream()) {
|
||||
Encode(t.TaggedValue, encodedContent);
|
||||
|
||||
EncodeLength((Int32)encodedContent.Length, stream);
|
||||
SByte[] tempSbyteArray = encodedContent.ToArray().ToSByteArray();
|
||||
stream.Write(tempSbyteArray.ToByteArray(), 0, tempSbyteArray.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an Asn1Identifier directly into the specified outputstream.
|
||||
/// </summary>
|
||||
/// <param name="id">The Asn1Identifier object to encode.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public static void Encode(Asn1Identifier id, Stream stream) {
|
||||
Int32 c = (Int32)id.Asn1Class;
|
||||
Int32 t = id.Tag;
|
||||
SByte ccf = (SByte)((c << 6) | (id.Constructed ? 0x20 : 0));
|
||||
|
||||
if(t < 30) {
|
||||
#pragma warning disable CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
stream.WriteByte((Byte)(ccf | t));
|
||||
#pragma warning restore CS0675 // Bitweiser OR-Operator, der bei einem signaturerweiterten Operanden verwendet wurde.
|
||||
} else {
|
||||
stream.WriteByte((Byte)(ccf | 0x1F));
|
||||
EncodeTagInteger(t, stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the length.
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeLength(Int32 length, Stream stream) {
|
||||
if(length < 0x80) {
|
||||
stream.WriteByte((Byte)length);
|
||||
} else {
|
||||
SByte[] octets = new SByte[4]; // 4 bytes sufficient for 32 bit int.
|
||||
SByte n;
|
||||
for(n = 0; length != 0; n++) {
|
||||
octets[n] = (SByte)(length & 0xFF);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
stream.WriteByte((Byte)(0x80 | n));
|
||||
|
||||
for(Int32 i = n - 1; i >= 0; i--) {
|
||||
stream.WriteByte((Byte)octets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the provided tag into the stream.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private static void EncodeTagInteger(Int32 val, Stream stream) {
|
||||
SByte[] octets = new SByte[5];
|
||||
Int32 n;
|
||||
|
||||
for(n = 0; val != 0; n++) {
|
||||
octets[n] = (SByte)(val & 0x7F);
|
||||
val >>= 7;
|
||||
}
|
||||
|
||||
for(Int32 i = n - 1; i > 0; i--) {
|
||||
stream.WriteByte((Byte)(octets[i] | 0x80));
|
||||
}
|
||||
|
||||
stream.WriteByte((Byte)octets[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,402 +1,382 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Unosquare.Swan.Exceptions;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// The central class that encapsulates the connection
|
||||
/// to a directory server through the Ldap protocol.
|
||||
/// LdapConnection objects are used to perform common Ldap
|
||||
/// operations such as search, modify and add.
|
||||
/// In addition, LdapConnection objects allow you to bind to an
|
||||
/// Ldap server, set connection and search constraints, and perform
|
||||
/// several other tasks.
|
||||
/// An LdapConnection object is not connected on
|
||||
/// construction and can only be connected to one server at one
|
||||
/// port.
|
||||
///
|
||||
/// Based on https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to use the LdapConnection class:
|
||||
///
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Networking.Ldap;
|
||||
/// using System.Threading.Tasks;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // create a LdapConnection object
|
||||
/// var connection = new LdapConnection();
|
||||
///
|
||||
/// // connect to a server
|
||||
/// await connection.Connect("ldap.forumsys.com", 389);
|
||||
///
|
||||
/// // set up the credentials
|
||||
/// await connection.Bind("cn=read-only-admin,dc=example,dc=com", "password");
|
||||
///
|
||||
/// // retrieve all entries that have the specified email using ScopeSub
|
||||
/// // which searches all entries at all levels under
|
||||
/// // and including the specified base DN
|
||||
/// var searchResult = await connection
|
||||
/// .Search("dc=example,dc=com", LdapConnection.ScopeSub, "(cn=Isaac Newton)");
|
||||
///
|
||||
/// // if there are more entries remaining keep going
|
||||
/// while (searchResult.HasMore())
|
||||
/// {
|
||||
/// // point to the next entry
|
||||
/// var entry = searchResult.Next();
|
||||
///
|
||||
/// // get all attributes
|
||||
/// var entryAttributes = entry.GetAttributeSet();
|
||||
///
|
||||
/// // select its name and print it out
|
||||
/// entryAttributes.GetAttribute("cn").StringValue.Info();
|
||||
/// }
|
||||
///
|
||||
/// // modify Tesla and sets its email as tesla@email.com
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Replace,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // delete the listed values from the given attribute
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Delete,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // add back the recently deleted property
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Add,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // disconnect from the LDAP server
|
||||
/// connection.Disconnect();
|
||||
///
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class LdapConnection : IDisposable {
|
||||
private const Int32 LdapV3 = 3;
|
||||
|
||||
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0069:Verwerfbare Felder verwerfen", Justification = "<Ausstehend>")]
|
||||
private Connection _conn;
|
||||
private Boolean _isDisposing;
|
||||
|
||||
/// <summary>
|
||||
/// The central class that encapsulates the connection
|
||||
/// to a directory server through the Ldap protocol.
|
||||
/// LdapConnection objects are used to perform common Ldap
|
||||
/// operations such as search, modify and add.
|
||||
/// In addition, LdapConnection objects allow you to bind to an
|
||||
/// Ldap server, set connection and search constraints, and perform
|
||||
/// several other tasks.
|
||||
/// An LdapConnection object is not connected on
|
||||
/// construction and can only be connected to one server at one
|
||||
/// port.
|
||||
///
|
||||
/// Based on https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard.
|
||||
/// Returns the protocol version uses to authenticate.
|
||||
/// 0 is returned if no authentication has been performed.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to use the LdapConnection class:
|
||||
///
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Networking.Ldap;
|
||||
/// using System.Threading.Tasks;
|
||||
///
|
||||
/// static async Task Main()
|
||||
/// {
|
||||
/// // create a LdapConnection object
|
||||
/// var connection = new LdapConnection();
|
||||
///
|
||||
/// // connect to a server
|
||||
/// await connection.Connect("ldap.forumsys.com", 389);
|
||||
///
|
||||
/// // set up the credentials
|
||||
/// await connection.Bind("cn=read-only-admin,dc=example,dc=com", "password");
|
||||
///
|
||||
/// // retrieve all entries that have the specified email using ScopeSub
|
||||
/// // which searches all entries at all levels under
|
||||
/// // and including the specified base DN
|
||||
/// var searchResult = await connection
|
||||
/// .Search("dc=example,dc=com", LdapConnection.ScopeSub, "(cn=Isaac Newton)");
|
||||
///
|
||||
/// // if there are more entries remaining keep going
|
||||
/// while (searchResult.HasMore())
|
||||
/// {
|
||||
/// // point to the next entry
|
||||
/// var entry = searchResult.Next();
|
||||
///
|
||||
/// // get all attributes
|
||||
/// var entryAttributes = entry.GetAttributeSet();
|
||||
///
|
||||
/// // select its name and print it out
|
||||
/// entryAttributes.GetAttribute("cn").StringValue.Info();
|
||||
/// }
|
||||
///
|
||||
/// // modify Tesla and sets its email as tesla@email.com
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Replace,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // delete the listed values from the given attribute
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Delete,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // add back the recently deleted property
|
||||
/// connection.Modify("uid=tesla,dc=example,dc=com",
|
||||
/// new[] {
|
||||
/// new LdapModification(LdapModificationOp.Add,
|
||||
/// "mail", "tesla@email.com")
|
||||
/// });
|
||||
///
|
||||
/// // disconnect from the LDAP server
|
||||
/// connection.Disconnect();
|
||||
///
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class LdapConnection : IDisposable
|
||||
{
|
||||
private const int LdapV3 = 3;
|
||||
|
||||
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
|
||||
|
||||
private Connection _conn;
|
||||
private bool _isDisposing;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the protocol version uses to authenticate.
|
||||
/// 0 is returned if no authentication has been performed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
public int ProtocolVersion => BindProperties?.ProtocolVersion ?? LdapV3;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distinguished name (DN) used for as the bind name during
|
||||
/// the last successful bind operation. null is returned
|
||||
/// if no authentication has been performed or if the bind resulted in
|
||||
/// an anonymous connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public string AuthenticationDn => BindProperties == null ? null : (BindProperties.Anonymous ? null : BindProperties.AuthenticationDN);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the method used to authenticate the connection. The return
|
||||
/// value is one of the following:.
|
||||
/// <ul><li>"none" indicates the connection is not authenticated.</li><li>
|
||||
/// "simple" indicates simple authentication was used or that a null
|
||||
/// or empty authentication DN was specified.
|
||||
/// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication method.
|
||||
/// </value>
|
||||
public string AuthenticationMethod => BindProperties == null ? "simple" : BindProperties.AuthenticationMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the connection represented by this object is open
|
||||
/// at this time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if connection is open; false if the connection is closed.
|
||||
/// </returns>
|
||||
public bool Connected => _conn?.IsConnected == true;
|
||||
|
||||
internal BindProperties BindProperties { get; set; }
|
||||
|
||||
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
_isDisposing = true;
|
||||
Disconnect();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously authenticates to the Ldap server (that the object is
|
||||
/// currently connected to) using the specified name, password, Ldap version,
|
||||
/// and constraints.
|
||||
/// If the object has been disconnected from an Ldap server,
|
||||
/// this method attempts to reconnect to the server. If the object
|
||||
/// has already authenticated, the old authentication is discarded.
|
||||
/// </summary>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and password.
|
||||
/// Note: the application should use care in the use
|
||||
/// of String password objects. These are long lived
|
||||
/// objects, and may expose a security risk, especially
|
||||
/// in objects that are serialized. The LdapConnection
|
||||
/// keeps no long lived instances of these objects.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public Task Bind(string dn, string password) => Bind(LdapV3, dn, password);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously authenticates to the Ldap server (that the object is
|
||||
/// currently connected to) using the specified name, password, Ldap version,
|
||||
/// and constraints.
|
||||
/// If the object has been disconnected from an Ldap server,
|
||||
/// this method attempts to reconnect to the server. If the object
|
||||
/// has already authenticated, the old authentication is discarded.
|
||||
/// </summary>
|
||||
/// <param name="version">The Ldap protocol version, use Ldap_V3.
|
||||
/// Ldap_V2 is not supported.</param>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and passwd as password.
|
||||
/// Note: the application should use care in the use
|
||||
/// of String password objects. These are long lived
|
||||
/// objects, and may expose a security risk, especially
|
||||
/// in objects that are serialized. The LdapConnection
|
||||
/// keeps no long lived instances of these objects.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public Task Bind(int version, string dn, string password)
|
||||
{
|
||||
dn = string.IsNullOrEmpty(dn) ? string.Empty : dn.Trim();
|
||||
var passwordData = string.IsNullOrWhiteSpace(password) ? new sbyte[] { } : Encoding.UTF8.GetSBytes(password);
|
||||
|
||||
var anonymous = false;
|
||||
|
||||
if (passwordData.Length == 0)
|
||||
{
|
||||
anonymous = true; // anonymous, password length zero with simple bind
|
||||
dn = string.Empty; // set to null if anonymous
|
||||
}
|
||||
|
||||
BindProperties = new BindProperties(version, dn, "simple", anonymous);
|
||||
|
||||
return RequestLdapMessage(new LdapBindRequest(version, dn, passwordData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the specified host and port.
|
||||
/// If this LdapConnection object represents an open connection, the
|
||||
/// connection is closed first before the new connection is opened.
|
||||
/// At this point, there is no authentication, and any operations are
|
||||
/// conducted as an anonymous client.
|
||||
/// </summary>
|
||||
/// <param name="host">A host name or a dotted string representing the IP address
|
||||
/// of a host running an Ldap server.</param>
|
||||
/// <param name="port">The TCP or UDP port number to connect to or contact.
|
||||
/// The default Ldap port is 389.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task Connect(string host, int port)
|
||||
{
|
||||
var tcpClient = new TcpClient();
|
||||
await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
|
||||
_conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0);
|
||||
|
||||
/// <value>
|
||||
/// The protocol version.
|
||||
/// </value>
|
||||
public Int32 ProtocolVersion => this.BindProperties?.ProtocolVersion ?? LdapV3;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distinguished name (DN) used for as the bind name during
|
||||
/// the last successful bind operation. null is returned
|
||||
/// if no authentication has been performed or if the bind resulted in
|
||||
/// an anonymous connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public String AuthenticationDn => this.BindProperties == null ? null : (this.BindProperties.Anonymous ? null : this.BindProperties.AuthenticationDN);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the method used to authenticate the connection. The return
|
||||
/// value is one of the following:.
|
||||
/// <ul><li>"none" indicates the connection is not authenticated.</li><li>
|
||||
/// "simple" indicates simple authentication was used or that a null
|
||||
/// or empty authentication DN was specified.
|
||||
/// </li><li>"sasl" indicates that a SASL mechanism was used to authenticate</li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication method.
|
||||
/// </value>
|
||||
public String AuthenticationMethod => this.BindProperties == null ? "simple" : this.BindProperties.AuthenticationMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the connection represented by this object is open
|
||||
/// at this time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if connection is open; false if the connection is closed.
|
||||
/// </returns>
|
||||
public Boolean Connected => this._conn?.IsConnected == true;
|
||||
|
||||
internal BindProperties BindProperties {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal List<RfcLdapMessage> Messages { get; } = new List<RfcLdapMessage>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() {
|
||||
if(this._isDisposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposing = true;
|
||||
this.Disconnect();
|
||||
this._cts?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously authenticates to the Ldap server (that the object is
|
||||
/// currently connected to) using the specified name, password, Ldap version,
|
||||
/// and constraints.
|
||||
/// If the object has been disconnected from an Ldap server,
|
||||
/// this method attempts to reconnect to the server. If the object
|
||||
/// has already authenticated, the old authentication is discarded.
|
||||
/// </summary>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and password.
|
||||
/// Note: the application should use care in the use
|
||||
/// of String password objects. These are long lived
|
||||
/// objects, and may expose a security risk, especially
|
||||
/// in objects that are serialized. The LdapConnection
|
||||
/// keeps no long lived instances of these objects.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public Task Bind(String dn, String password) => this.Bind(LdapV3, dn, password);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously authenticates to the Ldap server (that the object is
|
||||
/// currently connected to) using the specified name, password, Ldap version,
|
||||
/// and constraints.
|
||||
/// If the object has been disconnected from an Ldap server,
|
||||
/// this method attempts to reconnect to the server. If the object
|
||||
/// has already authenticated, the old authentication is discarded.
|
||||
/// </summary>
|
||||
/// <param name="version">The Ldap protocol version, use Ldap_V3.
|
||||
/// Ldap_V2 is not supported.</param>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and passwd as password.
|
||||
/// Note: the application should use care in the use
|
||||
/// of String password objects. These are long lived
|
||||
/// objects, and may expose a security risk, especially
|
||||
/// in objects that are serialized. The LdapConnection
|
||||
/// keeps no long lived instances of these objects.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public Task Bind(Int32 version, String dn, String password) {
|
||||
dn = String.IsNullOrEmpty(dn) ? String.Empty : dn.Trim();
|
||||
SByte[] passwordData = String.IsNullOrWhiteSpace(password) ? new SByte[] { } : Encoding.UTF8.GetSBytes(password);
|
||||
|
||||
Boolean anonymous = false;
|
||||
|
||||
if(passwordData.Length == 0) {
|
||||
anonymous = true; // anonymous, password length zero with simple bind
|
||||
dn = String.Empty; // set to null if anonymous
|
||||
}
|
||||
|
||||
this.BindProperties = new BindProperties(version, dn, "simple", anonymous);
|
||||
|
||||
return this.RequestLdapMessage(new LdapBindRequest(version, dn, passwordData));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the specified host and port.
|
||||
/// If this LdapConnection object represents an open connection, the
|
||||
/// connection is closed first before the new connection is opened.
|
||||
/// At this point, there is no authentication, and any operations are
|
||||
/// conducted as an anonymous client.
|
||||
/// </summary>
|
||||
/// <param name="host">A host name or a dotted string representing the IP address
|
||||
/// of a host running an Ldap server.</param>
|
||||
/// <param name="port">The TCP or UDP port number to connect to or contact.
|
||||
/// The default Ldap port is 389.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task Connect(String host, Int32 port) {
|
||||
TcpClient tcpClient = new TcpClient();
|
||||
await tcpClient.ConnectAsync(host, port).ConfigureAwait(false);
|
||||
this._conn = new Connection(tcpClient, Encoding.UTF8, "\r\n", true, 0);
|
||||
|
||||
#pragma warning disable 4014
|
||||
Task.Run(() => RetrieveMessages(), _cts.Token);
|
||||
_ = Task.Run(() => this.RetrieveMessages(), this._cts.Token);
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously disconnects from the Ldap server.
|
||||
/// Before the object can perform Ldap operations again, it must
|
||||
/// reconnect to the server by calling connect.
|
||||
/// The disconnect method abandons any outstanding requests, issues an
|
||||
/// unbind request to the server, and then closes the socket.
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
// disconnect from API call
|
||||
_cts.Cancel();
|
||||
_conn.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously reads the entry for the specified distinguished name (DN),
|
||||
/// using the specified constraints, and retrieves only the specified
|
||||
/// attributes from the entry.
|
||||
/// </summary>
|
||||
/// <param name="dn">The distinguished name of the entry to retrieve.</param>
|
||||
/// <param name="attrs">The names of the attributes to retrieve.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// the LdapEntry read from the server.
|
||||
/// </returns>
|
||||
/// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception>
|
||||
public async Task<LdapEntry> Read(string dn, string[] attrs = null, CancellationToken ct = default)
|
||||
{
|
||||
var sr = await Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
|
||||
LdapEntry ret = null;
|
||||
|
||||
if (sr.HasMore())
|
||||
{
|
||||
ret = sr.Next();
|
||||
if (sr.HasMore())
|
||||
{
|
||||
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the search specified by the parameters,
|
||||
/// also allowing specification of constraints for the search (such
|
||||
/// as the maximum number of entries to find or the maximum time to
|
||||
/// wait for search results).
|
||||
/// </summary>
|
||||
/// <param name="base">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search.</param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public async Task<LdapSearchResults> Search(
|
||||
string @base,
|
||||
LdapScope scope,
|
||||
string filter = "objectClass=*",
|
||||
string[] attrs = null,
|
||||
bool typesOnly = false,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// TODO: Add Search options
|
||||
var msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
|
||||
|
||||
await RequestLdapMessage(msg, ct).ConfigureAwait(false);
|
||||
|
||||
return new LdapSearchResults(Messages, msg.MessageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the specified dn.
|
||||
/// </summary>
|
||||
/// <param name="distinguishedName">Name of the distinguished.</param>
|
||||
/// <param name="mods">The mods.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">distinguishedName.</exception>
|
||||
public Task Modify(string distinguishedName, LdapModification[] mods, CancellationToken ct = default)
|
||||
{
|
||||
if (distinguishedName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(distinguishedName));
|
||||
}
|
||||
|
||||
return RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
|
||||
}
|
||||
|
||||
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
LberEncoder.Encode(msg.Asn1Object, stream);
|
||||
await _conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
while (new List<RfcLdapMessage>(Messages).Any(x => x.MessageId == msg.MessageId) == false)
|
||||
await Task.Delay(100, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
|
||||
var first = new List<RfcLdapMessage>(Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
|
||||
|
||||
if (first != null)
|
||||
{
|
||||
var response = new LdapResponse(first);
|
||||
response.ChkResultCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RetrieveMessages()
|
||||
{
|
||||
while (!_cts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asn1Id = new Asn1Identifier(_conn.ActiveStream);
|
||||
|
||||
if (asn1Id.Tag != Asn1Sequence.Tag)
|
||||
{
|
||||
continue; // loop looking for an RfcLdapMessage identifier
|
||||
}
|
||||
|
||||
// Turn the message into an RfcMessage class
|
||||
var asn1Len = new Asn1Length(_conn.ActiveStream);
|
||||
|
||||
Messages.Add(new RfcLdapMessage(_conn.ActiveStream, asn1Len.Length));
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously disconnects from the Ldap server.
|
||||
/// Before the object can perform Ldap operations again, it must
|
||||
/// reconnect to the server by calling connect.
|
||||
/// The disconnect method abandons any outstanding requests, issues an
|
||||
/// unbind request to the server, and then closes the socket.
|
||||
/// </summary>
|
||||
public void Disconnect() {
|
||||
// disconnect from API call
|
||||
this._cts.Cancel();
|
||||
this._conn.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously reads the entry for the specified distinguished name (DN),
|
||||
/// using the specified constraints, and retrieves only the specified
|
||||
/// attributes from the entry.
|
||||
/// </summary>
|
||||
/// <param name="dn">The distinguished name of the entry to retrieve.</param>
|
||||
/// <param name="attrs">The names of the attributes to retrieve.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// the LdapEntry read from the server.
|
||||
/// </returns>
|
||||
/// <exception cref="LdapException">Read response is ambiguous, multiple entries returned.</exception>
|
||||
public async Task<LdapEntry> Read(String dn, String[] attrs = null, CancellationToken ct = default) {
|
||||
LdapSearchResults sr = await this.Search(dn, LdapScope.ScopeSub, null, attrs, false, ct);
|
||||
LdapEntry ret = null;
|
||||
|
||||
if(sr.HasMore()) {
|
||||
ret = sr.Next();
|
||||
if(sr.HasMore()) {
|
||||
throw new LdapException("Read response is ambiguous, multiple entries returned", LdapStatusCode.AmbiguousResponse);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the search specified by the parameters,
|
||||
/// also allowing specification of constraints for the search (such
|
||||
/// as the maximum number of entries to find or the maximum time to
|
||||
/// wait for search results).
|
||||
/// </summary>
|
||||
/// <param name="base">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search.</param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
public async Task<LdapSearchResults> Search(
|
||||
String @base,
|
||||
LdapScope scope,
|
||||
String filter = "objectClass=*",
|
||||
String[] attrs = null,
|
||||
Boolean typesOnly = false,
|
||||
CancellationToken ct = default) {
|
||||
// TODO: Add Search options
|
||||
LdapSearchRequest msg = new LdapSearchRequest(@base, scope, filter, attrs, 0, 1000, 0, typesOnly, null);
|
||||
|
||||
await this.RequestLdapMessage(msg, ct).ConfigureAwait(false);
|
||||
|
||||
return new LdapSearchResults(this.Messages, msg.MessageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the specified dn.
|
||||
/// </summary>
|
||||
/// <param name="distinguishedName">Name of the distinguished.</param>
|
||||
/// <param name="mods">The mods.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task" /> representing the asynchronous operation.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">distinguishedName.</exception>
|
||||
public Task Modify(String distinguishedName, LdapModification[] mods, CancellationToken ct = default) {
|
||||
if(distinguishedName == null) {
|
||||
throw new ArgumentNullException(nameof(distinguishedName));
|
||||
}
|
||||
|
||||
return this.RequestLdapMessage(new LdapModifyRequest(distinguishedName, mods, null), ct);
|
||||
}
|
||||
|
||||
internal async Task RequestLdapMessage(LdapMessage msg, CancellationToken ct = default) {
|
||||
using(MemoryStream stream = new MemoryStream()) {
|
||||
LberEncoder.Encode(msg.Asn1Object, stream);
|
||||
await this._conn.WriteDataAsync(stream.ToArray(), true, ct).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
while(new List<RfcLdapMessage>(this.Messages).Any(x => x.MessageId == msg.MessageId) == false) {
|
||||
await Task.Delay(100, ct).ConfigureAwait(false);
|
||||
}
|
||||
} catch(ArgumentException) {
|
||||
// expected
|
||||
}
|
||||
|
||||
RfcLdapMessage first = new List<RfcLdapMessage>(this.Messages).FirstOrDefault(x => x.MessageId == msg.MessageId);
|
||||
|
||||
if(first != null) {
|
||||
LdapResponse response = new LdapResponse(first);
|
||||
response.ChkResultCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RetrieveMessages() {
|
||||
while(!this._cts.IsCancellationRequested) {
|
||||
try {
|
||||
Asn1Identifier asn1Id = new Asn1Identifier(this._conn.ActiveStream);
|
||||
|
||||
if(asn1Id.Tag != Asn1Sequence.Tag) {
|
||||
continue; // loop looking for an RfcLdapMessage identifier
|
||||
}
|
||||
|
||||
// Turn the message into an RfcMessage class
|
||||
Asn1Length asn1Len = new Asn1Length(this._conn.ActiveStream);
|
||||
|
||||
this.Messages.Add(new RfcLdapMessage(this._conn.ActiveStream, asn1Len.Length));
|
||||
} catch(IOException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,292 +1,275 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Exceptions;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unosquare.Swan.Exceptions;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Encapsulates optional additional parameters or constraints to be applied to
|
||||
/// an Ldap operation.
|
||||
/// When included with LdapConstraints or LdapSearchConstraints
|
||||
/// on an LdapConnection or with a specific operation request, it is
|
||||
/// sent to the server along with operation requests.
|
||||
/// </summary>
|
||||
public class LdapControl {
|
||||
/// <summary>
|
||||
/// Encapsulates optional additional parameters or constraints to be applied to
|
||||
/// an Ldap operation.
|
||||
/// When included with LdapConstraints or LdapSearchConstraints
|
||||
/// on an LdapConnection or with a specific operation request, it is
|
||||
/// sent to the server along with operation requests.
|
||||
/// Initializes a new instance of the <see cref="LdapControl"/> class.
|
||||
/// Constructs a new LdapControl object using the specified values.
|
||||
/// </summary>
|
||||
public class LdapControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapControl"/> class.
|
||||
/// Constructs a new LdapControl object using the specified values.
|
||||
/// </summary>
|
||||
/// <param name="oid">The OID of the control, as a dotted string.</param>
|
||||
/// <param name="critical">True if the Ldap operation should be discarded if
|
||||
/// the control is not supported. False if
|
||||
/// the operation can be processed without the control.</param>
|
||||
/// <param name="values">The control-specific data.</param>
|
||||
/// <exception cref="ArgumentException">An OID must be specified.</exception>
|
||||
public LdapControl(string oid, bool critical, sbyte[] values)
|
||||
{
|
||||
if (oid == null)
|
||||
{
|
||||
throw new ArgumentException("An OID must be specified");
|
||||
}
|
||||
|
||||
Asn1Object = new RfcControl(
|
||||
oid,
|
||||
new Asn1Boolean(critical),
|
||||
values == null ? null : new Asn1OctetString(values));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the identifier of the control.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The identifier.
|
||||
/// </value>
|
||||
public string Id => Asn1Object.ControlType.StringValue();
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the control is critical for the operation.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if critical; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool Critical => Asn1Object.Criticality.BooleanValue();
|
||||
|
||||
internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
|
||||
|
||||
internal RfcControl Asn1Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registers a class to be instantiated on receipt of a control with the
|
||||
/// given OID.
|
||||
/// Any previous registration for the OID is overridden. The
|
||||
/// controlClass must be an extension of LdapControl.
|
||||
/// </summary>
|
||||
/// <param name="oid">The object identifier of the control.</param>
|
||||
/// <param name="controlClass">A class which can instantiate an LdapControl.</param>
|
||||
public static void Register(string oid, Type controlClass)
|
||||
=> RegisteredControls.RegisterResponseControl(oid, controlClass);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the control-specific data of the object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The control-specific data of the object as a byte array,
|
||||
/// or null if the control has no data.
|
||||
/// </returns>
|
||||
public sbyte[] GetValue() => Asn1Object.ControlValue?.ByteValue();
|
||||
|
||||
internal void SetValue(sbyte[] controlValue)
|
||||
{
|
||||
Asn1Object.ControlValue = new Asn1OctetString(controlValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="oid">The OID of the control, as a dotted string.</param>
|
||||
/// <param name="critical">True if the Ldap operation should be discarded if
|
||||
/// the control is not supported. False if
|
||||
/// the operation can be processed without the control.</param>
|
||||
/// <param name="values">The control-specific data.</param>
|
||||
/// <exception cref="ArgumentException">An OID must be specified.</exception>
|
||||
public LdapControl(String oid, Boolean critical, SByte[] values) {
|
||||
if(oid == null) {
|
||||
throw new ArgumentException("An OID must be specified");
|
||||
}
|
||||
|
||||
this.Asn1Object = new RfcControl(
|
||||
oid,
|
||||
new Asn1Boolean(critical),
|
||||
values == null ? null : new Asn1OctetString(values));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple bind request.
|
||||
/// Returns the identifier of the control.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public class LdapBindRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapBindRequest"/> class.
|
||||
/// Constructs a simple bind request.
|
||||
/// </summary>
|
||||
/// <param name="version">The Ldap protocol version, use Ldap_V3.
|
||||
/// Ldap_V2 is not supported.</param>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and passwd as password.</param>
|
||||
public LdapBindRequest(int version, string dn, sbyte[] password)
|
||||
: base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Authentication DN for a bind request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public string AuthenticationDN => Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Asn1Object.ToString();
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The identifier.
|
||||
/// </value>
|
||||
public String Id => this.Asn1Object.ControlType.StringValue();
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a continuation reference from an asynchronous search operation.
|
||||
/// Returns whether the control is critical for the operation.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
internal class LdapSearchResultReference : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class.
|
||||
/// Constructs an LdapSearchResultReference object.
|
||||
/// </summary>
|
||||
/// <param name="message">The LdapMessage with a search reference.</param>
|
||||
internal LdapSearchResultReference(RfcLdapMessage message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns any URLs in the object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The referrals.
|
||||
/// </value>
|
||||
public string[] Referrals
|
||||
{
|
||||
get
|
||||
{
|
||||
var references = ((RfcSearchResultReference)Message.Response).ToArray();
|
||||
var srefs = new string[references.Length];
|
||||
for (var i = 0; i < references.Length; i++)
|
||||
{
|
||||
srefs[i] = ((Asn1OctetString)references[i]).StringValue();
|
||||
}
|
||||
|
||||
return srefs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LdapResponse : LdapMessage
|
||||
{
|
||||
internal LdapResponse(RfcLdapMessage message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public string ErrorMessage => ((IRfcResponse)Message.Response).GetErrorMessage().StringValue();
|
||||
|
||||
public string MatchedDN => ((IRfcResponse)Message.Response).GetMatchedDN().StringValue();
|
||||
|
||||
public LdapStatusCode ResultCode => Message.Response is RfcSearchResultEntry ||
|
||||
(IRfcResponse)Message.Response is RfcIntermediateResponse
|
||||
? LdapStatusCode.Success
|
||||
: (LdapStatusCode)((IRfcResponse)Message.Response).GetResultCode().IntValue();
|
||||
|
||||
internal LdapException Exception { get; set; }
|
||||
|
||||
internal void ChkResultCode()
|
||||
{
|
||||
if (Exception != null)
|
||||
{
|
||||
throw Exception;
|
||||
}
|
||||
|
||||
switch (ResultCode)
|
||||
{
|
||||
case LdapStatusCode.Success:
|
||||
case LdapStatusCode.CompareTrue:
|
||||
case LdapStatusCode.CompareFalse:
|
||||
break;
|
||||
case LdapStatusCode.Referral:
|
||||
throw new LdapException(
|
||||
"Automatic referral following not enabled",
|
||||
LdapStatusCode.Referral,
|
||||
ErrorMessage);
|
||||
default:
|
||||
throw new LdapException(ResultCode.ToString().Humanize(), ResultCode, ErrorMessage, MatchedDN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// <c>true</c> if critical; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean Critical => this.Asn1Object.Criticality.BooleanValue();
|
||||
|
||||
internal static RespControlVector RegisteredControls { get; } = new RespControlVector(5);
|
||||
|
||||
internal RfcControl Asn1Object {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RespControlVector class implements extends the
|
||||
/// existing Vector class so that it can be used to maintain a
|
||||
/// list of currently registered control responses.
|
||||
/// Registers a class to be instantiated on receipt of a control with the
|
||||
/// given OID.
|
||||
/// Any previous registration for the OID is overridden. The
|
||||
/// controlClass must be an extension of LdapControl.
|
||||
/// </summary>
|
||||
internal class RespControlVector : List<RespControlVector.RegisteredControl>
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
public RespControlVector(int cap)
|
||||
: base(cap)
|
||||
{
|
||||
}
|
||||
|
||||
public void RegisterResponseControl(string oid, Type controlClass)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
Add(new RegisteredControl(this, oid, controlClass));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inner class defined to create a temporary object to encapsulate
|
||||
/// all registration information about a response control.
|
||||
/// </summary>
|
||||
internal class RegisteredControl
|
||||
{
|
||||
public RegisteredControl(RespControlVector enclosingInstance, string oid, Type controlClass)
|
||||
{
|
||||
EnclosingInstance = enclosingInstance;
|
||||
MyOid = oid;
|
||||
MyClass = controlClass;
|
||||
}
|
||||
|
||||
internal Type MyClass { get; }
|
||||
|
||||
internal string MyOid { get; }
|
||||
|
||||
private RespControlVector EnclosingInstance { get; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="oid">The object identifier of the control.</param>
|
||||
/// <param name="controlClass">A class which can instantiate an LdapControl.</param>
|
||||
public static void Register(String oid, Type controlClass)
|
||||
=> RegisteredControls.RegisterResponseControl(oid, controlClass);
|
||||
|
||||
/// <summary>
|
||||
/// Represents and Ldap Bind Request.
|
||||
/// <pre>
|
||||
/// BindRequest ::= [APPLICATION 0] SEQUENCE {
|
||||
/// version INTEGER (1 .. 127),
|
||||
/// name LdapDN,
|
||||
/// authentication AuthenticationChoice }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="IRfcRequest" />
|
||||
internal sealed class RfcBindRequest
|
||||
: Asn1Sequence, IRfcRequest
|
||||
{
|
||||
private readonly sbyte[] _password;
|
||||
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest);
|
||||
|
||||
public RfcBindRequest(int version, string name, sbyte[] password)
|
||||
: base(3)
|
||||
{
|
||||
_password = password;
|
||||
Add(new Asn1Integer(version));
|
||||
Add(name);
|
||||
Add(new RfcAuthenticationChoice(password));
|
||||
}
|
||||
|
||||
public Asn1Integer Version
|
||||
{
|
||||
get => (Asn1Integer)Get(0);
|
||||
set => Set(0, value);
|
||||
}
|
||||
|
||||
public Asn1OctetString Name
|
||||
{
|
||||
get => (Asn1OctetString)Get(1);
|
||||
set => Set(1, value);
|
||||
}
|
||||
|
||||
public RfcAuthenticationChoice AuthenticationChoice
|
||||
{
|
||||
get => (RfcAuthenticationChoice)Get(2);
|
||||
set => Set(2, value);
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => Id;
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString)Get(1)).StringValue();
|
||||
}
|
||||
/// Returns the control-specific data of the object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The control-specific data of the object as a byte array,
|
||||
/// or null if the control has no data.
|
||||
/// </returns>
|
||||
public SByte[] GetValue() => this.Asn1Object.ControlValue?.ByteValue();
|
||||
|
||||
internal void SetValue(SByte[] controlValue) => this.Asn1Object.ControlValue = new Asn1OctetString(controlValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple bind request.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public class LdapBindRequest : LdapMessage {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapBindRequest"/> class.
|
||||
/// Constructs a simple bind request.
|
||||
/// </summary>
|
||||
/// <param name="version">The Ldap protocol version, use Ldap_V3.
|
||||
/// Ldap_V2 is not supported.</param>
|
||||
/// <param name="dn">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name.</param>
|
||||
/// <param name="password">If non-null and non-empty, specifies that the
|
||||
/// connection and all operations through it should
|
||||
/// be authenticated with dn as the distinguished
|
||||
/// name and passwd as password.</param>
|
||||
public LdapBindRequest(Int32 version, String dn, SByte[] password)
|
||||
: base(LdapOperation.BindRequest, new RfcBindRequest(version, dn, password)) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Authentication DN for a bind request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The authentication dn.
|
||||
/// </value>
|
||||
public String AuthenticationDN => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() => this.Asn1Object.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encapsulates a continuation reference from an asynchronous search operation.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
internal class LdapSearchResultReference : LdapMessage {
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResultReference"/> class.
|
||||
/// Constructs an LdapSearchResultReference object.
|
||||
/// </summary>
|
||||
/// <param name="message">The LdapMessage with a search reference.</param>
|
||||
internal LdapSearchResultReference(RfcLdapMessage message)
|
||||
: base(message) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns any URLs in the object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The referrals.
|
||||
/// </value>
|
||||
public String[] Referrals {
|
||||
get {
|
||||
Asn1Object[] references = ((RfcSearchResultReference)this.Message.Response).ToArray();
|
||||
String[] srefs = new String[references.Length];
|
||||
for(Int32 i = 0; i < references.Length; i++) {
|
||||
srefs[i] = ((Asn1OctetString)references[i]).StringValue();
|
||||
}
|
||||
|
||||
return srefs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LdapResponse : LdapMessage {
|
||||
internal LdapResponse(RfcLdapMessage message)
|
||||
: base(message) {
|
||||
}
|
||||
|
||||
public String ErrorMessage => ((IRfcResponse)this.Message.Response).GetErrorMessage().StringValue();
|
||||
|
||||
public String MatchedDN => ((IRfcResponse)this.Message.Response).GetMatchedDN().StringValue();
|
||||
|
||||
public LdapStatusCode ResultCode => this.Message.Response is RfcSearchResultEntry ||
|
||||
(IRfcResponse)this.Message.Response is RfcIntermediateResponse
|
||||
? LdapStatusCode.Success
|
||||
: (LdapStatusCode)((IRfcResponse)this.Message.Response).GetResultCode().IntValue();
|
||||
|
||||
internal LdapException Exception {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal void ChkResultCode() {
|
||||
if(this.Exception != null) {
|
||||
throw this.Exception;
|
||||
}
|
||||
|
||||
switch(this.ResultCode) {
|
||||
case LdapStatusCode.Success:
|
||||
case LdapStatusCode.CompareTrue:
|
||||
case LdapStatusCode.CompareFalse:
|
||||
break;
|
||||
case LdapStatusCode.Referral:
|
||||
throw new LdapException(
|
||||
"Automatic referral following not enabled",
|
||||
LdapStatusCode.Referral,
|
||||
this.ErrorMessage);
|
||||
default:
|
||||
throw new LdapException(this.ResultCode.ToString().Humanize(), this.ResultCode, this.ErrorMessage, this.MatchedDN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The RespControlVector class implements extends the
|
||||
/// existing Vector class so that it can be used to maintain a
|
||||
/// list of currently registered control responses.
|
||||
/// </summary>
|
||||
internal class RespControlVector : List<RespControlVector.RegisteredControl> {
|
||||
private readonly Object _syncLock = new Object();
|
||||
|
||||
public RespControlVector(Int32 cap)
|
||||
: base(cap) {
|
||||
}
|
||||
|
||||
public void RegisterResponseControl(String oid, Type controlClass) {
|
||||
lock(this._syncLock) {
|
||||
this.Add(new RegisteredControl(this, oid, controlClass));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inner class defined to create a temporary object to encapsulate
|
||||
/// all registration information about a response control.
|
||||
/// </summary>
|
||||
internal class RegisteredControl {
|
||||
public RegisteredControl(RespControlVector enclosingInstance, String oid, Type controlClass) {
|
||||
this.EnclosingInstance = enclosingInstance;
|
||||
this.MyOid = oid;
|
||||
this.MyClass = controlClass;
|
||||
}
|
||||
|
||||
internal Type MyClass {
|
||||
get;
|
||||
}
|
||||
|
||||
internal String MyOid {
|
||||
get;
|
||||
}
|
||||
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
|
||||
private RespControlVector EnclosingInstance {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents and Ldap Bind Request.
|
||||
/// <pre>
|
||||
/// BindRequest ::= [APPLICATION 0] SEQUENCE {
|
||||
/// version INTEGER (1 .. 127),
|
||||
/// name LdapDN,
|
||||
/// authentication AuthenticationChoice }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="IRfcRequest" />
|
||||
internal sealed class RfcBindRequest
|
||||
: Asn1Sequence, IRfcRequest {
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
|
||||
private readonly SByte[] _password;
|
||||
private static readonly Asn1Identifier Id = new Asn1Identifier(LdapOperation.BindRequest);
|
||||
|
||||
public RfcBindRequest(Int32 version, String name, SByte[] password)
|
||||
: base(3) {
|
||||
this._password = password;
|
||||
this.Add(new Asn1Integer(version));
|
||||
this.Add(name);
|
||||
this.Add(new RfcAuthenticationChoice(password));
|
||||
}
|
||||
|
||||
public Asn1Integer Version {
|
||||
get => (Asn1Integer)this.Get(0);
|
||||
set => this.Set(0, value);
|
||||
}
|
||||
|
||||
public Asn1OctetString Name {
|
||||
get => (Asn1OctetString)this.Get(1);
|
||||
set => this.Set(1, value);
|
||||
}
|
||||
|
||||
public RfcAuthenticationChoice AuthenticationChoice {
|
||||
get => (RfcAuthenticationChoice)this.Get(2);
|
||||
set => this.Set(2, value);
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => Id;
|
||||
|
||||
public String GetRequestDN() => ((Asn1OctetString)this.Get(1)).StringValue();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,135 +1,130 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Ldap Modification Operators.
|
||||
/// </summary>
|
||||
public enum LdapModificationOp {
|
||||
/// <summary>
|
||||
/// Ldap Modification Operators.
|
||||
/// Adds the listed values to the given attribute, creating
|
||||
/// the attribute if it does not already exist.
|
||||
/// </summary>
|
||||
public enum LdapModificationOp
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the listed values to the given attribute, creating
|
||||
/// the attribute if it does not already exist.
|
||||
/// </summary>
|
||||
Add = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the listed values from the given attribute,
|
||||
/// removing the entire attribute (1) if no values are listed or
|
||||
/// (2) if all current values of the attribute are listed for
|
||||
/// deletion.
|
||||
/// </summary>
|
||||
Delete = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all existing values of the given attribute
|
||||
/// with the new values listed, creating the attribute if it
|
||||
/// does not already exist.
|
||||
/// A replace with no value deletes the entire attribute if it
|
||||
/// exists, and is ignored if the attribute does not exist.
|
||||
/// </summary>
|
||||
Replace = 2,
|
||||
}
|
||||
|
||||
Add = 0,
|
||||
|
||||
/// <summary>
|
||||
/// LDAP valid scopes.
|
||||
/// Deletes the listed values from the given attribute,
|
||||
/// removing the entire attribute (1) if no values are listed or
|
||||
/// (2) if all current values of the attribute are listed for
|
||||
/// deletion.
|
||||
/// </summary>
|
||||
public enum LdapScope
|
||||
{
|
||||
/// <summary>
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search only the base object.
|
||||
/// </summary>
|
||||
ScopeBase = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search only the immediate subordinates of the base object.
|
||||
/// </summary>
|
||||
ScopeOne = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search the base object and all entries within its subtree.
|
||||
/// </summary>
|
||||
ScopeSub = 2,
|
||||
}
|
||||
|
||||
Delete = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Substring Operators.
|
||||
/// Replaces all existing values of the given attribute
|
||||
/// with the new values listed, creating the attribute if it
|
||||
/// does not already exist.
|
||||
/// A replace with no value deletes the entire attribute if it
|
||||
/// exists, and is ignored if the attribute does not exist.
|
||||
/// </summary>
|
||||
internal enum SubstringOp
|
||||
{
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an INITIAL component of a SUBSTRING.
|
||||
/// Note: An initial SUBSTRING is represented as "value*".
|
||||
/// </summary>
|
||||
Initial = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an ANY component of a SUBSTRING.
|
||||
/// Note: An ANY SUBSTRING is represented as "*value*".
|
||||
/// </summary>
|
||||
Any = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for a FINAL component of a SUBSTRING.
|
||||
/// Note: A FINAL SUBSTRING is represented as "*value".
|
||||
/// </summary>
|
||||
Final = 2,
|
||||
}
|
||||
|
||||
Replace = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LDAP valid scopes.
|
||||
/// </summary>
|
||||
public enum LdapScope {
|
||||
/// <summary>
|
||||
/// Filtering Operators.
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search only the base object.
|
||||
/// </summary>
|
||||
internal enum FilterOp
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifier for AND component.
|
||||
/// </summary>
|
||||
And = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for OR component.
|
||||
/// </summary>
|
||||
Or = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for NOT component.
|
||||
/// </summary>
|
||||
Not = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for EQUALITY_MATCH component.
|
||||
/// </summary>
|
||||
EqualityMatch = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for SUBSTRINGS component.
|
||||
/// </summary>
|
||||
Substrings = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for GREATER_OR_EQUAL component.
|
||||
/// </summary>
|
||||
GreaterOrEqual = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for LESS_OR_EQUAL component.
|
||||
/// </summary>
|
||||
LessOrEqual = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for PRESENT component.
|
||||
/// </summary>
|
||||
Present = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for APPROX_MATCH component.
|
||||
/// </summary>
|
||||
ApproxMatch = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for EXTENSIBLE_MATCH component.
|
||||
/// </summary>
|
||||
ExtensibleMatch = 9,
|
||||
}
|
||||
ScopeBase = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search only the immediate subordinates of the base object.
|
||||
/// </summary>
|
||||
ScopeOne = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Used with search to specify that the scope of entrys to search is to
|
||||
/// search the base object and all entries within its subtree.
|
||||
/// </summary>
|
||||
ScopeSub = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Substring Operators.
|
||||
/// </summary>
|
||||
internal enum SubstringOp {
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an INITIAL component of a SUBSTRING.
|
||||
/// Note: An initial SUBSTRING is represented as "value*".
|
||||
/// </summary>
|
||||
Initial = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for an ANY component of a SUBSTRING.
|
||||
/// Note: An ANY SUBSTRING is represented as "*value*".
|
||||
/// </summary>
|
||||
Any = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Search Filter Identifier for a FINAL component of a SUBSTRING.
|
||||
/// Note: A FINAL SUBSTRING is represented as "*value".
|
||||
/// </summary>
|
||||
Final = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filtering Operators.
|
||||
/// </summary>
|
||||
internal enum FilterOp {
|
||||
/// <summary>
|
||||
/// Identifier for AND component.
|
||||
/// </summary>
|
||||
And = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for OR component.
|
||||
/// </summary>
|
||||
Or = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for NOT component.
|
||||
/// </summary>
|
||||
Not = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for EQUALITY_MATCH component.
|
||||
/// </summary>
|
||||
EqualityMatch = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for SUBSTRINGS component.
|
||||
/// </summary>
|
||||
Substrings = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for GREATER_OR_EQUAL component.
|
||||
/// </summary>
|
||||
GreaterOrEqual = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for LESS_OR_EQUAL component.
|
||||
/// </summary>
|
||||
LessOrEqual = 6,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for PRESENT component.
|
||||
/// </summary>
|
||||
Present = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for APPROX_MATCH component.
|
||||
/// </summary>
|
||||
ApproxMatch = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Identifier for EXTENSIBLE_MATCH component.
|
||||
/// </summary>
|
||||
ExtensibleMatch = 9,
|
||||
}
|
||||
}
|
||||
@@ -1,140 +1,120 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// The base class for Ldap request and response messages.
|
||||
/// Subclassed by response messages used in asynchronous operations.
|
||||
/// </summary>
|
||||
public class LdapMessage {
|
||||
internal RfcLdapMessage Message;
|
||||
|
||||
private Int32 _imsgNum = -1; // This instance LdapMessage number
|
||||
|
||||
private LdapOperation _messageType = LdapOperation.Unknown;
|
||||
|
||||
private String _stringTag;
|
||||
|
||||
internal LdapMessage() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base class for Ldap request and response messages.
|
||||
/// Subclassed by response messages used in asynchronous operations.
|
||||
/// Initializes a new instance of the <see cref="LdapMessage"/> class.
|
||||
/// Creates an LdapMessage when sending a protocol operation and sends
|
||||
/// some optional controls with the message.
|
||||
/// </summary>
|
||||
public class LdapMessage
|
||||
{
|
||||
internal RfcLdapMessage Message;
|
||||
|
||||
private int _imsgNum = -1; // This instance LdapMessage number
|
||||
|
||||
private LdapOperation _messageType = LdapOperation.Unknown;
|
||||
|
||||
private string _stringTag;
|
||||
|
||||
internal LdapMessage()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapMessage"/> class.
|
||||
/// Creates an LdapMessage when sending a protocol operation and sends
|
||||
/// some optional controls with the message.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="op">The operation type of message.</param>
|
||||
/// <param name="controls">The controls to use with the operation.</param>
|
||||
/// <seealso cref="Type"></seealso>
|
||||
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null)
|
||||
{
|
||||
// Get a unique number for this request message
|
||||
_messageType = type;
|
||||
RfcControls asn1Ctrls = null;
|
||||
|
||||
if (controls != null)
|
||||
{
|
||||
// Move LdapControls into an RFC 2251 Controls object.
|
||||
asn1Ctrls = new RfcControls();
|
||||
|
||||
foreach (var t in controls)
|
||||
{
|
||||
asn1Ctrls.Add(t.Asn1Object);
|
||||
}
|
||||
}
|
||||
|
||||
// create RFC 2251 LdapMessage
|
||||
Message = new RfcLdapMessage(op, asn1Ctrls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapMessage"/> class.
|
||||
/// Creates an Rfc 2251 LdapMessage when the libraries receive a response
|
||||
/// from a command.
|
||||
/// </summary>
|
||||
/// <param name="message">A response message.</param>
|
||||
internal LdapMessage(RfcLdapMessage message) => Message = message;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the message ID. The message ID is an integer value
|
||||
/// identifying the Ldap request and its response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message identifier.
|
||||
/// </value>
|
||||
public virtual int MessageId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_imsgNum == -1)
|
||||
{
|
||||
_imsgNum = Message.MessageId;
|
||||
}
|
||||
|
||||
return _imsgNum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the message is a request or a response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if request; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public virtual bool Request => Message.IsRequest();
|
||||
|
||||
internal LdapOperation Type
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_messageType == LdapOperation.Unknown)
|
||||
{
|
||||
_messageType = Message.Type;
|
||||
}
|
||||
|
||||
return _messageType;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual RfcLdapMessage Asn1Object => Message;
|
||||
|
||||
internal virtual LdapMessage RequestingMessage => Message.RequestingMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the identifier tag for this message.
|
||||
/// An identifier can be associated with a message with the
|
||||
/// <c>setTag</c> method.
|
||||
/// Tags are set by the application and not by the API or the server.
|
||||
/// If a server response <c>isRequest() == false</c> has no tag,
|
||||
/// the tag associated with the corresponding server request is used.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tag.
|
||||
/// </value>
|
||||
public virtual string Tag
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stringTag != null)
|
||||
{
|
||||
return _stringTag;
|
||||
}
|
||||
|
||||
return Request ? null : RequestingMessage?._stringTag;
|
||||
}
|
||||
|
||||
set => _stringTag = value;
|
||||
}
|
||||
|
||||
private string Name => Type.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString() => $"{Name}({MessageId}): {Message}";
|
||||
}
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="op">The operation type of message.</param>
|
||||
/// <param name="controls">The controls to use with the operation.</param>
|
||||
/// <seealso cref="Type"></seealso>
|
||||
internal LdapMessage(LdapOperation type, IRfcRequest op, LdapControl[] controls = null) {
|
||||
// Get a unique number for this request message
|
||||
this._messageType = type;
|
||||
RfcControls asn1Ctrls = null;
|
||||
|
||||
if(controls != null) {
|
||||
// Move LdapControls into an RFC 2251 Controls object.
|
||||
asn1Ctrls = new RfcControls();
|
||||
|
||||
foreach(LdapControl t in controls) {
|
||||
asn1Ctrls.Add(t.Asn1Object);
|
||||
}
|
||||
}
|
||||
|
||||
// create RFC 2251 LdapMessage
|
||||
this.Message = new RfcLdapMessage(op, asn1Ctrls);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapMessage"/> class.
|
||||
/// Creates an Rfc 2251 LdapMessage when the libraries receive a response
|
||||
/// from a command.
|
||||
/// </summary>
|
||||
/// <param name="message">A response message.</param>
|
||||
internal LdapMessage(RfcLdapMessage message) => this.Message = message;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the message ID. The message ID is an integer value
|
||||
/// identifying the Ldap request and its response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message identifier.
|
||||
/// </value>
|
||||
public virtual Int32 MessageId {
|
||||
get {
|
||||
if(this._imsgNum == -1) {
|
||||
this._imsgNum = this.Message.MessageId;
|
||||
}
|
||||
|
||||
return this._imsgNum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the message is a request or a response.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if request; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public virtual Boolean Request => this.Message.IsRequest();
|
||||
|
||||
internal LdapOperation Type {
|
||||
get {
|
||||
if(this._messageType == LdapOperation.Unknown) {
|
||||
this._messageType = this.Message.Type;
|
||||
}
|
||||
|
||||
return this._messageType;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual RfcLdapMessage Asn1Object => this.Message;
|
||||
|
||||
internal virtual LdapMessage RequestingMessage => this.Message.RequestingMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the identifier tag for this message.
|
||||
/// An identifier can be associated with a message with the
|
||||
/// <c>setTag</c> method.
|
||||
/// Tags are set by the application and not by the API or the server.
|
||||
/// If a server response <c>isRequest() == false</c> has no tag,
|
||||
/// the tag associated with the corresponding server request is used.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tag.
|
||||
/// </value>
|
||||
public virtual String Tag {
|
||||
get => this._stringTag ?? (this.Request ? null : this.RequestingMessage?._stringTag);
|
||||
|
||||
set => this._stringTag = value;
|
||||
}
|
||||
|
||||
private String Name => this.Type.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override String ToString() => $"{this.Name}({this.MessageId}): {this.Message}";
|
||||
}
|
||||
}
|
||||
@@ -1,78 +1,79 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// A single add, delete, or replace operation to an LdapAttribute.
|
||||
/// An LdapModification contains information on the type of modification
|
||||
/// being performed, the name of the attribute to be replaced, and the new
|
||||
/// value. Multiple modifications are expressed as an array of modifications,
|
||||
/// i.e., <c>LdapModification[]</c>.
|
||||
/// An LdapModification or an LdapModification array enable you to modify
|
||||
/// an attribute of an Ldap entry. The entire array of modifications must
|
||||
/// be performed by the server as a single atomic operation in the order they
|
||||
/// are listed. No changes are made to the directory unless all the operations
|
||||
/// succeed. If all succeed, a success result is returned to the application.
|
||||
/// It should be noted that if the connection fails during a modification,
|
||||
/// it is indeterminate whether the modification occurred or not.
|
||||
/// There are three types of modification operations: Add, Delete,
|
||||
/// and Replace.
|
||||
/// <b>Add: </b>Creates the attribute if it doesn't exist, and adds
|
||||
/// the specified values. This operation must contain at least one value, and
|
||||
/// all values of the attribute must be unique.
|
||||
/// <b>Delete: </b>Deletes specified values from the attribute. If no
|
||||
/// values are specified, or if all existing values of the attribute are
|
||||
/// specified, the attribute is removed. Mandatory attributes cannot be
|
||||
/// removed.
|
||||
/// <b>Replace: </b>Creates the attribute if necessary, and replaces
|
||||
/// all existing values of the attribute with the specified values.
|
||||
/// If you wish to keep any existing values of a multi-valued attribute,
|
||||
/// you must include these values in the replace operation.
|
||||
/// A replace operation with no value will remove the entire attribute if it
|
||||
/// exists, and is ignored if the attribute does not exist.
|
||||
/// Additional information on Ldap modifications is available in section 4.6
|
||||
/// of. <a href="http://www.ietf.org/rfc/rfc2251.txt">rfc2251.txt</a>
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Modify"></seealso>
|
||||
/// <seealso cref="LdapAttribute"></seealso>
|
||||
public sealed class LdapModification : LdapMessage {
|
||||
/// <summary>
|
||||
/// A single add, delete, or replace operation to an LdapAttribute.
|
||||
/// An LdapModification contains information on the type of modification
|
||||
/// being performed, the name of the attribute to be replaced, and the new
|
||||
/// value. Multiple modifications are expressed as an array of modifications,
|
||||
/// i.e., <c>LdapModification[]</c>.
|
||||
/// An LdapModification or an LdapModification array enable you to modify
|
||||
/// an attribute of an Ldap entry. The entire array of modifications must
|
||||
/// be performed by the server as a single atomic operation in the order they
|
||||
/// are listed. No changes are made to the directory unless all the operations
|
||||
/// succeed. If all succeed, a success result is returned to the application.
|
||||
/// It should be noted that if the connection fails during a modification,
|
||||
/// it is indeterminate whether the modification occurred or not.
|
||||
/// There are three types of modification operations: Add, Delete,
|
||||
/// and Replace.
|
||||
/// <b>Add: </b>Creates the attribute if it doesn't exist, and adds
|
||||
/// the specified values. This operation must contain at least one value, and
|
||||
/// all values of the attribute must be unique.
|
||||
/// <b>Delete: </b>Deletes specified values from the attribute. If no
|
||||
/// values are specified, or if all existing values of the attribute are
|
||||
/// specified, the attribute is removed. Mandatory attributes cannot be
|
||||
/// removed.
|
||||
/// <b>Replace: </b>Creates the attribute if necessary, and replaces
|
||||
/// all existing values of the attribute with the specified values.
|
||||
/// If you wish to keep any existing values of a multi-valued attribute,
|
||||
/// you must include these values in the replace operation.
|
||||
/// A replace operation with no value will remove the entire attribute if it
|
||||
/// exists, and is ignored if the attribute does not exist.
|
||||
/// Additional information on Ldap modifications is available in section 4.6
|
||||
/// of. <a href="http://www.ietf.org/rfc/rfc2251.txt">rfc2251.txt</a>
|
||||
/// Initializes a new instance of the <see cref="LdapModification" /> class.
|
||||
/// Specifies a modification to be made to an attribute.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Modify"></seealso>
|
||||
/// <seealso cref="LdapAttribute"></seealso>
|
||||
public sealed class LdapModification : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification" /> class.
|
||||
/// Specifies a modification to be made to an attribute.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attr">The attribute to modify.</param>
|
||||
public LdapModification(LdapModificationOp op, LdapAttribute attr)
|
||||
{
|
||||
Op = op;
|
||||
Attribute = attr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification"/> class.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attrName">Name of the attribute.</param>
|
||||
/// <param name="attrValue">The attribute value.</param>
|
||||
public LdapModification(LdapModificationOp op, string attrName, string attrValue)
|
||||
: this(op, new LdapAttribute(attrName, attrValue))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the attribute to modify, with any existing values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attribute.
|
||||
/// </value>
|
||||
public LdapAttribute Attribute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of modification specified by this object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The op.
|
||||
/// </value>
|
||||
public LdapModificationOp Op { get; }
|
||||
}
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attr">The attribute to modify.</param>
|
||||
public LdapModification(LdapModificationOp op, LdapAttribute attr) {
|
||||
this.Op = op;
|
||||
this.Attribute = attr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModification"/> class.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="attrName">Name of the attribute.</param>
|
||||
/// <param name="attrValue">The attribute value.</param>
|
||||
public LdapModification(LdapModificationOp op, String attrName, String attrValue)
|
||||
: this(op, new LdapAttribute(attrName, attrValue)) {
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the attribute to modify, with any existing values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attribute.
|
||||
/// </value>
|
||||
public LdapAttribute Attribute {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the type of modification specified by this object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The op.
|
||||
/// </value>
|
||||
public LdapModificationOp Op {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,60 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents a LDAP Modification Request Message.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public sealed class LdapModifyRequest : LdapMessage {
|
||||
/// <summary>
|
||||
/// Represents a LDAP Modification Request Message.
|
||||
/// Initializes a new instance of the <see cref="LdapModifyRequest"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
public sealed class LdapModifyRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapModifyRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="modifications">The modifications.</param>
|
||||
/// <param name="control">The control.</param>
|
||||
public LdapModifyRequest(string dn, LdapModification[] modifications, LdapControl[] control)
|
||||
: base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dn.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dn.
|
||||
/// </value>
|
||||
public string DN => Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Asn1Object.ToString();
|
||||
|
||||
private static Asn1SequenceOf EncodeModifications(LdapModification[] mods)
|
||||
{
|
||||
var rfcMods = new Asn1SequenceOf(mods.Length);
|
||||
|
||||
foreach (var t in mods)
|
||||
{
|
||||
var attr = t.Attribute;
|
||||
|
||||
var vals = new Asn1SetOf(attr.Size());
|
||||
if (attr.Size() > 0)
|
||||
{
|
||||
foreach (var val in attr.ByteValueArray)
|
||||
{
|
||||
vals.Add(new Asn1OctetString(val));
|
||||
}
|
||||
}
|
||||
|
||||
var rfcMod = new Asn1Sequence(2);
|
||||
rfcMod.Add(new Asn1Enumerated((int) t.Op));
|
||||
rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals));
|
||||
|
||||
rfcMods.Add(rfcMod);
|
||||
}
|
||||
|
||||
return rfcMods;
|
||||
}
|
||||
|
||||
internal class RfcAttributeTypeAndValues : Asn1Sequence
|
||||
{
|
||||
public RfcAttributeTypeAndValues(string type, Asn1Object vals)
|
||||
: base(2)
|
||||
{
|
||||
Add(type);
|
||||
Add(vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="modifications">The modifications.</param>
|
||||
/// <param name="control">The control.</param>
|
||||
public LdapModifyRequest(String dn, LdapModification[] modifications, LdapControl[] control)
|
||||
: base(LdapOperation.ModifyRequest, new RfcModifyRequest(dn, EncodeModifications(modifications)), control) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dn.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dn.
|
||||
/// </value>
|
||||
public String DN => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString() => this.Asn1Object.ToString();
|
||||
|
||||
private static Asn1SequenceOf EncodeModifications(LdapModification[] mods) {
|
||||
Asn1SequenceOf rfcMods = new Asn1SequenceOf(mods.Length);
|
||||
|
||||
foreach(LdapModification t in mods) {
|
||||
LdapAttribute attr = t.Attribute;
|
||||
|
||||
Asn1SetOf vals = new Asn1SetOf(attr.Size());
|
||||
if(attr.Size() > 0) {
|
||||
foreach(SByte[] val in attr.ByteValueArray) {
|
||||
vals.Add(new Asn1OctetString(val));
|
||||
}
|
||||
}
|
||||
|
||||
Asn1Sequence rfcMod = new Asn1Sequence(2);
|
||||
rfcMod.Add(new Asn1Enumerated((Int32)t.Op));
|
||||
rfcMod.Add(new RfcAttributeTypeAndValues(attr.Name, vals));
|
||||
|
||||
rfcMods.Add(rfcMod);
|
||||
}
|
||||
|
||||
return rfcMods;
|
||||
}
|
||||
|
||||
internal class RfcAttributeTypeAndValues : Asn1Sequence {
|
||||
public RfcAttributeTypeAndValues(String type, Asn1Object vals)
|
||||
: base(2) {
|
||||
this.Add(type);
|
||||
this.Add(vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +1,114 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// LDAP Operation.
|
||||
/// </summary>
|
||||
internal enum LdapOperation {
|
||||
/// <summary>
|
||||
/// LDAP Operation.
|
||||
/// The unknown
|
||||
/// </summary>
|
||||
internal enum LdapOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// The unknown
|
||||
/// </summary>
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// A bind request operation.
|
||||
/// BIND_REQUEST = 0
|
||||
/// </summary>
|
||||
BindRequest = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A bind response operation.
|
||||
/// BIND_RESPONSE = 1
|
||||
/// </summary>
|
||||
BindResponse = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An unbind request operation.
|
||||
/// UNBIND_REQUEST = 2
|
||||
/// </summary>
|
||||
UnbindRequest = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A search request operation.
|
||||
/// SEARCH_REQUEST = 3
|
||||
/// </summary>
|
||||
SearchRequest = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A search response containing data.
|
||||
/// SEARCH_RESPONSE = 4
|
||||
/// </summary>
|
||||
SearchResponse = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A search result message - contains search status.
|
||||
/// SEARCH_RESULT = 5
|
||||
/// </summary>
|
||||
SearchResult = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A modify request operation.
|
||||
/// MODIFY_REQUEST = 6
|
||||
/// </summary>
|
||||
ModifyRequest = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A modify response operation.
|
||||
/// MODIFY_RESPONSE = 7
|
||||
/// </summary>
|
||||
ModifyResponse = 7,
|
||||
|
||||
/// <summary>
|
||||
/// An abandon request operation.
|
||||
/// ABANDON_REQUEST = 16
|
||||
/// </summary>
|
||||
AbandonRequest = 16,
|
||||
|
||||
/// <summary>
|
||||
/// A search result reference operation.
|
||||
/// SEARCH_RESULT_REFERENCE = 19
|
||||
/// </summary>
|
||||
SearchResultReference = 19,
|
||||
|
||||
/// <summary>
|
||||
/// An extended request operation.
|
||||
/// EXTENDED_REQUEST = 23
|
||||
/// </summary>
|
||||
ExtendedRequest = 23,
|
||||
|
||||
/// <summary>
|
||||
/// An extended response operation.
|
||||
/// EXTENDED_RESPONSE = 24
|
||||
/// </summary>
|
||||
ExtendedResponse = 24,
|
||||
|
||||
/// <summary>
|
||||
/// An intermediate response operation.
|
||||
/// INTERMEDIATE_RESPONSE = 25
|
||||
/// </summary>
|
||||
IntermediateResponse = 25,
|
||||
}
|
||||
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// ASN1 tags.
|
||||
/// A bind request operation.
|
||||
/// BIND_REQUEST = 0
|
||||
/// </summary>
|
||||
internal enum Asn1IdentifierTag
|
||||
{
|
||||
/// <summary>
|
||||
/// Universal tag class.
|
||||
/// </summary>
|
||||
Universal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Application-wide tag class.
|
||||
/// </summary>
|
||||
Application = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Context-specific tag class.
|
||||
/// </summary>
|
||||
Context = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Private-use tag class.
|
||||
/// </summary>
|
||||
Private = 3,
|
||||
}
|
||||
BindRequest = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A bind response operation.
|
||||
/// BIND_RESPONSE = 1
|
||||
/// </summary>
|
||||
BindResponse = 1,
|
||||
|
||||
/// <summary>
|
||||
/// An unbind request operation.
|
||||
/// UNBIND_REQUEST = 2
|
||||
/// </summary>
|
||||
UnbindRequest = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A search request operation.
|
||||
/// SEARCH_REQUEST = 3
|
||||
/// </summary>
|
||||
SearchRequest = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A search response containing data.
|
||||
/// SEARCH_RESPONSE = 4
|
||||
/// </summary>
|
||||
SearchResponse = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A search result message - contains search status.
|
||||
/// SEARCH_RESULT = 5
|
||||
/// </summary>
|
||||
SearchResult = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A modify request operation.
|
||||
/// MODIFY_REQUEST = 6
|
||||
/// </summary>
|
||||
ModifyRequest = 6,
|
||||
|
||||
/// <summary>
|
||||
/// A modify response operation.
|
||||
/// MODIFY_RESPONSE = 7
|
||||
/// </summary>
|
||||
ModifyResponse = 7,
|
||||
|
||||
/// <summary>
|
||||
/// An abandon request operation.
|
||||
/// ABANDON_REQUEST = 16
|
||||
/// </summary>
|
||||
AbandonRequest = 16,
|
||||
|
||||
/// <summary>
|
||||
/// A search result reference operation.
|
||||
/// SEARCH_RESULT_REFERENCE = 19
|
||||
/// </summary>
|
||||
SearchResultReference = 19,
|
||||
|
||||
/// <summary>
|
||||
/// An extended request operation.
|
||||
/// EXTENDED_REQUEST = 23
|
||||
/// </summary>
|
||||
ExtendedRequest = 23,
|
||||
|
||||
/// <summary>
|
||||
/// An extended response operation.
|
||||
/// EXTENDED_RESPONSE = 24
|
||||
/// </summary>
|
||||
ExtendedResponse = 24,
|
||||
|
||||
/// <summary>
|
||||
/// An intermediate response operation.
|
||||
/// INTERMEDIATE_RESPONSE = 25
|
||||
/// </summary>
|
||||
IntermediateResponse = 25,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ASN1 tags.
|
||||
/// </summary>
|
||||
internal enum Asn1IdentifierTag {
|
||||
/// <summary>
|
||||
/// Universal tag class.
|
||||
/// </summary>
|
||||
Universal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Application-wide tag class.
|
||||
/// </summary>
|
||||
Application = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Context-specific tag class.
|
||||
/// </summary>
|
||||
Context = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Private-use tag class.
|
||||
/// </summary>
|
||||
Private = 3,
|
||||
}
|
||||
}
|
||||
@@ -1,185 +1,180 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search request.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapMessage" />
|
||||
internal sealed class LdapSearchRequest : LdapMessage {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search request.
|
||||
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapMessage" />
|
||||
internal sealed class LdapSearchRequest : LdapMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchRequest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ldapBase">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search. The following
|
||||
/// are the valid options:.
|
||||
/// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li>
|
||||
/// SCOPE_SUB - searches the base DN and all entries
|
||||
/// within its subtree
|
||||
/// </li></ul></param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.
|
||||
/// operation exceeds the time limit.</param>
|
||||
/// <param name="dereference">Specifies when aliases should be dereferenced.
|
||||
/// Must be one of the constants defined in
|
||||
/// LdapConstraints, which are DEREF_NEVER,
|
||||
/// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param>
|
||||
/// <param name="maxResults">The maximum number of search results to return
|
||||
/// for a search request.
|
||||
/// The search operation will be terminated by the server
|
||||
/// with an LdapException.SIZE_LIMIT_EXCEEDED if the
|
||||
/// number of results exceed the maximum.</param>
|
||||
/// <param name="serverTimeLimit">The maximum time in seconds that the server
|
||||
/// should spend returning search results. This is a
|
||||
/// server-enforced limit. A value of 0 means
|
||||
/// no time limit.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="cont">Any controls that apply to the search request.
|
||||
/// or null if none.</param>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public LdapSearchRequest(
|
||||
string ldapBase,
|
||||
LdapScope scope,
|
||||
string filter,
|
||||
string[] attrs,
|
||||
int dereference,
|
||||
int maxResults,
|
||||
int serverTimeLimit,
|
||||
bool typesOnly,
|
||||
LdapControl[] cont)
|
||||
: base(
|
||||
LdapOperation.SearchRequest,
|
||||
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
|
||||
cont)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an Iterator object representing the parsed filter for
|
||||
/// this search request.
|
||||
/// The first object returned from the Iterator is an Integer indicating
|
||||
/// the type of filter component. One or more values follow the component
|
||||
/// type as subsequent items in the Iterator. The pattern of Integer
|
||||
/// component type followed by values continues until the end of the
|
||||
/// filter.
|
||||
/// Values returned as a byte array may represent UTF-8 characters or may
|
||||
/// be binary values. The possible Integer components of a search filter
|
||||
/// and the associated values that follow are:.
|
||||
/// <ul><li>AND - followed by an Iterator value</li><li>OR - followed by an Iterator value</li><li>NOT - followed by an Iterator value</li><li>
|
||||
/// EQUALITY_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// GREATER_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// LESS_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// APPROX_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>PRESENT - followed by a attribute name respresented as a String</li><li>
|
||||
/// EXTENSIBLE_MATCH - followed by the name of the matching rule
|
||||
/// represented as a String, by the attribute name represented
|
||||
/// as a String, and by the attribute value represented as a
|
||||
/// byte array.
|
||||
/// </li><li>
|
||||
/// SUBSTRINGS - followed by the attribute name represented as a
|
||||
/// String, by one or more SUBSTRING components (INITIAL, ANY,
|
||||
/// or FINAL) followed by the SUBSTRING value.
|
||||
/// </li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The search filter.
|
||||
/// </value>
|
||||
public IEnumerator SearchFilter => RfcFilter.GetFilterIterator();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Base DN for a search request.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the base DN for a search request.
|
||||
/// </returns>
|
||||
public string DN => Asn1Object.RequestDn;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the scope of a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The scope.
|
||||
/// </value>
|
||||
public int Scope => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(1)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the behaviour of dereferencing aliases on a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dereference.
|
||||
/// </value>
|
||||
public int Dereference => ((Asn1Enumerated)((RfcSearchRequest)Asn1Object.Get(1)).Get(2)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the maximum number of entries to be returned on a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The maximum results.
|
||||
/// </value>
|
||||
public int MaxResults => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(3)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the server time limit for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The server time limit.
|
||||
/// </value>
|
||||
public int ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)Asn1Object.Get(1)).Get(4)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves whether attribute values or only attribute types(names) should
|
||||
/// be returned in a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [types only]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool TypesOnly => ((Asn1Boolean)((RfcSearchRequest)Asn1Object.Get(1)).Get(5)).BooleanValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of attribute names to request for in a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attributes.
|
||||
/// </value>
|
||||
public string[] Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
var attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)Asn1Object.Get(1)).Get(7);
|
||||
var values = new string[attrs.Size()];
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the filter in this search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The string filter.
|
||||
/// </value>
|
||||
public string StringFilter => RfcFilter.FilterToString();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an SearchFilter object representing a filter for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The RFC filter.
|
||||
/// </value>
|
||||
private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)Asn1Object.Get(1)).Get(6);
|
||||
}
|
||||
/// <param name="ldapBase">The base distinguished name to search from.</param>
|
||||
/// <param name="scope">The scope of the entries to search. The following
|
||||
/// are the valid options:.
|
||||
/// <ul><li>SCOPE_BASE - searches only the base DN</li><li>SCOPE_ONE - searches only entries under the base DN</li><li>
|
||||
/// SCOPE_SUB - searches the base DN and all entries
|
||||
/// within its subtree
|
||||
/// </li></ul></param>
|
||||
/// <param name="filter">The search filter specifying the search criteria.</param>
|
||||
/// <param name="attrs">The names of attributes to retrieve.
|
||||
/// operation exceeds the time limit.</param>
|
||||
/// <param name="dereference">Specifies when aliases should be dereferenced.
|
||||
/// Must be one of the constants defined in
|
||||
/// LdapConstraints, which are DEREF_NEVER,
|
||||
/// DEREF_FINDING, DEREF_SEARCHING, or DEREF_ALWAYS.</param>
|
||||
/// <param name="maxResults">The maximum number of search results to return
|
||||
/// for a search request.
|
||||
/// The search operation will be terminated by the server
|
||||
/// with an LdapException.SIZE_LIMIT_EXCEEDED if the
|
||||
/// number of results exceed the maximum.</param>
|
||||
/// <param name="serverTimeLimit">The maximum time in seconds that the server
|
||||
/// should spend returning search results. This is a
|
||||
/// server-enforced limit. A value of 0 means
|
||||
/// no time limit.</param>
|
||||
/// <param name="typesOnly">If true, returns the names but not the values of
|
||||
/// the attributes found. If false, returns the
|
||||
/// names and values for attributes found.</param>
|
||||
/// <param name="cont">Any controls that apply to the search request.
|
||||
/// or null if none.</param>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public LdapSearchRequest(
|
||||
String ldapBase,
|
||||
LdapScope scope,
|
||||
String filter,
|
||||
String[] attrs,
|
||||
Int32 dereference,
|
||||
Int32 maxResults,
|
||||
Int32 serverTimeLimit,
|
||||
Boolean typesOnly,
|
||||
LdapControl[] cont)
|
||||
: base(
|
||||
LdapOperation.SearchRequest,
|
||||
new RfcSearchRequest(ldapBase, scope, dereference, maxResults, serverTimeLimit, typesOnly, filter, attrs),
|
||||
cont) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an Iterator object representing the parsed filter for
|
||||
/// this search request.
|
||||
/// The first object returned from the Iterator is an Integer indicating
|
||||
/// the type of filter component. One or more values follow the component
|
||||
/// type as subsequent items in the Iterator. The pattern of Integer
|
||||
/// component type followed by values continues until the end of the
|
||||
/// filter.
|
||||
/// Values returned as a byte array may represent UTF-8 characters or may
|
||||
/// be binary values. The possible Integer components of a search filter
|
||||
/// and the associated values that follow are:.
|
||||
/// <ul><li>AND - followed by an Iterator value</li><li>OR - followed by an Iterator value</li><li>NOT - followed by an Iterator value</li><li>
|
||||
/// EQUALITY_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// GREATER_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// LESS_OR_EQUAL - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>
|
||||
/// APPROX_MATCH - followed by the attribute name represented as a
|
||||
/// String, and by the attribute value represented as a byte array
|
||||
/// </li><li>PRESENT - followed by a attribute name respresented as a String</li><li>
|
||||
/// EXTENSIBLE_MATCH - followed by the name of the matching rule
|
||||
/// represented as a String, by the attribute name represented
|
||||
/// as a String, and by the attribute value represented as a
|
||||
/// byte array.
|
||||
/// </li><li>
|
||||
/// SUBSTRINGS - followed by the attribute name represented as a
|
||||
/// String, by one or more SUBSTRING components (INITIAL, ANY,
|
||||
/// or FINAL) followed by the SUBSTRING value.
|
||||
/// </li></ul>
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The search filter.
|
||||
/// </value>
|
||||
public IEnumerator SearchFilter => this.RfcFilter.GetFilterIterator();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the Base DN for a search request.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// the base DN for a search request.
|
||||
/// </returns>
|
||||
public String DN => this.Asn1Object.RequestDn;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the scope of a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The scope.
|
||||
/// </value>
|
||||
public Int32 Scope => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(1)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the behaviour of dereferencing aliases on a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dereference.
|
||||
/// </value>
|
||||
public Int32 Dereference => ((Asn1Enumerated)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(2)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the maximum number of entries to be returned on a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The maximum results.
|
||||
/// </value>
|
||||
public Int32 MaxResults => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(3)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the server time limit for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The server time limit.
|
||||
/// </value>
|
||||
public Int32 ServerTimeLimit => ((Asn1Integer)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(4)).IntValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves whether attribute values or only attribute types(names) should
|
||||
/// be returned in a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [types only]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public Boolean TypesOnly => ((Asn1Boolean)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(5)).BooleanValue();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an array of attribute names to request for in a search.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The attributes.
|
||||
/// </value>
|
||||
public String[] Attributes {
|
||||
get {
|
||||
RfcAttributeDescriptionList attrs = (RfcAttributeDescriptionList)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(7);
|
||||
String[] values = new String[attrs.Size()];
|
||||
for(Int32 i = 0; i < values.Length; i++) {
|
||||
values[i] = ((Asn1OctetString)attrs.Get(i)).StringValue();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the filter in this search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The string filter.
|
||||
/// </value>
|
||||
public String StringFilter => this.RfcFilter.FilterToString();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves an SearchFilter object representing a filter for a search request.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The RFC filter.
|
||||
/// </value>
|
||||
private RfcFilter RfcFilter => (RfcFilter)((RfcSearchRequest)this.Asn1Object.Get(1)).Get(6);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +1,87 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// An LdapSearchResults object is returned from a synchronous search
|
||||
/// operation. It provides access to all results received during the
|
||||
/// operation (entries and exceptions).
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public sealed class LdapSearchResults {
|
||||
private readonly List<RfcLdapMessage> _messages;
|
||||
private readonly Int32 _messageId;
|
||||
|
||||
/// <summary>
|
||||
/// An LdapSearchResults object is returned from a synchronous search
|
||||
/// operation. It provides access to all results received during the
|
||||
/// operation (entries and exceptions).
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="LdapConnection.Search"></seealso>
|
||||
public sealed class LdapSearchResults
|
||||
{
|
||||
private readonly List<RfcLdapMessage> _messages;
|
||||
private readonly int _messageId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LdapSearchResults" /> class.
|
||||
/// </summary>
|
||||
/// <param name="messages">The messages.</param>
|
||||
/// <param name="messageId">The message identifier.</param>
|
||||
internal LdapSearchResults(List<RfcLdapMessage> messages, int messageId)
|
||||
{
|
||||
_messages = messages;
|
||||
_messageId = messageId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a count of the items in the search result.
|
||||
/// Returns a count of the entries and exceptions remaining in the object.
|
||||
/// If the search was submitted with a batch size greater than zero,
|
||||
/// getCount reports the number of results received so far but not enumerated
|
||||
/// with next(). If batch size equals zero, getCount reports the number of
|
||||
/// items received, since the application thread blocks until all results are
|
||||
/// received.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public int Count => new List<RfcLdapMessage>(_messages)
|
||||
.Count(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Reports if there are more search results.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if there are more search results.
|
||||
/// </returns>
|
||||
public bool HasMore() => new List<RfcLdapMessage>(_messages)
|
||||
.Any(x => x.MessageId == _messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next result as an LdapEntry.
|
||||
/// If automatic referral following is disabled or if a referral
|
||||
/// was not followed, next() will throw an LdapReferralException
|
||||
/// when the referral is received.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The next search result as an LdapEntry.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
|
||||
public LdapEntry Next()
|
||||
{
|
||||
var list = new List<RfcLdapMessage>(_messages)
|
||||
.Where(x => x.MessageId == _messageId);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
_messages.Remove(item);
|
||||
var response = GetResponse(item);
|
||||
|
||||
if (response is LdapSearchResult result)
|
||||
{
|
||||
return result.Entry;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(Next), "No more results");
|
||||
}
|
||||
|
||||
private static LdapMessage GetResponse(RfcLdapMessage item)
|
||||
{
|
||||
switch (item.Type)
|
||||
{
|
||||
case LdapOperation.SearchResponse:
|
||||
return new LdapSearchResult(item);
|
||||
case LdapOperation.SearchResultReference:
|
||||
return new LdapSearchResultReference(item);
|
||||
default:
|
||||
return new LdapResponse(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <param name="messages">The messages.</param>
|
||||
/// <param name="messageId">The message identifier.</param>
|
||||
internal LdapSearchResults(List<RfcLdapMessage> messages, Int32 messageId) {
|
||||
this._messages = messages;
|
||||
this._messageId = messageId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a count of the items in the search result.
|
||||
/// Returns a count of the entries and exceptions remaining in the object.
|
||||
/// If the search was submitted with a batch size greater than zero,
|
||||
/// getCount reports the number of results received so far but not enumerated
|
||||
/// with next(). If batch size equals zero, getCount reports the number of
|
||||
/// items received, since the application thread blocks until all results are
|
||||
/// received.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public Int32 Count => new List<RfcLdapMessage>(this._messages)
|
||||
.Count(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Reports if there are more search results.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if there are more search results.
|
||||
/// </returns>
|
||||
public Boolean HasMore() => new List<RfcLdapMessage>(this._messages)
|
||||
.Any(x => x.MessageId == this._messageId && GetResponse(x) is LdapSearchResult);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next result as an LdapEntry.
|
||||
/// If automatic referral following is disabled or if a referral
|
||||
/// was not followed, next() will throw an LdapReferralException
|
||||
/// when the referral is received.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The next search result as an LdapEntry.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Next - No more results.</exception>
|
||||
public LdapEntry Next() {
|
||||
IEnumerable<RfcLdapMessage> list = new List<RfcLdapMessage>(this._messages)
|
||||
.Where(x => x.MessageId == this._messageId);
|
||||
|
||||
foreach(RfcLdapMessage item in list) {
|
||||
_ = this._messages.Remove(item);
|
||||
LdapMessage response = GetResponse(item);
|
||||
|
||||
if(response is LdapSearchResult result) {
|
||||
return result.Entry;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(Next), "No more results");
|
||||
}
|
||||
|
||||
private static LdapMessage GetResponse(RfcLdapMessage item) {
|
||||
switch(item.Type) {
|
||||
case LdapOperation.SearchResponse:
|
||||
return new LdapSearchResult(item);
|
||||
case LdapOperation.SearchResultReference:
|
||||
return new LdapSearchResultReference(item);
|
||||
default:
|
||||
return new LdapResponse(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,304 +1,292 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// The class performs token processing from strings.
|
||||
/// </summary>
|
||||
internal class Tokenizer {
|
||||
// The tokenizer uses the default delimiter set: the space character, the tab character, the newline character, and the carriage-return character
|
||||
private readonly String _delimiters = " \t\n\r";
|
||||
|
||||
private readonly Boolean _returnDelims;
|
||||
|
||||
private List<String> _elements;
|
||||
|
||||
private String _source;
|
||||
|
||||
/// <summary>
|
||||
/// The class performs token processing from strings.
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// Initializes a new class instance with a specified string to process
|
||||
/// and the specified token delimiters to use.
|
||||
/// </summary>
|
||||
internal class Tokenizer
|
||||
{
|
||||
// The tokenizer uses the default delimiter set: the space character, the tab character, the newline character, and the carriage-return character
|
||||
private readonly string _delimiters = " \t\n\r";
|
||||
|
||||
private readonly bool _returnDelims;
|
||||
|
||||
private List<string> _elements;
|
||||
|
||||
private string _source;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// Initializes a new class instance with a specified string to process
|
||||
/// and the specified token delimiters to use.
|
||||
/// </summary>
|
||||
/// <param name="source">String to tokenize.</param>
|
||||
/// <param name="delimiters">String containing the delimiters.</param>
|
||||
/// <param name="retDel">if set to <c>true</c> [ret delete].</param>
|
||||
public Tokenizer(string source, string delimiters, bool retDel = false)
|
||||
{
|
||||
_elements = new List<string>();
|
||||
_delimiters = delimiters ?? _delimiters;
|
||||
_source = source;
|
||||
_returnDelims = retDel;
|
||||
if (_returnDelims)
|
||||
Tokenize();
|
||||
else
|
||||
_elements.AddRange(source.Split(_delimiters.ToCharArray()));
|
||||
RemoveEmptyStrings();
|
||||
}
|
||||
|
||||
public int Count => _elements.Count;
|
||||
|
||||
public bool HasMoreTokens() => _elements.Count > 0;
|
||||
|
||||
public string NextToken()
|
||||
{
|
||||
if (_source == string.Empty) throw new InvalidOperationException();
|
||||
|
||||
string result;
|
||||
if (_returnDelims)
|
||||
{
|
||||
RemoveEmptyStrings();
|
||||
result = _elements[0];
|
||||
_elements.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
_elements = new List<string>();
|
||||
_elements.AddRange(_source.Split(_delimiters.ToCharArray()));
|
||||
RemoveEmptyStrings();
|
||||
result = _elements[0];
|
||||
_elements.RemoveAt(0);
|
||||
_source = _source.Remove(_source.IndexOf(result, StringComparison.Ordinal), result.Length);
|
||||
_source = _source.TrimStart(_delimiters.ToCharArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RemoveEmptyStrings()
|
||||
{
|
||||
for (var index = 0; index < _elements.Count; index++)
|
||||
{
|
||||
if (_elements[index] != string.Empty) continue;
|
||||
|
||||
_elements.RemoveAt(index);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
private void Tokenize()
|
||||
{
|
||||
var tempstr = _source;
|
||||
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length > 0)
|
||||
{
|
||||
_elements.Add(tempstr);
|
||||
}
|
||||
else if (tempstr.IndexOfAny(_delimiters.ToCharArray()) < 0 && tempstr.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (tempstr.IndexOfAny(_delimiters.ToCharArray()) >= 0)
|
||||
{
|
||||
if (tempstr.IndexOfAny(_delimiters.ToCharArray()) == 0)
|
||||
{
|
||||
if (tempstr.Length > 1)
|
||||
{
|
||||
_elements.Add(tempstr.Substring(0, 1));
|
||||
tempstr = tempstr.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempstr = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var toks = tempstr.Substring(0, tempstr.IndexOfAny(_delimiters.ToCharArray()));
|
||||
_elements.Add(toks);
|
||||
_elements.Add(tempstr.Substring(toks.Length, 1));
|
||||
|
||||
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (tempstr.Length > 0)
|
||||
{
|
||||
_elements.Add(tempstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="source">String to tokenize.</param>
|
||||
/// <param name="delimiters">String containing the delimiters.</param>
|
||||
/// <param name="retDel">if set to <c>true</c> [ret delete].</param>
|
||||
public Tokenizer(String source, String delimiters, Boolean retDel = false) {
|
||||
this._elements = new List<String>();
|
||||
this._delimiters = delimiters ?? this._delimiters;
|
||||
this._source = source;
|
||||
this._returnDelims = retDel;
|
||||
if(this._returnDelims) {
|
||||
this.Tokenize();
|
||||
} else {
|
||||
this._elements.AddRange(source.Split(this._delimiters.ToCharArray()));
|
||||
}
|
||||
|
||||
this.RemoveEmptyStrings();
|
||||
}
|
||||
|
||||
public Int32 Count => this._elements.Count;
|
||||
|
||||
public Boolean HasMoreTokens() => this._elements.Count > 0;
|
||||
|
||||
public String NextToken() {
|
||||
if(this._source == String.Empty) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
String result;
|
||||
if(this._returnDelims) {
|
||||
this.RemoveEmptyStrings();
|
||||
result = this._elements[0];
|
||||
this._elements.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
this._elements = new List<String>();
|
||||
this._elements.AddRange(this._source.Split(this._delimiters.ToCharArray()));
|
||||
this.RemoveEmptyStrings();
|
||||
result = this._elements[0];
|
||||
this._elements.RemoveAt(0);
|
||||
this._source = this._source.Remove(this._source.IndexOf(result, StringComparison.Ordinal), result.Length);
|
||||
this._source = this._source.TrimStart(this._delimiters.ToCharArray());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void RemoveEmptyStrings() {
|
||||
for(Int32 index = 0; index < this._elements.Count; index++) {
|
||||
if(this._elements[index] != String.Empty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._elements.RemoveAt(index);
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
private void Tokenize() {
|
||||
String tempstr = this._source;
|
||||
if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length > 0) {
|
||||
this._elements.Add(tempstr);
|
||||
} else if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) < 0 && tempstr.Length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while(tempstr.IndexOfAny(this._delimiters.ToCharArray()) >= 0) {
|
||||
if(tempstr.IndexOfAny(this._delimiters.ToCharArray()) == 0) {
|
||||
if(tempstr.Length > 1) {
|
||||
this._elements.Add(tempstr.Substring(0, 1));
|
||||
tempstr = tempstr.Substring(1);
|
||||
} else {
|
||||
tempstr = String.Empty;
|
||||
}
|
||||
} else {
|
||||
String toks = tempstr.Substring(0, tempstr.IndexOfAny(this._delimiters.ToCharArray()));
|
||||
this._elements.Add(toks);
|
||||
this._elements.Add(tempstr.Substring(toks.Length, 1));
|
||||
|
||||
tempstr = tempstr.Length > toks.Length + 1 ? tempstr.Substring(toks.Length + 1) : String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if(tempstr.Length > 0) {
|
||||
this._elements.Add(tempstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Matching Rule Assertion.
|
||||
/// <pre>
|
||||
/// MatchingRuleAssertion ::= SEQUENCE {
|
||||
/// matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
/// type [2] AttributeDescription OPTIONAL,
|
||||
/// matchValue [3] AssertionValue,
|
||||
/// dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcMatchingRuleAssertion : Asn1Sequence {
|
||||
public RfcMatchingRuleAssertion(
|
||||
String matchingRule,
|
||||
String type,
|
||||
SByte[] matchValue,
|
||||
Asn1Boolean dnAttributes = null)
|
||||
: base(4) {
|
||||
if(matchingRule != null) {
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false));
|
||||
}
|
||||
|
||||
if(type != null) {
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(2), new Asn1OctetString(type), false));
|
||||
}
|
||||
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(3), new Asn1OctetString(matchValue), false));
|
||||
|
||||
// if dnAttributes if false, that is the default value and we must not
|
||||
// encode it. (See RFC 2251 5.1 number 4)
|
||||
if(dnAttributes != null && dnAttributes.BooleanValue()) {
|
||||
this.Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AttributeDescriptionList is used to list attributes to be returned in
|
||||
/// a search request.
|
||||
/// <pre>
|
||||
/// AttributeDescriptionList ::= SEQUENCE OF
|
||||
/// AttributeDescription
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcAttributeDescriptionList : Asn1SequenceOf {
|
||||
public RfcAttributeDescriptionList(String[] attrs)
|
||||
: base(attrs?.Length ?? 0) {
|
||||
if(attrs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(String attr in attrs) {
|
||||
this.Add(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Request.
|
||||
/// <pre>
|
||||
/// SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
||||
/// baseObject LdapDN,
|
||||
/// scope ENUMERATED {
|
||||
/// baseObject (0),
|
||||
/// singleLevel (1),
|
||||
/// wholeSubtree (2) },
|
||||
/// derefAliases ENUMERATED {
|
||||
/// neverDerefAliases (0),
|
||||
/// derefInSearching (1),
|
||||
/// derefFindingBaseObj (2),
|
||||
/// derefAlways (3) },
|
||||
/// sizeLimit INTEGER (0 .. maxInt),
|
||||
/// timeLimit INTEGER (0 .. maxInt),
|
||||
/// typesOnly BOOLEAN,
|
||||
/// filter Filter,
|
||||
/// attributes AttributeDescriptionList }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal class RfcSearchRequest : Asn1Sequence, IRfcRequest {
|
||||
public RfcSearchRequest(
|
||||
String basePath,
|
||||
LdapScope scope,
|
||||
Int32 derefAliases,
|
||||
Int32 sizeLimit,
|
||||
Int32 timeLimit,
|
||||
Boolean typesOnly,
|
||||
String filter,
|
||||
String[] attributes)
|
||||
: base(8) {
|
||||
this.Add(basePath);
|
||||
this.Add(new Asn1Enumerated(scope));
|
||||
this.Add(new Asn1Enumerated(derefAliases));
|
||||
this.Add(new Asn1Integer(sizeLimit));
|
||||
this.Add(new Asn1Integer(timeLimit));
|
||||
this.Add(new Asn1Boolean(typesOnly));
|
||||
this.Add(new RfcFilter(filter));
|
||||
this.Add(new RfcAttributeDescriptionList(attributes));
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest);
|
||||
|
||||
public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Substring Filter.
|
||||
/// <pre>
|
||||
/// SubstringFilter ::= SEQUENCE {
|
||||
/// type AttributeDescription,
|
||||
/// -- at least one must be present
|
||||
/// substrings SEQUENCE OF CHOICE {
|
||||
/// initial [0] LdapString,
|
||||
/// any [1] LdapString,
|
||||
/// final [2] LdapString } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSubstringFilter : Asn1Sequence {
|
||||
public RfcSubstringFilter(String type, Asn1Object substrings)
|
||||
: base(2) {
|
||||
this.Add(type);
|
||||
this.Add(substrings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Attribute Value Assertion.
|
||||
/// <pre>
|
||||
/// AttributeValueAssertion ::= SEQUENCE {
|
||||
/// attributeDesc AttributeDescription,
|
||||
/// assertionValue AssertionValue }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcAttributeValueAssertion : Asn1Sequence {
|
||||
public RfcAttributeValueAssertion(String ad, SByte[] av)
|
||||
: base(2) {
|
||||
this.Add(ad);
|
||||
this.Add(new Asn1OctetString(av));
|
||||
}
|
||||
|
||||
public String AttributeDescription => ((Asn1OctetString)this.Get(0)).StringValue();
|
||||
|
||||
public SByte[] AssertionValue => ((Asn1OctetString)this.Get(1)).ByteValue();
|
||||
}
|
||||
|
||||
/// <summary> Encapsulates an Ldap Bind properties.</summary>
|
||||
internal class BindProperties {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Matching Rule Assertion.
|
||||
/// <pre>
|
||||
/// MatchingRuleAssertion ::= SEQUENCE {
|
||||
/// matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
/// type [2] AttributeDescription OPTIONAL,
|
||||
/// matchValue [3] AssertionValue,
|
||||
/// dnAttributes [4] BOOLEAN DEFAULT FALSE }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcMatchingRuleAssertion : Asn1Sequence
|
||||
{
|
||||
public RfcMatchingRuleAssertion(
|
||||
string matchingRule,
|
||||
string type,
|
||||
sbyte[] matchValue,
|
||||
Asn1Boolean dnAttributes = null)
|
||||
: base(4)
|
||||
{
|
||||
if (matchingRule != null)
|
||||
Add(new Asn1Tagged(new Asn1Identifier(1), new Asn1OctetString(matchingRule), false));
|
||||
if (type != null)
|
||||
Add(new Asn1Tagged(new Asn1Identifier(2), new Asn1OctetString(type), false));
|
||||
|
||||
Add(new Asn1Tagged(new Asn1Identifier(3), new Asn1OctetString(matchValue), false));
|
||||
|
||||
// if dnAttributes if false, that is the default value and we must not
|
||||
// encode it. (See RFC 2251 5.1 number 4)
|
||||
if (dnAttributes != null && dnAttributes.BooleanValue())
|
||||
Add(new Asn1Tagged(new Asn1Identifier(4), dnAttributes, false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AttributeDescriptionList is used to list attributes to be returned in
|
||||
/// a search request.
|
||||
/// <pre>
|
||||
/// AttributeDescriptionList ::= SEQUENCE OF
|
||||
/// AttributeDescription
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcAttributeDescriptionList : Asn1SequenceOf
|
||||
{
|
||||
public RfcAttributeDescriptionList(string[] attrs)
|
||||
: base(attrs?.Length ?? 0)
|
||||
{
|
||||
if (attrs == null) return;
|
||||
|
||||
foreach (var attr in attrs)
|
||||
{
|
||||
Add(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Request.
|
||||
/// <pre>
|
||||
/// SearchRequest ::= [APPLICATION 3] SEQUENCE {
|
||||
/// baseObject LdapDN,
|
||||
/// scope ENUMERATED {
|
||||
/// baseObject (0),
|
||||
/// singleLevel (1),
|
||||
/// wholeSubtree (2) },
|
||||
/// derefAliases ENUMERATED {
|
||||
/// neverDerefAliases (0),
|
||||
/// derefInSearching (1),
|
||||
/// derefFindingBaseObj (2),
|
||||
/// derefAlways (3) },
|
||||
/// sizeLimit INTEGER (0 .. maxInt),
|
||||
/// timeLimit INTEGER (0 .. maxInt),
|
||||
/// typesOnly BOOLEAN,
|
||||
/// filter Filter,
|
||||
/// attributes AttributeDescriptionList }
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="BindProperties" /> class.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal class RfcSearchRequest : Asn1Sequence, IRfcRequest
|
||||
{
|
||||
public RfcSearchRequest(
|
||||
string basePath,
|
||||
LdapScope scope,
|
||||
int derefAliases,
|
||||
int sizeLimit,
|
||||
int timeLimit,
|
||||
bool typesOnly,
|
||||
string filter,
|
||||
string[] attributes)
|
||||
: base(8)
|
||||
{
|
||||
Add(basePath);
|
||||
Add(new Asn1Enumerated(scope));
|
||||
Add(new Asn1Enumerated(derefAliases));
|
||||
Add(new Asn1Integer(sizeLimit));
|
||||
Add(new Asn1Integer(timeLimit));
|
||||
Add(new Asn1Boolean(typesOnly));
|
||||
Add(new RfcFilter(filter));
|
||||
Add(new RfcAttributeDescriptionList(attributes));
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchRequest);
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString) Get(0)).StringValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Substring Filter.
|
||||
/// <pre>
|
||||
/// SubstringFilter ::= SEQUENCE {
|
||||
/// type AttributeDescription,
|
||||
/// -- at least one must be present
|
||||
/// substrings SEQUENCE OF CHOICE {
|
||||
/// initial [0] LdapString,
|
||||
/// any [1] LdapString,
|
||||
/// final [2] LdapString } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSubstringFilter : Asn1Sequence
|
||||
{
|
||||
public RfcSubstringFilter(string type, Asn1Object substrings)
|
||||
: base(2)
|
||||
{
|
||||
Add(type);
|
||||
Add(substrings);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Attribute Value Assertion.
|
||||
/// <pre>
|
||||
/// AttributeValueAssertion ::= SEQUENCE {
|
||||
/// attributeDesc AttributeDescription,
|
||||
/// assertionValue AssertionValue }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcAttributeValueAssertion : Asn1Sequence
|
||||
{
|
||||
public RfcAttributeValueAssertion(string ad, sbyte[] av)
|
||||
: base(2)
|
||||
{
|
||||
Add(ad);
|
||||
Add(new Asn1OctetString(av));
|
||||
}
|
||||
|
||||
public string AttributeDescription => ((Asn1OctetString) Get(0)).StringValue();
|
||||
|
||||
public sbyte[] AssertionValue => ((Asn1OctetString) Get(1)).ByteValue();
|
||||
}
|
||||
|
||||
/// <summary> Encapsulates an Ldap Bind properties.</summary>
|
||||
internal class BindProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BindProperties" /> class.
|
||||
/// </summary>
|
||||
/// <param name="version">The version.</param>
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <param name="anonymous">if set to <c>true</c> [anonymous].</param>
|
||||
public BindProperties(
|
||||
int version,
|
||||
string dn,
|
||||
string method,
|
||||
bool anonymous)
|
||||
{
|
||||
ProtocolVersion = version;
|
||||
AuthenticationDN = dn;
|
||||
AuthenticationMethod = method;
|
||||
Anonymous = anonymous;
|
||||
}
|
||||
|
||||
public int ProtocolVersion { get; }
|
||||
|
||||
public string AuthenticationDN { get; }
|
||||
|
||||
public string AuthenticationMethod { get; }
|
||||
|
||||
public bool Anonymous { get; }
|
||||
}
|
||||
/// <param name="version">The version.</param>
|
||||
/// <param name="dn">The dn.</param>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <param name="anonymous">if set to <c>true</c> [anonymous].</param>
|
||||
public BindProperties(
|
||||
Int32 version,
|
||||
String dn,
|
||||
String method,
|
||||
Boolean anonymous) {
|
||||
this.ProtocolVersion = version;
|
||||
this.AuthenticationDN = dn;
|
||||
this.AuthenticationMethod = method;
|
||||
this.Anonymous = anonymous;
|
||||
}
|
||||
|
||||
public Int32 ProtocolVersion {
|
||||
get;
|
||||
}
|
||||
|
||||
public String AuthenticationDN {
|
||||
get;
|
||||
}
|
||||
|
||||
public String AuthenticationMethod {
|
||||
get;
|
||||
}
|
||||
|
||||
public Boolean Anonymous {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +1,118 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Control.
|
||||
/// <pre>
|
||||
/// Control ::= SEQUENCE {
|
||||
/// controlType LdapOID,
|
||||
/// criticality BOOLEAN DEFAULT FALSE,
|
||||
/// controlValue OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcControl : Asn1Sequence {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Control.
|
||||
/// <pre>
|
||||
/// Control ::= SEQUENCE {
|
||||
/// controlType LdapOID,
|
||||
/// criticality BOOLEAN DEFAULT FALSE,
|
||||
/// controlValue OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcControl"/> class.
|
||||
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
|
||||
/// (4): If a value of a type is its default value, it MUST be
|
||||
/// absent.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcControl : Asn1Sequence
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcControl"/> class.
|
||||
/// Note: criticality is only added if true, as per RFC 2251 sec 5.1 part
|
||||
/// (4): If a value of a type is its default value, it MUST be
|
||||
/// absent.
|
||||
/// </summary>
|
||||
/// <param name="controlType">Type of the control.</param>
|
||||
/// <param name="criticality">The criticality.</param>
|
||||
/// <param name="controlValue">The control value.</param>
|
||||
public RfcControl(string controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null)
|
||||
: base(3)
|
||||
{
|
||||
Add(controlType);
|
||||
Add(criticality ?? new Asn1Boolean(false));
|
||||
|
||||
if (controlValue != null)
|
||||
Add(controlValue);
|
||||
}
|
||||
|
||||
public RfcControl(Asn1Structured seqObj)
|
||||
: base(3)
|
||||
{
|
||||
for (var i = 0; i < seqObj.Size(); i++)
|
||||
Add(seqObj.Get(i));
|
||||
}
|
||||
|
||||
public Asn1OctetString ControlType => (Asn1OctetString)Get(0);
|
||||
|
||||
public Asn1Boolean Criticality => Size() > 1 && Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
|
||||
|
||||
public Asn1OctetString ControlValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Size() > 2)
|
||||
{
|
||||
// MUST be a control value
|
||||
return (Asn1OctetString)Get(2);
|
||||
}
|
||||
|
||||
return Size() > 1 && Get(1) is Asn1OctetString s ? s : null;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
if (Size() == 3)
|
||||
{
|
||||
// We already have a control value, replace it
|
||||
Set(2, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Size() == 2)
|
||||
{
|
||||
// Get the second element
|
||||
var obj = Get(1);
|
||||
|
||||
// Is this a control value
|
||||
if (obj is Asn1OctetString)
|
||||
{
|
||||
// replace this one
|
||||
Set(1, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new one at the end
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Sasl Credentials.
|
||||
/// <pre>
|
||||
/// SaslCredentials ::= SEQUENCE {
|
||||
/// mechanism LdapString,
|
||||
/// credentials OCTET STRING OPTIONAL }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSaslCredentials : Asn1Sequence
|
||||
{
|
||||
public RfcSaslCredentials(string mechanism, sbyte[] credentials = null)
|
||||
: base(2)
|
||||
{
|
||||
Add(mechanism);
|
||||
if (credentials != null)
|
||||
Add(new Asn1OctetString(credentials));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Authentication Choice.
|
||||
/// <pre>
|
||||
/// AuthenticationChoice ::= CHOICE {
|
||||
/// simple [0] OCTET STRING,
|
||||
/// -- 1 and 2 reserved
|
||||
/// sasl [3] SaslCredentials }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" />
|
||||
internal class RfcAuthenticationChoice : Asn1Choice
|
||||
{
|
||||
public RfcAuthenticationChoice(sbyte[] passwd)
|
||||
: base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false))
|
||||
{
|
||||
}
|
||||
|
||||
public RfcAuthenticationChoice(string mechanism, sbyte[] credentials)
|
||||
: base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false))
|
||||
{
|
||||
// implicit tagging
|
||||
}
|
||||
}
|
||||
/// <param name="controlType">Type of the control.</param>
|
||||
/// <param name="criticality">The criticality.</param>
|
||||
/// <param name="controlValue">The control value.</param>
|
||||
public RfcControl(String controlType, Asn1Boolean criticality = null, Asn1Object controlValue = null)
|
||||
: base(3) {
|
||||
this.Add(controlType);
|
||||
this.Add(criticality ?? new Asn1Boolean(false));
|
||||
|
||||
if(controlValue != null) {
|
||||
this.Add(controlValue);
|
||||
}
|
||||
}
|
||||
|
||||
public RfcControl(Asn1Structured seqObj)
|
||||
: base(3) {
|
||||
for(Int32 i = 0; i < seqObj.Size(); i++) {
|
||||
this.Add(seqObj.Get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1OctetString ControlType => (Asn1OctetString)this.Get(0);
|
||||
|
||||
public Asn1Boolean Criticality => this.Size() > 1 && this.Get(1) is Asn1Boolean boolean ? boolean : new Asn1Boolean(false);
|
||||
|
||||
public Asn1OctetString ControlValue {
|
||||
get {
|
||||
if(this.Size() > 2) {
|
||||
// MUST be a control value
|
||||
return (Asn1OctetString)this.Get(2);
|
||||
}
|
||||
|
||||
return this.Size() > 1 && this.Get(1) is Asn1OctetString s ? s : null;
|
||||
}
|
||||
set {
|
||||
if(value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.Size() == 3) {
|
||||
// We already have a control value, replace it
|
||||
this.Set(2, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.Size() == 2) {
|
||||
// Get the second element
|
||||
Asn1Object obj = this.Get(1);
|
||||
|
||||
// Is this a control value
|
||||
if(obj is Asn1OctetString) {
|
||||
// replace this one
|
||||
this.Set(1, value);
|
||||
} else {
|
||||
// add a new one at the end
|
||||
this.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Sasl Credentials.
|
||||
/// <pre>
|
||||
/// SaslCredentials ::= SEQUENCE {
|
||||
/// mechanism LdapString,
|
||||
/// credentials OCTET STRING OPTIONAL }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal class RfcSaslCredentials : Asn1Sequence {
|
||||
public RfcSaslCredentials(String mechanism, SByte[] credentials = null)
|
||||
: base(2) {
|
||||
this.Add(mechanism);
|
||||
if(credentials != null) {
|
||||
this.Add(new Asn1OctetString(credentials));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Authentication Choice.
|
||||
/// <pre>
|
||||
/// AuthenticationChoice ::= CHOICE {
|
||||
/// simple [0] OCTET STRING,
|
||||
/// -- 1 and 2 reserved
|
||||
/// sasl [3] SaslCredentials }
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Choice" />
|
||||
internal class RfcAuthenticationChoice : Asn1Choice {
|
||||
public RfcAuthenticationChoice(SByte[] passwd)
|
||||
: base(new Asn1Tagged(new Asn1Identifier(0), new Asn1OctetString(passwd), false)) {
|
||||
}
|
||||
|
||||
public RfcAuthenticationChoice(String mechanism, SByte[] credentials)
|
||||
: base(new Asn1Tagged(new Asn1Identifier(3, true), new RfcSaslCredentials(mechanism, credentials), false)) {
|
||||
// implicit tagging
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,246 +1,238 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Encapsulates a single search result that is in response to an asynchronous
|
||||
/// search operation.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
internal class LdapSearchResult : LdapMessage {
|
||||
private LdapEntry _entry;
|
||||
|
||||
internal LdapSearchResult(RfcLdapMessage message)
|
||||
: base(message) {
|
||||
}
|
||||
|
||||
public LdapEntry Entry {
|
||||
get {
|
||||
if(this._entry != null) {
|
||||
return this._entry;
|
||||
}
|
||||
|
||||
LdapAttributeSet attrs = new LdapAttributeSet();
|
||||
RfcSearchResultEntry entry = (RfcSearchResultEntry)this.Message.Response;
|
||||
|
||||
foreach(Asn1Object o in entry.Attributes.ToArray()) {
|
||||
Asn1Sequence seq = (Asn1Sequence)o;
|
||||
LdapAttribute attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
|
||||
Asn1Set set = (Asn1Set)seq.Get(1);
|
||||
|
||||
foreach(Asn1Object t in set.ToArray()) {
|
||||
attr.AddValue(((Asn1OctetString)t).ByteValue());
|
||||
}
|
||||
|
||||
_ = attrs.Add(attr);
|
||||
}
|
||||
|
||||
this._entry = new LdapEntry(entry.ObjectName, attrs);
|
||||
|
||||
return this._entry;
|
||||
}
|
||||
}
|
||||
|
||||
public override String ToString() => this._entry?.ToString() ?? base.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Reference.
|
||||
/// <pre>
|
||||
/// SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LdapURL
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcSearchResultReference : Asn1SequenceOf {
|
||||
/// <summary>
|
||||
/// Encapsulates a single search result that is in response to an asynchronous
|
||||
/// search operation.
|
||||
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
|
||||
/// The only time a client will create a SearchResultReference is when it is
|
||||
/// decoding it from an Stream.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.LdapMessage" />
|
||||
internal class LdapSearchResult : LdapMessage
|
||||
{
|
||||
private LdapEntry _entry;
|
||||
|
||||
internal LdapSearchResult(RfcLdapMessage message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public LdapEntry Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_entry != null) return _entry;
|
||||
|
||||
var attrs = new LdapAttributeSet();
|
||||
var entry = (RfcSearchResultEntry) Message.Response;
|
||||
|
||||
foreach (var o in entry.Attributes.ToArray())
|
||||
{
|
||||
var seq = (Asn1Sequence) o;
|
||||
var attr = new LdapAttribute(((Asn1OctetString)seq.Get(0)).StringValue());
|
||||
var set = (Asn1Set)seq.Get(1);
|
||||
|
||||
foreach (var t in set.ToArray())
|
||||
{
|
||||
attr.AddValue(((Asn1OctetString)t).ByteValue());
|
||||
}
|
||||
|
||||
attrs.Add(attr);
|
||||
}
|
||||
|
||||
_entry = new LdapEntry(entry.ObjectName, attrs);
|
||||
|
||||
return _entry;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => _entry?.ToString() ?? base.ToString();
|
||||
}
|
||||
|
||||
/// <param name="stream">The streab.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcSearchResultReference(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Extended Response.
|
||||
/// <pre>
|
||||
/// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// responseName [10] LdapOID OPTIONAL,
|
||||
/// response [11] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse {
|
||||
public const Int32 ResponseNameCode = 10;
|
||||
|
||||
public const Int32 ResponseCode = 11;
|
||||
|
||||
private readonly Int32 _referralIndex;
|
||||
private readonly Int32 _responseNameIndex;
|
||||
private readonly Int32 _responseIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Reference.
|
||||
/// <pre>
|
||||
/// SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LdapURL
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class.
|
||||
/// The only time a client will create a ExtendedResponse is when it is
|
||||
/// decoding it from an stream.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcSearchResultReference : Asn1SequenceOf
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcSearchResultReference"/> class.
|
||||
/// The only time a client will create a SearchResultReference is when it is
|
||||
/// decoding it from an Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The streab.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcSearchResultReference(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResultReference);
|
||||
}
|
||||
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcExtendedResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
if(this.Size() <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(Int32 i = 3; i < this.Size(); i++) {
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(i);
|
||||
Asn1Identifier id = obj.GetIdentifier();
|
||||
|
||||
switch(id.Tag) {
|
||||
case RfcLdapResult.Referral:
|
||||
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
|
||||
|
||||
using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
|
||||
this.Set(i, new Asn1SequenceOf(bais, content.Length));
|
||||
}
|
||||
|
||||
this._referralIndex = i;
|
||||
break;
|
||||
case ResponseNameCode:
|
||||
this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
|
||||
this._responseNameIndex = i;
|
||||
break;
|
||||
case ResponseCode:
|
||||
this.Set(i, obj.TaggedValue);
|
||||
this._responseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1OctetString ResponseName => this._responseNameIndex != 0 ? (Asn1OctetString)this.Get(this._responseNameIndex) : null;
|
||||
|
||||
public Asn1OctetString Response => this._responseIndex != 0 ? (Asn1OctetString)this.Get(this._responseIndex) : null;
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral()
|
||||
=> this._referralIndex != 0 ? (Asn1SequenceOf)this.Get(this._referralIndex) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents and Ldap Bind Response.
|
||||
/// <pre>
|
||||
/// BindResponse ::= [APPLICATION 1] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// serverSaslCreds [7] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcBindResponse : Asn1Sequence, IRfcResponse {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Extended Response.
|
||||
/// <pre>
|
||||
/// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// responseName [10] LdapOID OPTIONAL,
|
||||
/// response [11] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcBindResponse"/> class.
|
||||
/// The only time a client will create a BindResponse is when it is
|
||||
/// decoding it from an InputStream
|
||||
/// Note: If serverSaslCreds is included in the BindResponse, it does not
|
||||
/// need to be decoded since it is already an OCTET STRING.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcExtendedResponse : Asn1Sequence, IRfcResponse
|
||||
{
|
||||
public const int ResponseNameCode = 10;
|
||||
|
||||
public const int ResponseCode = 11;
|
||||
|
||||
private readonly int _referralIndex;
|
||||
private readonly int _responseNameIndex;
|
||||
private readonly int _responseIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcExtendedResponse"/> class.
|
||||
/// The only time a client will create a ExtendedResponse is when it is
|
||||
/// decoding it from an stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcExtendedResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
if (Size() <= 3) return;
|
||||
|
||||
for (var i = 3; i < Size(); i++)
|
||||
{
|
||||
var obj = (Asn1Tagged) Get(i);
|
||||
var id = obj.GetIdentifier();
|
||||
|
||||
switch (id.Tag)
|
||||
{
|
||||
case RfcLdapResult.Referral:
|
||||
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
|
||||
|
||||
using (var bais = new MemoryStream(content.ToByteArray()))
|
||||
Set(i, new Asn1SequenceOf(bais, content.Length));
|
||||
|
||||
_referralIndex = i;
|
||||
break;
|
||||
case ResponseNameCode:
|
||||
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue()));
|
||||
_responseNameIndex = i;
|
||||
break;
|
||||
case ResponseCode:
|
||||
Set(i, obj.TaggedValue);
|
||||
_responseIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1OctetString ResponseName => _responseNameIndex != 0 ? (Asn1OctetString) Get(_responseNameIndex) : null;
|
||||
|
||||
public Asn1OctetString Response => _responseIndex != 0 ? (Asn1OctetString) Get(_responseIndex) : null;
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral()
|
||||
=> _referralIndex != 0 ? (Asn1SequenceOf) Get(_referralIndex) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ExtendedResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents and Ldap Bind Response.
|
||||
/// <pre>
|
||||
/// BindResponse ::= [APPLICATION 1] SEQUENCE {
|
||||
/// COMPONENTS OF LdapResult,
|
||||
/// serverSaslCreds [7] OCTET STRING OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcBindResponse : Asn1Sequence, IRfcResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcBindResponse"/> class.
|
||||
/// The only time a client will create a BindResponse is when it is
|
||||
/// decoding it from an InputStream
|
||||
/// Note: If serverSaslCreds is included in the BindResponse, it does not
|
||||
/// need to be decoded since it is already an OCTET STRING.
|
||||
/// </summary>
|
||||
/// <param name="stream">The in renamed.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcBindResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
// Decode optional referral from Asn1OctetString to Referral.
|
||||
if (Size() <= 3) return;
|
||||
|
||||
var obj = (Asn1Tagged) Get(3);
|
||||
|
||||
if (obj.GetIdentifier().Tag != RfcLdapResult.Referral) return;
|
||||
|
||||
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
|
||||
|
||||
using (var bais = new MemoryStream(content.ToByteArray()))
|
||||
Set(3, new Asn1SequenceOf(bais, content.Length));
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral() => Size() > 3 && Get(3) is Asn1SequenceOf ? (Asn1SequenceOf) Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LDAP Intermediate Response.
|
||||
/// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
|
||||
/// COMPONENTS OF LDAPResult, note: only present on incorrectly
|
||||
/// encoded response from pre Falcon-sp1 server
|
||||
/// responseName [10] LDAPOID OPTIONAL,
|
||||
/// responseValue [11] OCTET STRING OPTIONAL }.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse
|
||||
{
|
||||
public const int TagResponseName = 0;
|
||||
public const int TagResponse = 1;
|
||||
|
||||
public RfcIntermediateResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
var i = Size() >= 3 ? 3 : 0;
|
||||
|
||||
for (; i < Size(); i++)
|
||||
{
|
||||
var obj = (Asn1Tagged) Get(i);
|
||||
|
||||
switch (obj.GetIdentifier().Tag)
|
||||
{
|
||||
case TagResponseName:
|
||||
Set(i, new Asn1OctetString(((Asn1OctetString) obj.TaggedValue).ByteValue()));
|
||||
break;
|
||||
case TagResponse:
|
||||
Set(i, obj.TaggedValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => Size() > 3 ? (Asn1Enumerated) Get(0) : null;
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue()) : null;
|
||||
|
||||
public Asn1OctetString GetErrorMessage() =>
|
||||
Size() > 3 ? new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue()) : null;
|
||||
|
||||
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
|
||||
}
|
||||
/// <param name="stream">The in renamed.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
public RfcBindResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
// Decode optional referral from Asn1OctetString to Referral.
|
||||
if(this.Size() <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(3);
|
||||
|
||||
if(obj.GetIdentifier().Tag != RfcLdapResult.Referral) {
|
||||
return;
|
||||
}
|
||||
|
||||
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
|
||||
|
||||
using(MemoryStream bais = new MemoryStream(content.ToByteArray())) {
|
||||
this.Set(3, new Asn1SequenceOf(bais, content.Length));
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral() => this.Size() > 3 && this.Get(3) is Asn1SequenceOf ? (Asn1SequenceOf)this.Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.BindResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LDAP Intermediate Response.
|
||||
/// IntermediateResponse ::= [APPLICATION 25] SEQUENCE {
|
||||
/// COMPONENTS OF LDAPResult, note: only present on incorrectly
|
||||
/// encoded response from pre Falcon-sp1 server
|
||||
/// responseName [10] LDAPOID OPTIONAL,
|
||||
/// responseValue [11] OCTET STRING OPTIONAL }.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcIntermediateResponse : Asn1Sequence, IRfcResponse {
|
||||
public const Int32 TagResponseName = 0;
|
||||
public const Int32 TagResponse = 1;
|
||||
|
||||
public RfcIntermediateResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
Int32 i = this.Size() >= 3 ? 3 : 0;
|
||||
|
||||
for(; i < this.Size(); i++) {
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(i);
|
||||
|
||||
switch(obj.GetIdentifier().Tag) {
|
||||
case TagResponseName:
|
||||
this.Set(i, new Asn1OctetString(((Asn1OctetString)obj.TaggedValue).ByteValue()));
|
||||
break;
|
||||
case TagResponse:
|
||||
this.Set(i, obj.TaggedValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => this.Size() > 3 ? (Asn1Enumerated)this.Get(0) : null;
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue()) : null;
|
||||
|
||||
public Asn1OctetString GetErrorMessage() =>
|
||||
this.Size() > 3 ? new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue()) : null;
|
||||
|
||||
public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.IntermediateResponse);
|
||||
}
|
||||
}
|
||||
@@ -1,390 +1,379 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Message.
|
||||
/// <pre>
|
||||
/// LdapMessage ::= SEQUENCE {
|
||||
/// messageID MessageID,
|
||||
/// protocolOp CHOICE {
|
||||
/// bindRequest BindRequest,
|
||||
/// bindResponse BindResponse,
|
||||
/// unbindRequest UnbindRequest,
|
||||
/// searchRequest SearchRequest,
|
||||
/// searchResEntry SearchResultEntry,
|
||||
/// searchResDone SearchResultDone,
|
||||
/// searchResRef SearchResultReference,
|
||||
/// modifyRequest ModifyRequest,
|
||||
/// modifyResponse ModifyResponse,
|
||||
/// addRequest AddRequest,
|
||||
/// addResponse AddResponse,
|
||||
/// delRequest DelRequest,
|
||||
/// delResponse DelResponse,
|
||||
/// modDNRequest ModifyDNRequest,
|
||||
/// modDNResponse ModifyDNResponse,
|
||||
/// compareRequest CompareRequest,
|
||||
/// compareResponse CompareResponse,
|
||||
/// abandonRequest AbandonRequest,
|
||||
/// extendedReq ExtendedRequest,
|
||||
/// extendedResp ExtendedResponse },
|
||||
/// controls [0] Controls OPTIONAL }
|
||||
/// </pre>
|
||||
/// Note: The creation of a MessageID should be hidden within the creation of
|
||||
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
|
||||
/// upper and lower limit. There is never a case when a user should be
|
||||
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
|
||||
/// constructor should be package protected. (So the MessageID value
|
||||
/// isn't arbitrarily run up.).
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcLdapMessage : Asn1Sequence {
|
||||
private readonly Asn1Object _op;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Message.
|
||||
/// <pre>
|
||||
/// LdapMessage ::= SEQUENCE {
|
||||
/// messageID MessageID,
|
||||
/// protocolOp CHOICE {
|
||||
/// bindRequest BindRequest,
|
||||
/// bindResponse BindResponse,
|
||||
/// unbindRequest UnbindRequest,
|
||||
/// searchRequest SearchRequest,
|
||||
/// searchResEntry SearchResultEntry,
|
||||
/// searchResDone SearchResultDone,
|
||||
/// searchResRef SearchResultReference,
|
||||
/// modifyRequest ModifyRequest,
|
||||
/// modifyResponse ModifyResponse,
|
||||
/// addRequest AddRequest,
|
||||
/// addResponse AddResponse,
|
||||
/// delRequest DelRequest,
|
||||
/// delResponse DelResponse,
|
||||
/// modDNRequest ModifyDNRequest,
|
||||
/// modDNResponse ModifyDNResponse,
|
||||
/// compareRequest CompareRequest,
|
||||
/// compareResponse CompareResponse,
|
||||
/// abandonRequest AbandonRequest,
|
||||
/// extendedReq ExtendedRequest,
|
||||
/// extendedResp ExtendedResponse },
|
||||
/// controls [0] Controls OPTIONAL }
|
||||
/// </pre>
|
||||
/// Note: The creation of a MessageID should be hidden within the creation of
|
||||
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
|
||||
/// upper and lower limit. There is never a case when a user should be
|
||||
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
|
||||
/// constructor should be package protected. (So the MessageID value
|
||||
/// isn't arbitrarily run up.).
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Create an RfcLdapMessage request from input parameters.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcLdapMessage : Asn1Sequence
|
||||
{
|
||||
private readonly Asn1Object _op;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Create an RfcLdapMessage request from input parameters.
|
||||
/// </summary>
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="controls">The controls.</param>
|
||||
public RfcLdapMessage(IRfcRequest op, RfcControls controls)
|
||||
: base(3)
|
||||
{
|
||||
_op = (Asn1Object) op;
|
||||
|
||||
Add(new RfcMessageID()); // MessageID has static counter
|
||||
Add((Asn1Object) op);
|
||||
if (controls != null)
|
||||
{
|
||||
Add(controls);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Will decode an RfcLdapMessage directly from an InputStream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
|
||||
public RfcLdapMessage(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
// Decode implicitly tagged protocol operation from an Asn1Tagged type
|
||||
// to its appropriate application type.
|
||||
var protocolOp = (Asn1Tagged) Get(1);
|
||||
var protocolOpId = protocolOp.GetIdentifier();
|
||||
var content = ((Asn1OctetString) protocolOp.TaggedValue).ByteValue();
|
||||
var bais = new MemoryStream(content.ToByteArray());
|
||||
|
||||
switch ((LdapOperation) protocolOpId.Tag)
|
||||
{
|
||||
case LdapOperation.SearchResponse:
|
||||
Set(1, new RfcSearchResultEntry(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResult:
|
||||
Set(1, new RfcSearchResultDone(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResultReference:
|
||||
Set(1, new RfcSearchResultReference(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.BindResponse:
|
||||
Set(1, new RfcBindResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.ExtendedResponse:
|
||||
Set(1, new RfcExtendedResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.IntermediateResponse:
|
||||
Set(1, new RfcIntermediateResponse(bais, content.Length));
|
||||
break;
|
||||
case LdapOperation.ModifyResponse:
|
||||
Set(1, new RfcModifyResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"RfcLdapMessage: Invalid tag: {protocolOpId.Tag}");
|
||||
}
|
||||
|
||||
// decode optional implicitly tagged controls from Asn1Tagged type to
|
||||
// to RFC 2251 types.
|
||||
if (Size() <= 2) return;
|
||||
|
||||
var controls = (Asn1Tagged) Get(2);
|
||||
content = ((Asn1OctetString) controls.TaggedValue).ByteValue();
|
||||
|
||||
using (var ms = new MemoryStream(content.ToByteArray()))
|
||||
Set(2, new RfcControls(ms, content.Length));
|
||||
}
|
||||
|
||||
public int MessageId => ((Asn1Integer) Get(0)).IntValue();
|
||||
|
||||
/// <summary> Returns this RfcLdapMessage's message type.</summary>
|
||||
public LdapOperation Type => (LdapOperation) Get(1).GetIdentifier().Tag;
|
||||
|
||||
public Asn1Object Response => Get(1);
|
||||
|
||||
public string RequestDn => ((IRfcRequest) _op).GetRequestDN();
|
||||
|
||||
public LdapMessage RequestingMessage { get; set; }
|
||||
|
||||
public IRfcRequest GetRequest() => (IRfcRequest) Get(1);
|
||||
|
||||
public bool IsRequest() => Get(1) is IRfcRequest;
|
||||
}
|
||||
|
||||
/// <param name="op">The op.</param>
|
||||
/// <param name="controls">The controls.</param>
|
||||
public RfcLdapMessage(IRfcRequest op, RfcControls controls)
|
||||
: base(3) {
|
||||
this._op = (Asn1Object)op;
|
||||
|
||||
this.Add(new RfcMessageID()); // MessageID has static counter
|
||||
this.Add((Asn1Object)op);
|
||||
if(controls != null) {
|
||||
this.Add(controls);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Controls.
|
||||
/// <pre>
|
||||
/// Controls ::= SEQUENCE OF Control
|
||||
/// </pre>
|
||||
/// Initializes a new instance of the <see cref="RfcLdapMessage"/> class.
|
||||
/// Will decode an RfcLdapMessage directly from an InputStream.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcControls : Asn1SequenceOf
|
||||
{
|
||||
public const int Controls = 0;
|
||||
|
||||
public RfcControls()
|
||||
: base(5)
|
||||
{
|
||||
}
|
||||
|
||||
public RfcControls(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
// Convert each SEQUENCE element to a Control
|
||||
for (var i = 0; i < Size(); i++)
|
||||
{
|
||||
var tempControl = new RfcControl((Asn1Sequence) Get(i));
|
||||
Set(i, tempControl);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(RfcControl control) => base.Add(control);
|
||||
|
||||
public void Set(int index, RfcControl control) => base.Set(index, control);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
|
||||
}
|
||||
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <exception cref="Exception">RfcLdapMessage: Invalid tag: " + protocolOpId.Tag.</exception>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
|
||||
public RfcLdapMessage(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
// Decode implicitly tagged protocol operation from an Asn1Tagged type
|
||||
// to its appropriate application type.
|
||||
Asn1Tagged protocolOp = (Asn1Tagged)this.Get(1);
|
||||
Asn1Identifier protocolOpId = protocolOp.GetIdentifier();
|
||||
SByte[] content = ((Asn1OctetString)protocolOp.TaggedValue).ByteValue();
|
||||
MemoryStream bais = new MemoryStream(content.ToByteArray());
|
||||
|
||||
switch((LdapOperation)protocolOpId.Tag) {
|
||||
case LdapOperation.SearchResponse:
|
||||
this.Set(1, new RfcSearchResultEntry(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResult:
|
||||
this.Set(1, new RfcSearchResultDone(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.SearchResultReference:
|
||||
this.Set(1, new RfcSearchResultReference(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.BindResponse:
|
||||
this.Set(1, new RfcBindResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.ExtendedResponse:
|
||||
this.Set(1, new RfcExtendedResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
case LdapOperation.IntermediateResponse:
|
||||
this.Set(1, new RfcIntermediateResponse(bais, content.Length));
|
||||
break;
|
||||
case LdapOperation.ModifyResponse:
|
||||
this.Set(1, new RfcModifyResponse(bais, content.Length));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"RfcLdapMessage: Invalid tag: {protocolOpId.Tag}");
|
||||
}
|
||||
|
||||
// decode optional implicitly tagged controls from Asn1Tagged type to
|
||||
// to RFC 2251 types.
|
||||
if(this.Size() <= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Asn1Tagged controls = (Asn1Tagged)this.Get(2);
|
||||
content = ((Asn1OctetString)controls.TaggedValue).ByteValue();
|
||||
|
||||
using(MemoryStream ms = new MemoryStream(content.ToByteArray())) {
|
||||
this.Set(2, new RfcControls(ms, content.Length));
|
||||
}
|
||||
}
|
||||
|
||||
public Int32 MessageId => ((Asn1Integer)this.Get(0)).IntValue();
|
||||
|
||||
/// <summary> Returns this RfcLdapMessage's message type.</summary>
|
||||
public LdapOperation Type => (LdapOperation)this.Get(1).GetIdentifier().Tag;
|
||||
|
||||
public Asn1Object Response => this.Get(1);
|
||||
|
||||
public String RequestDn => ((IRfcRequest)this._op).GetRequestDN();
|
||||
|
||||
public LdapMessage RequestingMessage {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public IRfcRequest GetRequest() => (IRfcRequest)this.Get(1);
|
||||
|
||||
public Boolean IsRequest() => this.Get(1) is IRfcRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Ldap Controls.
|
||||
/// <pre>
|
||||
/// Controls ::= SEQUENCE OF Control
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1SequenceOf" />
|
||||
internal class RfcControls : Asn1SequenceOf {
|
||||
public const Int32 Controls = 0;
|
||||
|
||||
public RfcControls()
|
||||
: base(5) {
|
||||
}
|
||||
|
||||
public RfcControls(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
// Convert each SEQUENCE element to a Control
|
||||
for(Int32 i = 0; i < this.Size(); i++) {
|
||||
RfcControl tempControl = new RfcControl((Asn1Sequence)this.Get(i));
|
||||
this.Set(i, tempControl);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(RfcControl control) => base.Add(control);
|
||||
|
||||
public void Set(Int32 index, RfcControl control) => base.Set(index, control);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(Controls, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents RfcLdapMessages that contain a response from a
|
||||
/// server.
|
||||
/// If the protocol operation of the RfcLdapMessage is of this type,
|
||||
/// it contains at least an RfcLdapResult.
|
||||
/// </summary>
|
||||
internal interface IRfcResponse {
|
||||
/// <summary>
|
||||
/// This interface represents RfcLdapMessages that contain a response from a
|
||||
/// server.
|
||||
/// If the protocol operation of the RfcLdapMessage is of this type,
|
||||
/// it contains at least an RfcLdapResult.
|
||||
/// Gets the result code.
|
||||
/// </summary>
|
||||
internal interface IRfcResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the result code.
|
||||
/// </summary>
|
||||
/// <returns>Asn1Enumerated.</returns>
|
||||
Asn1Enumerated GetResultCode();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matched dn.
|
||||
/// </summary>
|
||||
/// <returns>RfcLdapDN.</returns>
|
||||
Asn1OctetString GetMatchedDN();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <returns>RfcLdapString.</returns>
|
||||
Asn1OctetString GetErrorMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the referral.
|
||||
/// </summary>
|
||||
/// <returns>Asn1SequenceOf.</returns>
|
||||
Asn1SequenceOf GetReferral();
|
||||
}
|
||||
|
||||
/// <returns>Asn1Enumerated.</returns>
|
||||
Asn1Enumerated GetResultCode();
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents Protocol Operations that are requests from a
|
||||
/// client.
|
||||
/// Gets the matched dn.
|
||||
/// </summary>
|
||||
internal interface IRfcRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a new request using the data from the this object.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.String" />.</returns>
|
||||
string GetRequestDN();
|
||||
}
|
||||
|
||||
/// <returns>RfcLdapDN.</returns>
|
||||
Asn1OctetString GetMatchedDN();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LdapResult.
|
||||
/// <pre>
|
||||
/// LdapResult ::= SEQUENCE {
|
||||
/// resultCode ENUMERATED {
|
||||
/// success (0),
|
||||
/// operationsError (1),
|
||||
/// protocolError (2),
|
||||
/// timeLimitExceeded (3),
|
||||
/// sizeLimitExceeded (4),
|
||||
/// compareFalse (5),
|
||||
/// compareTrue (6),
|
||||
/// authMethodNotSupported (7),
|
||||
/// strongAuthRequired (8),
|
||||
/// -- 9 reserved --
|
||||
/// referral (10), -- new
|
||||
/// adminLimitExceeded (11), -- new
|
||||
/// unavailableCriticalExtension (12), -- new
|
||||
/// confidentialityRequired (13), -- new
|
||||
/// saslBindInProgress (14), -- new
|
||||
/// noSuchAttribute (16),
|
||||
/// undefinedAttributeType (17),
|
||||
/// inappropriateMatching (18),
|
||||
/// constraintViolation (19),
|
||||
/// attributeOrValueExists (20),
|
||||
/// invalidAttributeSyntax (21),
|
||||
/// -- 22-31 unused --
|
||||
/// noSuchObject (32),
|
||||
/// aliasProblem (33),
|
||||
/// invalidDNSyntax (34),
|
||||
/// -- 35 reserved for undefined isLeaf --
|
||||
/// aliasDereferencingProblem (36),
|
||||
/// -- 37-47 unused --
|
||||
/// inappropriateAuthentication (48),
|
||||
/// invalidCredentials (49),
|
||||
/// insufficientAccessRights (50),
|
||||
/// busy (51),
|
||||
/// unavailable (52),
|
||||
/// unwillingToPerform (53),
|
||||
/// loopDetect (54),
|
||||
/// -- 55-63 unused --
|
||||
/// namingViolation (64),
|
||||
/// objectClassViolation (65),
|
||||
/// notAllowedOnNonLeaf (66),
|
||||
/// notAllowedOnRDN (67),
|
||||
/// entryAlreadyExists (68),
|
||||
/// objectClassModsProhibited (69),
|
||||
/// -- 70 reserved for CLdap --
|
||||
/// affectsMultipleDSAs (71), -- new
|
||||
/// -- 72-79 unused --
|
||||
/// other (80) },
|
||||
/// -- 81-90 reserved for APIs --
|
||||
/// matchedDN LdapDN,
|
||||
/// errorMessage LdapString,
|
||||
/// referral [3] Referral OPTIONAL }
|
||||
/// </pre>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcLdapResult : Asn1Sequence, IRfcResponse
|
||||
{
|
||||
public const int Referral = 3;
|
||||
|
||||
public RfcLdapResult(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
// Decode optional referral from Asn1OctetString to Referral.
|
||||
if (Size() <= 3) return;
|
||||
|
||||
var obj = (Asn1Tagged) Get(3);
|
||||
var id = obj.GetIdentifier();
|
||||
|
||||
if (id.Tag != Referral) return;
|
||||
|
||||
var content = ((Asn1OctetString) obj.TaggedValue).ByteValue();
|
||||
Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), content.Length));
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated) Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString) Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString) Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral() => Size() > 3 ? (Asn1SequenceOf) Get(3) : null;
|
||||
}
|
||||
|
||||
/// <returns>RfcLdapString.</returns>
|
||||
Asn1OctetString GetErrorMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Done Response.
|
||||
/// <pre>
|
||||
/// SearchResultDone ::= [APPLICATION 5] LdapResult
|
||||
/// </pre>
|
||||
/// Gets the referral.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" />
|
||||
internal class RfcSearchResultDone : RfcLdapResult
|
||||
{
|
||||
public RfcSearchResultDone(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult);
|
||||
}
|
||||
|
||||
/// <returns>Asn1SequenceOf.</returns>
|
||||
Asn1SequenceOf GetReferral();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface represents Protocol Operations that are requests from a
|
||||
/// client.
|
||||
/// </summary>
|
||||
internal interface IRfcRequest {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Entry.
|
||||
/// <pre>
|
||||
/// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
||||
/// objectName LdapDN,
|
||||
/// attributes PartialAttributeList }
|
||||
/// </pre>
|
||||
/// Builds a new request using the data from the this object.
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcSearchResultEntry : Asn1Sequence
|
||||
{
|
||||
public RfcSearchResultEntry(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public string ObjectName => ((Asn1OctetString) Get(0)).StringValue();
|
||||
|
||||
public Asn1Sequence Attributes => (Asn1Sequence) Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse);
|
||||
}
|
||||
|
||||
/// <returns>A <see cref="System.String" />.</returns>
|
||||
String GetRequestDN();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an LdapResult.
|
||||
/// <pre>
|
||||
/// LdapResult ::= SEQUENCE {
|
||||
/// resultCode ENUMERATED {
|
||||
/// success (0),
|
||||
/// operationsError (1),
|
||||
/// protocolError (2),
|
||||
/// timeLimitExceeded (3),
|
||||
/// sizeLimitExceeded (4),
|
||||
/// compareFalse (5),
|
||||
/// compareTrue (6),
|
||||
/// authMethodNotSupported (7),
|
||||
/// strongAuthRequired (8),
|
||||
/// -- 9 reserved --
|
||||
/// referral (10), -- new
|
||||
/// adminLimitExceeded (11), -- new
|
||||
/// unavailableCriticalExtension (12), -- new
|
||||
/// confidentialityRequired (13), -- new
|
||||
/// saslBindInProgress (14), -- new
|
||||
/// noSuchAttribute (16),
|
||||
/// undefinedAttributeType (17),
|
||||
/// inappropriateMatching (18),
|
||||
/// constraintViolation (19),
|
||||
/// attributeOrValueExists (20),
|
||||
/// invalidAttributeSyntax (21),
|
||||
/// -- 22-31 unused --
|
||||
/// noSuchObject (32),
|
||||
/// aliasProblem (33),
|
||||
/// invalidDNSyntax (34),
|
||||
/// -- 35 reserved for undefined isLeaf --
|
||||
/// aliasDereferencingProblem (36),
|
||||
/// -- 37-47 unused --
|
||||
/// inappropriateAuthentication (48),
|
||||
/// invalidCredentials (49),
|
||||
/// insufficientAccessRights (50),
|
||||
/// busy (51),
|
||||
/// unavailable (52),
|
||||
/// unwillingToPerform (53),
|
||||
/// loopDetect (54),
|
||||
/// -- 55-63 unused --
|
||||
/// namingViolation (64),
|
||||
/// objectClassViolation (65),
|
||||
/// notAllowedOnNonLeaf (66),
|
||||
/// notAllowedOnRDN (67),
|
||||
/// entryAlreadyExists (68),
|
||||
/// objectClassModsProhibited (69),
|
||||
/// -- 70 reserved for CLdap --
|
||||
/// affectsMultipleDSAs (71), -- new
|
||||
/// -- 72-79 unused --
|
||||
/// other (80) },
|
||||
/// -- 81-90 reserved for APIs --
|
||||
/// matchedDN LdapDN,
|
||||
/// errorMessage LdapString,
|
||||
/// referral [3] Referral OPTIONAL }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcResponse" />
|
||||
internal class RfcLdapResult : Asn1Sequence, IRfcResponse {
|
||||
public const Int32 Referral = 3;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0068:Empfohlenes Dispose-Muster verwenden", Justification = "<Ausstehend>")]
|
||||
public RfcLdapResult(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
// Decode optional referral from Asn1OctetString to Referral.
|
||||
if(this.Size() <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
Asn1Tagged obj = (Asn1Tagged)this.Get(3);
|
||||
Asn1Identifier id = obj.GetIdentifier();
|
||||
|
||||
if(id.Tag != Referral) {
|
||||
return;
|
||||
}
|
||||
|
||||
SByte[] content = ((Asn1OctetString)obj.TaggedValue).ByteValue();
|
||||
this.Set(3, new Asn1SequenceOf(new MemoryStream(content.ToByteArray()), content.Length));
|
||||
}
|
||||
|
||||
public Asn1Enumerated GetResultCode() => (Asn1Enumerated)this.Get(0);
|
||||
|
||||
public Asn1OctetString GetMatchedDN() => new Asn1OctetString(((Asn1OctetString)this.Get(1)).ByteValue());
|
||||
|
||||
public Asn1OctetString GetErrorMessage() => new Asn1OctetString(((Asn1OctetString)this.Get(2)).ByteValue());
|
||||
|
||||
public Asn1SequenceOf GetReferral() => this.Size() > 3 ? (Asn1SequenceOf)this.Get(3) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Done Response.
|
||||
/// <pre>
|
||||
/// SearchResultDone ::= [APPLICATION 5] LdapResult
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.RfcLdapResult" />
|
||||
internal class RfcSearchResultDone : RfcLdapResult {
|
||||
public RfcSearchResultDone(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Search Result Entry.
|
||||
/// <pre>
|
||||
/// SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
|
||||
/// objectName LdapDN,
|
||||
/// attributes PartialAttributeList }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
internal sealed class RfcSearchResultEntry : Asn1Sequence {
|
||||
public RfcSearchResultEntry(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public String ObjectName => ((Asn1OctetString)this.Get(0)).StringValue();
|
||||
|
||||
public Asn1Sequence Attributes => (Asn1Sequence)this.Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.SearchResponse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Message ID.
|
||||
/// <pre>
|
||||
/// MessageID ::= INTEGER (0 .. maxInt)
|
||||
/// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
|
||||
/// Note: The creation of a MessageID should be hidden within the creation of
|
||||
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
|
||||
/// upper and lower limit. There is never a case when a user should be
|
||||
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
|
||||
/// class should be package protected. (So the MessageID value isn't
|
||||
/// arbitrarily run up.)
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" />
|
||||
internal class RfcMessageID : Asn1Integer {
|
||||
private static Int32 _messageId;
|
||||
private static readonly Object SyncRoot = new Object();
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Message ID.
|
||||
/// <pre>
|
||||
/// MessageID ::= INTEGER (0 .. maxInt)
|
||||
/// maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
|
||||
/// Note: The creation of a MessageID should be hidden within the creation of
|
||||
/// an RfcLdapMessage. The MessageID needs to be in sequence, and has an
|
||||
/// upper and lower limit. There is never a case when a user should be
|
||||
/// able to specify the MessageID for an RfcLdapMessage. The MessageID()
|
||||
/// class should be package protected. (So the MessageID value isn't
|
||||
/// arbitrarily run up.)
|
||||
/// </pre></summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Integer" />
|
||||
internal class RfcMessageID : Asn1Integer
|
||||
{
|
||||
private static int _messageId;
|
||||
private static readonly object SyncRoot = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RfcMessageID"/> class.
|
||||
/// Creates a MessageID with an auto incremented Asn1Integer value.
|
||||
/// Bounds: (0 .. 2,147,483,647) (2^^31 - 1 or Integer.MAX_VALUE)
|
||||
/// MessageID zero is never used in this implementation. Always
|
||||
/// start the messages with one.
|
||||
/// </summary>
|
||||
protected internal RfcMessageID()
|
||||
: base(MessageId)
|
||||
{
|
||||
}
|
||||
|
||||
private static int MessageId
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _messageId < int.MaxValue ? ++_messageId : (_messageId = 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Initializes a new instance of the <see cref="RfcMessageID"/> class.
|
||||
/// Creates a MessageID with an auto incremented Asn1Integer value.
|
||||
/// Bounds: (0 .. 2,147,483,647) (2^^31 - 1 or Integer.MAX_VALUE)
|
||||
/// MessageID zero is never used in this implementation. Always
|
||||
/// start the messages with one.
|
||||
/// </summary>
|
||||
protected internal RfcMessageID()
|
||||
: base(MessageId) {
|
||||
}
|
||||
|
||||
private static Int32 MessageId {
|
||||
get {
|
||||
lock(SyncRoot) {
|
||||
return _messageId < Int32.MaxValue ? ++_messageId : (_messageId = 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,42 @@
|
||||
namespace Unosquare.Swan.Networking.Ldap
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Ldap Modify Request.
|
||||
/// <pre>
|
||||
/// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||
/// object LdapDN,
|
||||
/// modification SEQUENCE OF SEQUENCE {
|
||||
/// operation ENUMERATED {
|
||||
/// add (0),
|
||||
/// delete (1),
|
||||
/// replace (2) },
|
||||
/// modification AttributeTypeAndValues } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal sealed class RfcModifyRequest
|
||||
: Asn1Sequence, IRfcRequest
|
||||
{
|
||||
public RfcModifyRequest(string obj, Asn1SequenceOf modification)
|
||||
: base(2)
|
||||
{
|
||||
Add(obj);
|
||||
Add(modification);
|
||||
}
|
||||
|
||||
public Asn1SequenceOf Modifications => (Asn1SequenceOf)Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
|
||||
|
||||
public string GetRequestDN() => ((Asn1OctetString)Get(0)).StringValue();
|
||||
}
|
||||
|
||||
internal class RfcModifyResponse : RfcLdapResult
|
||||
{
|
||||
public RfcModifyResponse(Stream stream, int len)
|
||||
: base(stream, len)
|
||||
{
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
|
||||
}
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Unosquare.Swan.Networking.Ldap {
|
||||
/// <summary>
|
||||
/// Represents an Ldap Modify Request.
|
||||
/// <pre>
|
||||
/// ModifyRequest ::= [APPLICATION 6] SEQUENCE {
|
||||
/// object LdapDN,
|
||||
/// modification SEQUENCE OF SEQUENCE {
|
||||
/// operation ENUMERATED {
|
||||
/// add (0),
|
||||
/// delete (1),
|
||||
/// replace (2) },
|
||||
/// modification AttributeTypeAndValues } }
|
||||
/// </pre>
|
||||
/// </summary>
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.Asn1Sequence" />
|
||||
/// <seealso cref="Unosquare.Swan.Networking.Ldap.IRfcRequest" />
|
||||
internal sealed class RfcModifyRequest
|
||||
: Asn1Sequence, IRfcRequest {
|
||||
public RfcModifyRequest(String obj, Asn1SequenceOf modification)
|
||||
: base(2) {
|
||||
this.Add(obj);
|
||||
this.Add(modification);
|
||||
}
|
||||
|
||||
public Asn1SequenceOf Modifications => (Asn1SequenceOf)this.Get(1);
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyRequest);
|
||||
|
||||
public String GetRequestDN() => ((Asn1OctetString)this.Get(0)).StringValue();
|
||||
}
|
||||
|
||||
internal class RfcModifyResponse : RfcLdapResult {
|
||||
public RfcModifyResponse(Stream stream, Int32 len)
|
||||
: base(stream, len) {
|
||||
}
|
||||
|
||||
public override Asn1Identifier GetIdentifier() => new Asn1Identifier(LdapOperation.ModifyResponse);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user