RaspberryIO/Unosquare.Swan/Networking/Ldap/LdapControl.cs
2019-12-03 18:44:25 +01:00

275 lines
9.7 KiB
C#

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>
/// 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, 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>
/// Returns the identifier of the control.
/// </summary>
/// <value>
/// The identifier.
/// </value>
public String Id => this.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 Boolean Critical => this.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() => 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();
}
}