RaspberryIO/Unosquare.Swan/Networking/Ldap/LdapControl.cs
2019-02-17 14:08:57 +01:00

292 lines
10 KiB
C#

namespace Unosquare.Swan.Networking.Ldap
{
using System;
using System.Collections.Generic;
using Exceptions;
/// <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>
/// 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);
}
}
/// <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(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();
}
/// <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
{
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);
}
}
}
/// <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(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; }
}
}
/// <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();
}
}