Showing posts with label Directory Modifications. Show all posts
Showing posts with label Directory Modifications. Show all posts

Sunday, August 14, 2011

LINQ To LDAP: CR(U)D

So now that I've covered Adding entries, let's talk about updating. Updating your entries is a little different than in a RDBMS.

Using S.DS.P directly you can make modifications to an entry like so:
var firstNameMod = new DirectoryAttributeModification
                        {
                            Name = "givenname",
                            Operation = DirectoryAttributeOperation.Replace,

                        };
firstNameMod.Add("Jack");

var commentMod = new DirectoryAttributeModification
                        {
                            Name = "comment",
                            Operation = DirectoryAttributeOperation.Add,

                        };
commentMod.Add("add a property to this entry");

var dateOfBirthMod = new DirectoryAttributeModification
                        {
                            Name = "dateofbirth",
                            Operation = DirectoryAttributeOperation.Delete,

                        };

ModifyRequest request = new ModifyRequest("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", new [] {firstNameMod, commentMod, dateOfBirthMod });

ldapConnection.SendRequest(request);

So what you see here is a request to modify John Doe by changing his given name to Jack, adding a brand new attribute called comment, and deleting his date of birth. Those last two modification will actually make a change to the structure of the object. So how do you do this with LINQ To LDAP? Replace (updating) operations are pretty easy, but I separate adding or removing attributes into different methods because it's difficult to identify the intention (i.e. are you trying to delete an attribute because you set it to null).

I'll continue with my previous User class example, but with a few modifications for change tracking. The primary change is sub typing DirectoryObjectBase (built into LINQ to LDAP) and changing your setters to look like this:
set
{
     _firstName = value;
     AttributeChanged("FirstName");
}

Similar to INotifyPropertyChanged, just call the AttributeChanged when the property changes. However, I don't use the interface because I didn't want to worry about events subscriptions. But don't feel like you have to use DirectoryObjectBase. LINQ To LDAP will work just as well without it.

Change tracking on entries is enabled when you use either a full projection ( Select(u => u) ), no projection or GetByDN. Since I support projections of mapped entries, I had to disable it so you wouldn't accidentally update attributes to an empty value. If your object sub types DirectoryObjectBase and you try to update it without change tracking being enabled, then I actually throw an exception. For dynamic queries, change tracking is already built in so you don't have to do anything special.

So let's update!
var factory = new LdapConnectionFactory("localhost");

//mapped
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    var user = context.GetByDN<User>("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local");
    user.FirstName = "Jack";
    context.Update(user);
}

//dynamic
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    dynamic user = context.GetByDN("CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "givenname");
    user.givenname = "Jack";
    context.Update(user.DistinguishedName, user);
}

//update structure
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    context.AddAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", "comment", 
        "add a property to this entry");

    context.DeleteAttribute(
        "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
        "dateofbirth");
}

There's one more operation for updating. In order to change an entry's distinguished name or move it to a different location you have to issue a ModifyDN request. This kind of request looks like this:
//Move entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=John Doe"
};

connection.SendRequest(dnRequest);

//Move entry using LINQ To LDAP
string newDn = directoryContext.MoveEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"CN=Deactivated Users,CN=Employees,DC=Northwind,DC=local");


//Rename entry using raw S.DS.P
var dnRequest = new ModifyDNRequest
{
	DistinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local"
	NewParentDistinguishedName = "CN=Users,CN=Employees,DC=Northwind,DC=local",
	NewName = "CN=Jack Doe"
};

connection.SendRequest(dnRequest);

//Rename entry using LINQ To LDAP
string newDn = directoryContext.RenameEntry(
	"CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local", 
	"Jack Doe");

And that's all there is to it. One thing that you must know is there's no concept of transactions in LDAP (at least not yet) so if you want to update multiple entries, be aware that if it fails half-way through, there's no rollback.

LINQ To LDAP 2.0 Beta is out over at CodePlex!

Wednesday, July 27, 2011

LINQ To LDAP: (C)RUD

Just like any other data store you can create, update, and delete data in a directory.

Here's how you create entries using S.DS.P:

LdapConnection connection = new LdapConnection("localhost");

string distinguishedName = "CN=John Doe,CN=Users,CN=Employees,DC=Northwind,DC=local";
AddRequest request = new AddRequest(distinguishedName, "User");

request.Attributes.Add(new DirectoryAttribute("givenname", "John"));
request.Attributes.Add(new DirectoryAttribute("sn", "Doe"));
request.Attributes.Add(new DirectoryAttribute("employeeid", "1"));

connection.SendRequest(request);

Looks pretty straightforward. You give your entry a primary key (distinguished name), an object class, and then populate the attributes for the new entry and submit it to the directory.

So here's how you do it with mapped classes:
public abstract class DirectoryObject
{
    [DistinguishedName]
    public string DistinguishedName { get; set; }

    [DirectoryAttribute(StoreGenerated = true)]
    public DateTime? WhenChanged { get; set; }

    [DirectoryAttribute("cn")]
    public string CommonName { get; set; }

    [DirectoryAttribute(StoreGenerated = true)]
    public DateTime? WhenCreated { get; set; }

    [DirectoryAttribute("objectguid", StoreGenerated = true)]
    public Guid Guid { get; set; }

    [DirectoryAttribute]
    public string Name { get; set; }
}

[DirectorySchema(NamingContext, ObjectCategory = "Person", ObjectClass = "user")]
public class User : DirectoryObject
{
    private const string NamingContext = "CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";

    [DirectoryAttribute("objectsid", StoreGenerated = true)]
    public SecurityIdentifier SID { get; set; }

    [DirectoryAttribute("givenname")]
    public string FirstName { get; set; }

    [DirectoryAttribute("sn")]
    public string LastName { get; set; }

    [DirectoryAttribute("directreports")]
    public string[] Employees { get; set; }

    [DirectoryAttribute]
    public string Title { get; set; }

    [DirectoryAttribute]
    public string PostalCode { get; set; }

    [DirectoryAttribute(ImageFormat = ImageType.Png)]
    public Bitmap Photo { get; set; }

    [DirectoryAttribute("l")]
    public string City { get; set; }

    [DirectoryAttribute("c")]
    public string Country { get; set; }

    [DirectoryAttribute]
    public string EmployeeID { get; set; }

    [DirectoryAttribute]
    public string TelephoneNumber { get; set; }

    [DirectoryAttribute("pwdlastset", DateTimeFormat = null, StoreGenerated = true)]
    public DateTime? PasswordLastSet { get; set; }

    [DirectoryAttribute]
    public string Street { get; set; }

    public void SetDistinguishedName()
    {
        DistinguishedName = "CN=" + CommonName + "," + NamingContext;
    }
}

There are a few items to note here. First is the StoreGenerated property of DirectoryAttribute. This allows the DirectoryContext to only update attributes that the store doesn't manage. The second is the DistinguishedName attribute which mainly helps when calling Add and Update. The third is the DirectoryObject which isn't a part of LINQ to LDAP, but is an example abstract base class that all directory objects can sub-type.
User user = new User
                {
                    City = "Some City",
                    CommonName = "John Doe",
                    Country = "US",
                    EmployeeID = "1",
                    FirstName = "John",
                    LastName = "Doe",
                    Name = "Doe, John",
                    Street = "1234 Street",
                    Title = "Unknown",
                    PostalCode = "12345",
                    TelephoneNumber = "123-456-7890"
                };

user.SetDistinguishedName();
var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    User added = context.Add(user);
}

I create a new user and initialize its properties. Whenever an object is added or updated, a fresh version is retrieved using GetByDN.

Alternatively, you can perform the same operation using a dictionary:
string dn = "CN=John Doe,CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";
var attributes = new Dictionary<string, object>
                        {
                            {"l", "Some City"},
                            {"cn", "John Doe"},
                            {"c", "US"},
                            {"EmployeeID", "1"},
                            {"givenname", "John"},
                            {"sn", "Doe"},
                            {"Name", "Doe, John"},
                            {"Street", "1234 Street"},
                            {"Title", "Unknown"},
                            {"PostalCode", "12345"},
                            {"TelephoneNumber", "123-456-7890"}
                        };

var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    IDictionary<string, object> added = context.Add(dn, "User", attributes);
}

Or by converting an anonymous object to a dictionary:
string dn = "CN=John Doe,CN=Users 1,CN=TestContainer,CN=Employees,DC=Northwind,DC=local";
var user = new
                {
                    City = "Some City",
                    CommonName = "John Doe",
                    Country = "US",
                    EmployeeID = "1",
                    FirstName = "John",
                    LastName = "Doe",
                    Name = "Doe, John",
                    Street = "1234 Street",
                    Title = "Unknown",
                    PostalCode = "12345",
                    TelephoneNumber = "123-456-7890"
                };

var factory = new LdapConnectionFactory("localhost");
using (var context = new DirectoryContext(factory.GetConnection(), true))
{
    IDictionary<string, object> added = context.Add(dn, "User", user.ToDictionary());
}

ToDictionary is just an extension method that reflects over an attribute and creates a dictionary from its properties.

So I think that covers how to add new entries. Questions, thoughts, improvements?