Module RADUM
In: lib/radum/ad.rb
lib/radum/container.rb
lib/radum/group.rb
lib/radum/logger.rb
lib/radum/user.rb

The RADUM module provides an interface to Microsoft Active Directory for working with users and groups. The User class represents a standard Windows user account. The UNIXUser class represents a Windows account that has UNIX attributes. Similarly, the Group class represents a standard Windows group, and the UNIXGroup class represents a Windows group that has UNIX attributes. UNIX attributes are supported if Active Directory has been extended, such as when Microsoft Identity Management for UNIX has been installed. LDAP extensions for UNIX are not required if only Windows users and groups are operated on. This module concentrates only on users and groups at this time.

This is a pure Ruby implementation. Windows command line tools are not used in any way, so this will work from other platforms such as Mac OS X and Linux in addition to Windows. RADUM does require the Active Directory server to support SSL because that is a requirement for creating user accounts through LDAP. This means that a domain must have a certificate server. Using a self-signed certificate should be fine.

RADUM considers its view of user and group attributes to be authoritative, with the exception that it will not modify the members of a group that it is not already aware of. The general RADUM pattern is:

See the class documenation for more details, especially the AD#load and AD#sync methods.

AD

The AD class represents an Active Directory. An AD object can be created with something like the following code:

 ad = RADUM::AD.new :root => 'dc=example,dc=com',
                    :user => 'cn=Administrator,cn=Users',
                    :password => 'password',
                    :server => '192.168.1.1'

The AD class will connect to Active Directory when it needs to do so automatically. A connection will not be made until required. Some arguments in RADUM are specified using a relative path sans the :root argument to the AD.new method, such as the :user argument and Container names discussed next. The AD class provides a read-only ldap attriubte that allows direct interaction with Active Directory if needed. The AD object automatically adds the "cn=Users" Container object because most users have "Domain Users" as their primary Windows group. The primary Windows group is necessary for creating User and UNIXUser objects, especially when AD#load is called. Note that the :root and :user arguments can use lowercase or uppercase for the "dc=", "cn=", or "ou=" path elements.

Container

Container objects are added to the AD object before AD#load is called in most cases. Container objects can be created with something like the following code:

 people = RADUM::Container.new :name => 'ou=People',
                               :directory => ad

It is possible to have nested Container objects as well, such as:

 staff = RADUM::Container.new :name => 'ou=Staff,ou=People',
                              :directory => ad
 faculty = RADUM::Container.new :name => 'ou=Faculty,ou=People',
                                :directory => ad
 grads = RADUM::Container.new :name => 'ou=Grads,ou=People',
                              :directory => ad

Container objects do not have a direct reference to Container objects they logically contain, but the RADUM code handles these cases when creating and removing Container objects from Active Directory. Container objects do have references to all users and groups they contain as well as the AD object they belong to.

The :name of a Container object is the only other case where a relative path is used. The :name should be the directory path (the distinguished name) sans the :root path given to the AD object that owns the Container. Container objects can be Active Directory organizational units or containers. Note that Active Directory containers cannot contain organizational units, but the reverse is true, therefore the following :name would be invalid:

 invalid = RADUM::Container.new :name => 'ou=Staff,cn=People',
                                :directory => ad

but the following :name is valid:

 valid = RADUM::Container.new :name => 'cn=Staff,ou=People',
                              :directory => ad

In general, one should use Active Directory organizational units instead of containers. They are much more useful. The :name argument can use lowercase or uppercase for the "cn=" and "ou=" path elements.

Container objects should be added to the AD object before doing serious work in the general case for the best results. This doesn‘t necessarily have to be done if only creating new objects, but it is still a good idea that won‘t hurt anyway. The "cn=Users" container is added by default to an AD object when it is created to help make things easier as it is usually required, and if it really is not needed, it should not take too many resources.

Removing Container Objects

Removing a Container causes that Container to be removed from Active Directory when AD#sync is called, but only if that is possible. Container objects cannot be removed if they have groups that cannot be removed nor if they logically contain other Container objects. When calling the AD#remove_container method, checks are made for all the objects RADUM knows about. When calling AD#sync checks are made in Active Directory. See the AD#remove_container method documentation for more details. Note that removing a Container will always remove all User or UNIXUser objects it contains regardless, and when AD#sync is called, all possible objects that can be removed will be due to the implementation of AD#remove_container.

It is also possible to destroy a Container. Unlike with users and groups, destroying a Container with AD#destroy_container simply removes the reference to the Container in the AD object. It does not result in the Container objects contents from being removed. The Container simply will not be processed in AD#load and AD#sync calls. The AD object determines all user and group references based on the Container objects it owns.

Loading the AD Object

Once the Container objects have been added to the AD object, the AD object can be loaded. Calling AD#load is straightforward:

 ad.load

This will create User, UNIXUser, Group, and UNIXGroup objects to represent all such objects in the Container objects added to the AD object. The AD#load method determines if a user or group is a Windows or UNIX type based on the object‘s LDAP attributes. AD#load can be called multiple times, but it will ignore loading any user or group objects that already exist in the RADUM environment.

Working with Users and Groups

User and group object creation results in the users and groups being added to their Container object automatically. Users and groups also have direct references to their corresponding collections of groups for users and groups plus users for groups (since groups can contain other groups as members). User memberships in groups can be accomplished from either perspective due to this automatic handling:

Group membership in groups has to be handled by using the Group#add_group method only, which adds the Group or UNIXGroup used as the argument as a member of the Group object to which the method belongs.

RADUM ensures that duplicate users and groups cannot be created, including specific attributes of those objects which must be unique. Trying to do something that would result in duplication generally raises a RuntimeError.

RADUM handles implicit group memberships from the Windows perspective for UNIXUser objects when adding them to UNIXGroup objects. When adding a UNIXUser to a UNIXGroup, the UNIXUser gets a membership in the UNIXGroup from both the UNIX and Windows perspectives.

Creating Group and UNIXGroup Objects

Group objects represent Windows groups and UNIXGroup objects represent UNIX groups, which are Windows groups that have UNIX attributes. Examples include:

 research_users = RADUM::Group.new :name => 'Research Users',
                                   :container => faculty
 unix_staff = RADUM::UNIXGroup.new :name => 'staff',
                                   :container => staff,
                                   :gid => 1005

There are additional argumements in both cases. See Group.new and UNIXGroup.new for additional details. In both cases, the :rid argument should only be used by AD#load to specify the group object‘s relative identifier, which is based on the LDAP objectSid attribute. There is an :nis_domain argument for UNIXGroup objects that defaults to "radum" if not specified. NIS is not required for using Active Directory in a centralized authentication design. One could access the UNIX attributes through LDAP directly for example. However, the NIS domain needs to be present if one wants to use the Active Directory Users and Computers GUI tool, therefore one is specified by default. Of course, this should still be set correctly in order to work in the Active Directory Users and Computers GUI tool, but it can be changed there easily and the attributes should still show up correctly. The :type argument applies to both Group and UNIXGroup objects. The default value is RADUM::GROUP_GLOBAL_SECURITY, which is the default when creating group objects using the Active Directory Users and Computers GUI tool in Windows. More details about the restrictions for certain group types can be found in the User#primary_group= method documentation.

There is a useful method for finding the next available GID for the UNIXGroup object‘s :gid argument:

 gid = ad.load_next_gid

The AD#load_next_gid method searches Active Directory and the current RADUM objects for the next GID value that can be used. The :gid attribute for a UNIXGroup also becomes a UNIXUser object‘s GID value, depending on the :unix_main_group for that UNIXUser.

Removing Group and UNIXGroup Objects

Group and UNIXGroup objects can be removed from RADUM by using the following method for the Container object that they belong to:

 faculty.remove_group research_users

or by searching for a group:

 faculty.remove_group ad.find_group_by_name('Research Users')

Removing a group causes that group to be removed from Active Directory when AD#sync is called, but only if that is possible. Group and UNIXGroup objects cannot be removed if they are the primary Windows group of any user in Active Directory nor if they are the UNIX main group for any UNIXUser objects in Active Directory. When calling the Container#remove_group method, checks are made for all the objects RADUM knows about. When calling AD#sync checks are made for all users in Active Directory.

It is also possible to destroy a Group or UNIXGroup object. Destroying a Group or UNIXGroup does not remove the object from Active Directory. Instead it removes all references to the object in RADUM itself. The same checks are made against all objects RADUM knows about before destroying the object can succeed. See the Container#destroy_group method documentation for more details.

In both cases, any external references ot the orginal object should be discarded.

Converting Group and UNIXGroup Objects

It is possible to convert Group objects to UNIXGroup objects and vice versa under certain conditions. Group objects can converted to UNIXGroup objects if the Group is not the primary Windows group for a user RADUM knows about. This is not really a strict requirement for users in Active Directory, but it is a limitation of the current RADUM implementation. UNIXGroup objects can be converted to Group objects with the same implementation restriction with the additional restriction that the UNIXGroup cannot be the UNIX main group for any user in Active Directory. Conversion causes this check to be made outside of the AD#load and AD#sync methods immediately before proceeding. If the conversion is successful, a new object of the desired type is created and placed into the RADUM system in place of the original object. Any external references to the orginal object should be discarded as they will be treated as if they were removed objects, even though they are not removed from Active Directory. Note that UNIX attributes are removed from UNIXGroup objects in Active Directory immediately before AD#sync when converted to Group objects.

Creating User and UNIXUser Objects

User objects represent Windows users and UNIXUser objects represent UNIX users, which are Windows users that have UNIX attributes. Examples include:

 RADUM::User.new :username => 'martin',
                 :container => faculty,
                 :primary_group => ad.find_group_by_name('Domain Users')
 RADUM::UNIXUser.new :username => 'rowland',
                     :container => staff,
                     :primary_group => ad.find_group_by_name('Domain Users'),
                     :uid => 5437,
                     :unix_main_group => unix_staff,
                     :shell => '/bin/bash',
                     :home_directory => '/home/rowland'

There are additional argumements in both cases. See User.new and UNIXUser.new for additional details. In both cases, the :rid argument should only be used by AD#load to specify the user object‘s relative identifier, which is based on the LDAP objectSid attribute. There is an :nis_domain argument for UNIXUser objects that defaults to "radum" if not specified. NIS is not required for using Active Directory in a centralized authentication design. One could access the UNIX attributes through LDAP directly for example. However, the NIS domain needs to be present if one wants to use the Active Directory Users and Computers GUI tool, therefore one is specified by default. Of course, this should still be set correctly in order to work in the Active Directory Users and Computers GUI tool, but it can be changed there easily and the attributes should still show up correctly. The :disabled argument applies to both User and UNIXUser objects. The default value is false. The password attribute for User and UNIXUser objects is nil unless set, and when set it causes the user in Active Directory to have that password if it meets the Group Policy password requirements. Once set through AD#sync, the password attribute is set to nil again. If there is no password set when the user is created in Active Directory, a random password that probably meets the Group Policy password requirements will be generated, and the user will not be forced to change that password unless User#force_change_password is also called before calling AD#sync. The GID value for a UNIXUser comes from its :unix_main_group argument UNIXGroup value. There are many attributes that can be set for User and UNIXUser objects. See the User and UNIXUser class documentation for more details.

There is a useful method for finding the next available UID for the UNIXUser object‘s :uid argument:

 uid = ad.load_next_uid

The AD#load_next_uid method searches Active Directory and the current RADUM objects for the next UID value that can be used.

Removing User and UNIXUser Objects

User and UNIXUser objects can be removed from RADUM by using the following method for the Container object that they belong to:

 faculty.remove_user ad.find_user_by_username('martin')

or by specifying a reference to a User or UNIXUser object directly.

Removing a user causes that user to be removed from Active Directory when AD#sync is called.

It is also possible to destroy a User or UNIXUser object. Destroying a User or UNIXUser does not remove the object from Active Directory. Instead it removes all references to the object in RADUM itself.

In both cases, any external references ot the orginal object should be discarded.

Converting User and UNIXUser Objects

It is possible to convert User objects to UNIXUser objects and vice versa. If the conversion is successful, a new object of the desired type is created and placed into the RADUM system in place of the original object. Any external references to the orginal object should be discarded as they will be treated as if they were removed objects, even though they are not removed from Active Directory. Note that UNIX attributes are removed from UNIXUser objects in Active Directory immediately before AD#sync when converted to User objects.

Synchronizing to Active Directory

After making any changes, synchronizing those changes to Active Directory can be accomplished with the following:

 ad.sync

This method can be called whenever changes are made. Changes in RADUM are considered authoritative and Active Directory objects are updated to reflect their attributes as RADUM sees the world. This is true for group memberships except for members that RADUM does not know about. This means that AD#sync will not cause users and groups it does not know about to be removed from a Group or UNIXGroup it does know about, but it will remove users and groups that have been explicitly removed in the RADUM system.

Logging

The RADUM module instantiates an object of the Logger class. This can be used to log operational progress:

 RADUM::logger.log("\nInitializing...\n\n", RADUM::LOG_DEBUG)

The RADUM::LOG_DEBUG level includes a lot of verbose progress information that can be highly useful. The log level argument is optional and defaults to RADUM::LOG_NORMAL. See the Logger class documentation for more details. Note that log output can also be sent to a file.

Windows Server Versions

RADUM has been exclusively tested against Windows Server 2008 and Windows Server 2003 R2 Standard Edition SP2. The testing system had Microsoft Identity Management for UNIX installed and had the Certificate Services Role added, which is added using Add/Remove Programs in Windows Server 2003. Microsoft Identity Management for UNIX is generally required for editing UNIXUser and UNIXGroup objects in the Active Directory Users and Computers GUI tool if desired, and the Certificate Services Role is required in the domain because SSL is required for creating User and UNIXUser objects using LDAP. The LDAP attributes required for UNIXUser objects don‘t necessarily require Microsoft Identity Management for UNIX to be installed.

RADUM works with a domain functional level of Windows 2003 Server or higher. Most of RADUM will work with a domain functional level of Windows 2000 native except for Universal group types and groups being members of groups. If those features are not needed, the Windows 2000 native domain functional level can be used, but RADUM assumes these features are possible, so one would need to ensure they do not use them if working at a domain functional level lower than Windows Server 2003. RADUM was not tested at a domain functional level lower than Windows 2000 native.

Author:Shaun Rowland <rowand@shaunrowland.com>
Copyright:Copyright 2009 Shaun Rowland. All rights reserved.
License:BSD License included in the project LICENSE file.

Methods

logger  

Classes and Modules

Class RADUM::AD
Class RADUM::Container
Class RADUM::Group
Class RADUM::Logger
Class RADUM::UNIXGroup
Class RADUM::UNIXUser
Class RADUM::User

Constants

GROUP_DOMAIN_LOCAL_SECURITY = -2147483644   Group type constants.

These are the Fixnum representations of what should be Bignum objects in some cases as far as I am aware. In the Active Directory Users and Groups GUI tool, they are shown as hexidecimal values, indicating they should be Bignums (well, some of them). However, if you try to edit the values in that tool (with advanced attribute editing enabled or with the ADSI Edit tool) these show up as the Fixnum values here. We are going to stick with that, even though it is lame. I could not pull these out as Bignum objects. Some of these are small enough to be Fixnums though, so I left them as their hexidecimal values. These values correspond to the LDAP groupType attribute for group objects.

GROUP_DOMAIN_LOCAL_DISTRIBUTION = 0x4
GROUP_GLOBAL_SECURITY = -2147483646
GROUP_GLOBAL_DISTRIBUTION = 0x2
GROUP_UNIVERSAL_SECURITY = -2147483640
GROUP_UNIVERSAL_DISTRIBUTION = 0x8
UF_ACCOUNTDISABLE = 0x0002;   Some useful constants from lmaccess.h for use with creating user accounts.
UF_PASSWD_NOTREQD = 0x0020;
UF_PASSWD_CANT_CHANGE = 0x0040;
UF_NORMAL_ACCOUNT = 0x0200;
LOG_NONE = 0   Logger constants.
LOG_NORMAL = 1
LOG_DEBUG = 2

Public Class methods

Access the RADUM Logger instance.

[Source]

    # File lib/radum/logger.rb, line 64
64:   def RADUM.logger
65:     @radum_logger
66:   end

[Validate]