[Bps-public-commit] RT-Extension-LDAPImport branch, documentation-updates, created. 0.32_03-1-g8289123

Thomas Sibley trs at bestpractical.com
Tue Apr 10 15:37:17 EDT 2012


The branch, documentation-updates has been created
        at  828912378ebadd6f842029f3c2765d4355ae7dde (commit)

- Log -----------------------------------------------------------------
commit 828912378ebadd6f842029f3c2765d4355ae7dde
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Apr 6 11:32:27 2012 -0400

    Documentation updates.
    
    Moved install instructions to INSTALL.
    
    README is now generated from the pod via ReadmeFromPod.
    
    Some more updates to pod in main file.

diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..d8769bf
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,41 @@
+RT-Extension-LDAPImport
+
+INSTALLATION
+
+How to install:
+
+1. perl Makefile.PL
+2. make
+3. make install (may need root permissions)
+4. Edit your /path-to-rt/etc/RT_SiteConfig.pm 
+    Set(@Plugins, qw(RT::Extension::LDAPImport));
+    or add RT::Extension::LDAPImport to your existing @Plugins line
+5. Clear your mason cache
+     rm -rf /path-to-rt/var/mason_data/obj
+6. Restart your webserver
+
+This will install an rtldapimport script in
+/opt/rt4/local/plugins/RT-Extension-LDAPImport/bin
+and install the RT::Extension::LDAPImport
+module.
+
+RUNNING THE IMPORTER
+
+Set the module documentation for details on
+configuration and running the importer.
+
+>perldoc RT::Extension::LDAPImmport
+
+DEPENDENCIES
+
+  Class::Accessor
+  Net::LDAP
+  RT: 3.8.x
+
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2007-2011, Best Practical Solutions LLC.
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
diff --git a/Makefile.PL b/Makefile.PL
index f7a15bc..8ecc144 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -5,6 +5,7 @@ author ('Kevin Falcone <falcone at bestpractical.com>');
 license('perl');
 abstract('Import RT Users from an LDAP store');
 all_from('lib/RT/Extension/LDAPImport.pm');
+readme_from('lib/RT/Extension/LDAPImport.pm');
 
 requires('Test::More');
 requires('Net::LDAP');
diff --git a/README b/README
index da27925..e51860b 100644
--- a/README
+++ b/README
@@ -1,145 +1,385 @@
-RT-Extension-LDAPImport
+NAME
+    RT::Extension::LDAPImport - Import Users from an LDAP store
 
-INSTALLATION
+SYNOPSIS
+        # In RT_SiteConfig.pm
 
-How to install:
+        Set($LDAPHost,'my.ldap.host')
+        Set($LDAPUSER,'me');
+        Set($LDAPPassword,'mypass');
+        Set($LDAPFilter, '(&(cn = users))');
+        Set($LDAPMapping, {Name         => 'uid', # required
+                           EmailAddress => 'mail',
+                           RealName     => 'cn',
+                           WorkPhone    => 'telephoneNumber',
+                           Organization => 'departmentName'});
 
-1. perl Makefile.PL
-2. make
-3. make install (may need root permissions)
-4. Edit your /opt/rt3/etc/RT_SiteConfig.pm 
-    Set(@Plugins, qw(RT::Extension::LDAPImport));
-    or add RT::Extension::LDAPImport to your existing @Plugins line
-5. Clear your mason cache
-     rm -rf /opt/rt3/var/mason_data/obj
-6. Restart your webserver
+        # Add to any existing plugins
+        Set(@Plugins, qw(RT::Extension::LDAPImport));
 
+        # If you want to sync Groups RT <-> LDAP
 
-This will install an rtldapimport script and the RT::Extension::LDAPImport
-module.
+        Set($LDAPGroupBase, 'ou=Groups,o=Our Place');
+        Set($LDAPGroupFilter, '(&(cn = Groups))');
+        Set($LDAPGroupMapping, {Name               => 'cn',
+                                Member_Attr        => 'member',
+                                Member_Attr_Value  => 'dn' });
+
+        # Run a test import
+        /opt/rt4/local/plugins/RT-Extension-LDAPImport/bin/rtldapimport \
+        --debug > ldapimport.debug 2>&1
+
+        # Run for real, possibly put in cron
+        /opt/rt4/local/plugins/RT-Extension-LDAPImport/bin/rtldapimport \
+        --import
 
 CONFIGURATION
+    All of the configuration for the importer goes your "RT_SiteConfig.pm"
+    file. Some of these values pass through to Net::LDAP so you can check
+    there for valid values and more advanced options.
+
+    "Set($LDAPHost,'our.ldap.host');"
+        Hostname or ldap(s):// uri:
+
+    "Set($LDAPUser, 'uid=foo,ou=users,dc=example,dc=com');"
+        Your LDAP username or DN. If unset, we'll attempt an anonymous bind.
+
+    "Set($LDAPPassword, 'ldap pass');"
+        Your LDAP password.
+
+    "Set($LDAPBase, 'ou=People,o=Our Place');"
+        Base object to search from.
+
+    "Set($LDAPFilter, '(&(cn = users))');"
+        The LDAP search filter to apply (in this case, find all the users).
+
+    "Set($LDAPMapping..."
+            Set($LDAPMapping, {Name         => 'uid',
+                               EmailAddress => 'mail',
+                               RealName     => 'cn',
+                               WorkPhone    => 'telephoneNumber',
+                               Organization => 'departmentName'});
+
+        This provides the mapping of attributes in RT to attribute in LDAP.
+        Only Name is required for RT.
+
+        The LDAP attributes can also be an arrayref of LDAP fields
+
+            WorkPhone => [qw/CompanyPhone Extension/]
+
+        which will be concatenated together with a space.
+
+        The LDAP attribute can also be a subroutine reference that returns
+        either an arrayref or a list of attributes.
+
+    "Set($LDAPCreatePrivileged, 1);"
+        By default users are created as Unprivileged, but you can change
+        this by setting $LDAPCreatePrivileged to 1.
+
+    "Set($LDAPGroupName,'My Imported Users');"
+        The RT Group new and updated users belong to. By default, all users
+        added or updated by the importer will belong to the 'Imported from
+        LDAP' group.
+
+    "Set($LDAPSkipAutogeneratedGroup, 1);"
+        Set this to true to prevent users from being automatically added to
+        the group configured by $LDAPGroupName.
+
+    "Set($LDAPUpdateUsers, 1);"
+        By default, existing users are skipped. If you turn on
+        LDAPUpdateUsers, we will clobber existing data with data from LDAP.
+
+    "Set($LDAPUpdateOnly, 1);"
+        By default, we create users who don't exist in RT but do match your
+        LDAP filter and obey $LDAPUpdateUsers for existing users. This
+        setting updates existing users, overriding $LDAPUpdateUsers, but
+        won't create new users who are found in LDAP but not in RT.
+
+    "Set($LDAPGroupBase, 'ou=Groups,o=Our Place');"
+        Where to search for groups to import.
+
+    "Set($LDAPGroupFilter, '(&(cn = Groups))');"
+        The search filter to apply.
+
+    "Set($LDAPGroupMapping..."
+            Set($LDAPGroupMapping, {Name               => 'cn',
+                                    Member_Attr        => 'member',
+                                    Member_Attr_Value  => 'dn' });
+
+        A mapping of RT attributes to LDAP attributes to identify group
+        members. Name will become the name of the group in RT, in this case
+        pulling from the cn attribute on the LDAP group record returned.
+
+        "Member_Attr" is the field in the LDAP group record the importer
+        should look at for group members. These values (there may be
+        multiple members) will then be compared to the RT user name, which
+        came from the LDAP user record.
+
+        "Member_Attr_Value", which defaults to 'dn', specifies where on the
+        LDAP user record the importer should look to compare the member
+        value. A match between the member field on the group record and this
+        identifier (dn or other LDAP field) on a user record means the user
+        will be added to that group in RT.
+
+        You can provide a "Description" key which will be added as the group
+        description in RT. The default description is 'Imported from LDAP'.
+
+    "Set($LDAPSizeLimit, 1000);"
+        You can set this value if your LDAP server has result size limits.
+
+Mapping Groups Between RT and LDAP
+    If you are using the importer, you likely want to manage access via LDAP
+    by putting people in groups like 'DBAs' and 'IT Support', but also have
+    groups for other non-RT related things. In this case, you won't want to
+    create all of your LDAP groups in RT. To limit the groups that get
+    mirrored, construct your $LDAPGroupFilter as an OR (|) with all of the
+    RT groups you want to mirror from LDAP. For example:
+
+        Set($LDAPGroupBase, 'OU=Groups,OU=Company,DC=COM');
+        Set($LDAPGroupFilter, '(|(CN=DBAs)(CN=IT Support))');
+
+    The importer will then import only the groups that match. In this case,
+    import means:
+
+    *   Verifying the group is in AD;
+
+    *   Creating the group in RT if it doesn't exist;
+
+    *   Populating the group with the members identified in AD;
+
+    The import script will also issue a warning if a user isn't found in RT,
+    but this should only happen when testing. When running with --import on,
+    users are created before groups are processed, so all users (group
+    members) should exist unless there are inconsistencies in your LDAP
+    configuration.
+
+Running the Import
+    Executing "rtldapimport" will run a test that connects to your LDAP
+    server and prints out a list of the users found. To see more about these
+    users, and to see more general debug information, include the "--debug"
+    flag.
+
+    That debug information is also sent to the RT log with the debug level.
+    Errors are logged to the screen and to the RT log.
+
+    Executing "rtldapimport" with the "--import" flag will cause it to
+    import users into your RT database. It is recommended that you make a
+    database backup before doing this. If your filters aren't set properly
+    this could create a lot of users or groups in your RT instance.
+
+RT Versions
+    The importer works with RT 3.8 and newer including RT 4.
+
+    It may work with RT 3.6.
+
+LDAP Filters
+    The ldapsearch
+    <http://www.openldap.org/software/man.cgi?query=ldapsearch&manpath=OpenL
+    DAP+2.0-Release> utility in openldap can be very helpful while refining
+    your filters.
+
+METHODS
+  connect_ldap
+    Relies on the config variables $RT::LDAPHost, $RT::LDAPUser and
+    $RT::LDAPPassword being set in your RT Config files.
+
+     Set($LDAPHost,'my.ldap.host')
+     Set($LDAPUSER,'me');
+     Set($LDAPPassword,'mypass');
+
+    LDAPUser and LDAPPassword can be blank, which will cause an anonymous
+    bind.
+
+    LDAPHost can be a hostname or an ldap:// ldaps:// uri.
+
+  run_user_search
+    Set up the appropriate arguments for a listing of users.
+
+  _run_search
+    Executes a search using the provided base and filter.
+
+    Will connect to LDAP server using "connect_ldap".
+
+    Returns an array of Net::LDAP::Entry objects, possibly consolidated from
+    multiple LDAP pages.
+
+  import_users import => 1|0
+    Takes the results of the search from run_search and maps attributes from
+    LDAP into "RT::User" attributes using $RT::LDAPMapping. Creates RT users
+    if they don't already exist.
+
+    With no arguments, only prints debugging information. Pass "--import" to
+    actually change data.
+
+    $RT::LDAPMapping> should be set in your "RT_SiteConfig.pm" file and look
+    like this.
+
+     Set($LDAPMapping, { RTUserField => LDAPField, RTUserField => LDAPField });
+
+    RTUserField is the name of a field on an "RT::User" object LDAPField can
+    be a simple scalar and that attribute will be looked up in LDAP.
+
+    It can also be an arrayref, in which case each of the elements will be
+    evaluated in turn. Scalars will be looked up in LDAP and concatenated
+    together with a single space.
+
+    If the value is a sub reference, it will be executed. The sub should
+    return a scalar, which will be examined. If it is a scalar, the value
+    will be looked up in LDAP. If it is an arrayref, the values will be
+    concatenated together with a single space.
+
+    By default users are created as Unprivileged, but you can change this by
+    setting $LDAPCreatePrivileged to 1.
+
+  _import_user
+    The user has run us with --import, so bring data in.
+
+  _cache_user ldap_entry => Net::LDAP::Entry, [user => { ... }]
+    Adds the user to a global cache which is used when importing groups
+    later.
+
+    Optionally takes a second argument which is a user data object returned
+    by _build_user_object. If not given, _cache_user will call
+    _build_user_object itself.
+
+    Returns the user Name.
+
+  _check_ldap_mapping
+    Returns true is there is an "LDAPMapping" configured, returns false,
+    logs an error and disconnects from ldap if there is no mapping.
+
+  _build_user_object
+    Utility method which wraps "_build_object" to provide sane defaults for
+    building users. It also tries to ensure a Name exists in the returned
+    object.
+
+  _build_object
+    Builds up data from LDAP for importing Returns a hash of user or group
+    data ready for "RT::User::Create" or "RT::Group::Create".
+
+   _parse_ldap_mapping
+    Internal helper function for "import_user". If we're passed an arrayref,
+    it will recurse over each of the elements in case one of them is another
+    arrayref or subroutine.
+
+    If we're passed a subref, it executes the code and recurses over each of
+    the returned values so that a returned array or arrayref will work.
+
+    If we're passed a scalar, returns that.
 
-There are several config variables which must be set in
-your RT_SiteConfig file
-
-Hostname or ldap(s):// uri
- Set($LDAPHost,'our.ldap.host');
-
-Your LDAP username or DN
-Leaving this unset will cause us to use an anonymous bind
- Set($LDAPUser, 'uid=foo,ou=users,dc=example,dc=com');
+    Returns a list of values that need to be concatenated together.
 
-Your LDAP Password
- Set($LDAPPassword, 'ldap pass');
+  create_rt_user
+    Takes a hashref of args to pass to "RT::User::Create" Will try loading
+    the user and will only create a new user if it can't find an existing
+    user with the "Name" or "EmailAddress" arg passed in.
 
-Where to search
- Set($LDAPBase, 'ou=People,o=Our Place');
+    If the $LDAPUpdateUsers variable is true, data in RT will be clobbered
+    with data in LDAP. Otherwise we will skip to the next user.
 
-The search filter to apply (in this case, find all the bobs)
- Set($LDAPFilter, '(&(cn = bob*))');
+    If $LDAPUpdateOnly is true, we will not create new users but we will
+    update existing ones.
 
-A mapping of 
-Attribute in RT => Attribute in LDAP
-(this has changed since version 1, which was the other way around)
- Set($LDAPMapping, {Name         => 'uid',
-                    EmailAddress => 'mail',
-                    RealName     => 'cn',
-                    WorkPhone    => 'telephoneNumber',
-                    Organization => 'departmentName'});
+  add_user_to_group
+    Adds new users to the group specified in the $LDAPGroupName variable
+    (defaults to 'Imported from LDAP'). You can avoid this if you set
+    $LDAPSkipAutogeneratedGroup.
 
-The LDAP attributes can also be an arrayref of LDAP fields
-WorkPhone => [qw/CompanyPhone Extension/] 
-which will be concatenated together with a space
+  setup_group
+    Pulls the $LDAPGroupName object out of the DB or creates it if we need
+    to do so.
 
-The LDAP attribute can also be a subroutine reference
-that returns either an arrayref or a list of attributes
+   add_custom_field_value
+    Adds values to a Select (one|many) Custom Field. The Custom Field should
+    already exist, otherwise this will throw an error and not import any
+    data.
 
-By default users are created as Unprivileged, but you can change this by
-setting $LDAPCreatePrivileged to 1.
+    This could probably use some caching.
 
-For more information on these see the import_users documentation
-in RT::Extension::LDAPImport
+  import_groups import => 1|0
+    Takes the results of the search from "run_group_search" and maps
+    attributes from LDAP into "RT::Group" attributes using
+    $RT::LDAPGroupMapping.
 
-The Group new users belong to (optional)
-All new users will belong to the 'Imported from LDAP' group
-You can change the name of this group using the $LDAPGroupName
-variable
- Set($LDAPGroupName,'Imported Users');
-If you would like to prevent users from being added to this
-group, you can set this to true:
- Set($LDAPSkipAutogeneratedGroup, 1);
+    Creates groups if they don't exist.
 
-Should we update existing users (optional)
-By default, existing users are skipped.  If you
-turn on LDAPUpdateUsers, we will clobber existing
-data with data from LDAP.
- Set($LDAPUpdateUsers,1);
+    Removes users from groups if they have been removed from the group on
+    LDAP.
 
-Should we import new users or just update existing ones?
-By default, we create users who don't exist in RT but do
-match your LDAP filter and obey $LDAPUpdateUsers for existing
-users.  This setting overrides $LDAPUpdateUsers but won't create
-users who are found in LDAP but not in RT.
- Set($LDAPUpdateOnly,1);
+    With no arguments, only prints debugging information. Pass "--import" to
+    actually change data.
 
-Where to search for groups to import
- Set($LDAPGroupBase, 'ou=Groups,o=Our Place');
+   run_group_search
+    Set up the appropriate arguments for a listing of users.
 
-The search filter to apply (in this case, find all the bobs)
- Set($LDAPGroupFilter, '(&(cn = bob*))');
+  _import_group
+    The user has run us with "--import", so bring data in.
 
-A mapping of
-Attribute in RT => Attribute in LDAP
-(this has changed since version 1, which was the other way around)
- Set($LDAPGroupMapping, {Name               => 'cn',
-                         Member_Attr        => 'member',
-                         Member_Attr_Value  => 'dn' });
+  create_rt_group
+    Takes a hashref of args to pass to "RT::Group::Create" Will try loading
+    the group and will only create a new group if it can't find an existing
+    group with the "Name" or "EmailAddress" arg passed in.
 
-The mapping logic is the same as the LDAPMapping.
-There are two important special-case keys, Member_Attr and Member_Attr_Value.
-Member_Attr tells the importer which attribute contains group members.
-Member_Attr_Value, which defaults to 'dn', specifies what kind of user values
-are in Member_Attr.  OpenLDAP, for example, often stores uid instead of dn in
-member.
+    If $LDAPUpdateOnly is true, we will not create new groups but we will
+    update existing ones.
 
-If you do not specify a Description attribute, it will be filled with
-'Imported from LDAP'
+    There is currently no way to prevent Group data from being clobbered
+    from LDAP.
 
-Your LDAP server may have result size limits.  If it does, you should set
-$LDAPSizeLimit appropriately:
- Set($LDAPSizeLimit, 1000);
+   add_group_members
+    Iterate over the list of values in the "Member_Attr" LDAP entry. Look up
+    the appropriate username from LDAP. Add those users to the group. Remove
+    members of the RT Group who are no longer members of the LDAP group.
 
-RUNNING THE IMPORT
+  _show_group
+    Show debugging information about the group record we're going to import
+    when the groups reruns us with "--import".
 
-If RT is not installed in /opt/rt3, you will need to change the 
-use lib '/opt/rt3/lib';
-line in rtldapimport to point to the directory where RT.pm can be found
+   disconnect_ldap
+    Disconnects from the LDAP server.
 
-executing rtldapimport will run a test that connects to your LDAP server
-and prints out a list of the users found.  To see more about these users,
-include the --debug flag.
+    Takes no arguments, returns nothing.
 
-executing rtldapimport with the --import flag will cause it to import
-users into your RT database.  It is recommended that you make a database
-backup before doing this.
+Utility Functions
+   screendebug
+    We always log to the RT log file with level 'debug'. This duplicates the
+    messages to the screen.
 
-rtldapimport can be run with a --debug flag that will make it 
-print a lot of information to the screen.
+BUGS AND LIMITATIONS
+    No bugs have been reported.
 
-That debug information is also sent to the RT log with the debug level.
-Errors are logged to the screen and to the RT log 
+    Please report any bugs or feature requests to
+    "bug-rt-extension-ldapimport at rt.cpan.org", or through the web interface
+    at <http://rt.cpan.org>.
 
-DEPENDENCIES
+AUTHOR
+    Kevin Falcone "<falcone at bestpractical.com>"
 
-  Class::Accessor
-  Net::LDAP
-  RT: 3.6.x
+LICENCE AND COPYRIGHT
+    Copyright (c) 2007, Best Practical Solutions, LLC. All rights reserved.
 
+    This module is free software; you can redistribute it and/or modify it
+    under the same terms as Perl itself. See perlartistic.
 
-COPYRIGHT AND LICENCE
+DISCLAIMER OF WARRANTY
+    BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+    FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+    OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+    PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
+    EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+    ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
+    YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+    NECESSARY SERVICING, REPAIR, OR CORRECTION.
 
-Copyright (C) 2007-2011, Best Practical Solutions LLC.
+    IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+    WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+    REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
+    TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
+    CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+    SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+    RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+    FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+    SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+    DAMAGES.
 
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself.
diff --git a/inc/Module/Install/ReadmeFromPod.pm b/inc/Module/Install/ReadmeFromPod.pm
new file mode 100644
index 0000000..fb7075f
--- /dev/null
+++ b/inc/Module/Install/ReadmeFromPod.pm
@@ -0,0 +1,138 @@
+#line 1
+package Module::Install::ReadmeFromPod;
+
+use 5.006;
+use strict;
+use warnings;
+use base qw(Module::Install::Base);
+use vars qw($VERSION);
+
+$VERSION = '0.18';
+
+sub readme_from {
+  my $self = shift;
+  return unless $self->is_admin;
+
+  # Input file
+  my $in_file  = shift || $self->_all_from
+    or die "Can't determine file to make readme_from";
+
+  # Get optional arguments
+  my ($clean, $format, $out_file, $options);
+  my $args = shift;
+  if ( ref $args ) {
+    # Arguments are in a hashref
+    if ( ref($args) ne 'HASH' ) {
+      die "Expected a hashref but got a ".ref($args)."\n";
+    } else {
+      $clean    = $args->{'clean'};
+      $format   = $args->{'format'};
+      $out_file = $args->{'output_file'};
+      $options  = $args->{'options'};
+    }
+  } else {
+    # Arguments are in a list
+    $clean    = $args;
+    $format   = shift;
+    $out_file = shift;
+    $options  = \@_;
+  }
+
+  # Default values;
+  $clean  ||= 0;
+  $format ||= 'txt';
+
+  # Generate README
+  print "readme_from $in_file to $format\n";
+  if ($format =~ m/te?xt/) {
+    $out_file = $self->_readme_txt($in_file, $out_file, $options);
+  } elsif ($format =~ m/html?/) {
+    $out_file = $self->_readme_htm($in_file, $out_file, $options);
+  } elsif ($format eq 'man') {
+    $out_file = $self->_readme_man($in_file, $out_file, $options);
+  } elsif ($format eq 'pdf') {
+    $out_file = $self->_readme_pdf($in_file, $out_file, $options);
+  }
+
+  if ($clean) {
+    $self->clean_files($out_file);
+  }
+
+  return 1;
+}
+
+
+sub _readme_txt {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README';
+  require Pod::Text;
+  my $parser = Pod::Text->new( @$options );
+  open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
+  $parser->output_fh( *$out_fh );
+  $parser->parse_file( $in_file );
+  close $out_fh;
+  return $out_file;
+}
+
+
+sub _readme_htm {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.htm';
+  require Pod::Html;
+  Pod::Html::pod2html(
+    "--infile=$in_file",
+    "--outfile=$out_file",
+    @$options,
+  );
+  # Remove temporary files if needed
+  for my $file ('pod2htmd.tmp', 'pod2htmi.tmp') {
+    if (-e $file) {
+      unlink $file or warn "Warning: Could not remove file '$file'.\n$!\n";
+    }
+  }
+  return $out_file;
+}
+
+
+sub _readme_man {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.1';
+  require Pod::Man;
+  my $parser = Pod::Man->new( @$options );
+  $parser->parse_from_file($in_file, $out_file);
+  return $out_file;
+}
+
+
+sub _readme_pdf {
+  my ($self, $in_file, $out_file, $options) = @_;
+  $out_file ||= 'README.pdf';
+  eval { require App::pod2pdf; }
+    or die "Could not generate $out_file because pod2pdf could not be found\n";
+  my $parser = App::pod2pdf->new( @$options );
+  $parser->parse_from_file($in_file);
+  open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
+  select $out_fh;
+  $parser->output;
+  select STDOUT;
+  close $out_fh;
+  return $out_file;
+}
+
+
+sub _all_from {
+  my $self = shift;
+  return unless $self->admin->{extensions};
+  my ($metadata) = grep {
+    ref($_) eq 'Module::Install::Metadata';
+  } @{$self->admin->{extensions}};
+  return unless $metadata;
+  return $metadata->{values}{all_from} || '';
+}
+
+'Readme!';
+
+__END__
+
+#line 254
+
diff --git a/lib/RT/Extension/LDAPImport.pm b/lib/RT/Extension/LDAPImport.pm
index 411f138..8636f31 100644
--- a/lib/RT/Extension/LDAPImport.pm
+++ b/lib/RT/Extension/LDAPImport.pm
@@ -18,27 +18,227 @@ use Data::Dumper;
 
 RT::Extension::LDAPImport - Import Users from an LDAP store
 
-
 =head1 SYNOPSIS
 
-    use RT::Extension::LDAPImport;
+    # In RT_SiteConfig.pm
+
+    Set($LDAPHost,'my.ldap.host')
+    Set($LDAPUSER,'me');
+    Set($LDAPPassword,'mypass');
+    Set($LDAPFilter, '(&(cn = users))');
+    Set($LDAPMapping, {Name         => 'uid', # required
+                       EmailAddress => 'mail',
+                       RealName     => 'cn',
+                       WorkPhone    => 'telephoneNumber',
+                       Organization => 'departmentName'});
+
+    # Add to any existing plugins
+    Set(@Plugins, qw(RT::Extension::LDAPImport));
+
+    # If you want to sync Groups RT <-> LDAP
+
+    Set($LDAPGroupBase, 'ou=Groups,o=Our Place');
+    Set($LDAPGroupFilter, '(&(cn = Groups))');
+    Set($LDAPGroupMapping, {Name               => 'cn',
+                            Member_Attr        => 'member',
+                            Member_Attr_Value  => 'dn' });
+
+    # Run a test import
+    /opt/rt4/local/plugins/RT-Extension-LDAPImport/bin/rtldapimport \
+    --debug > ldapimport.debug 2>&1
+
+    # Run for real, possibly put in cron
+    /opt/rt4/local/plugins/RT-Extension-LDAPImport/bin/rtldapimport \
+    --import
+
+=head1 CONFIGURATION
+
+All of the configuration for the importer goes
+your C<RT_SiteConfig.pm> file. Some of these values pass through
+to L<Net::LDAP> so you can check there for valid values and more
+advanced options.
+
+=over
+
+=item C<< Set($LDAPHost,'our.ldap.host'); >>
+
+Hostname or ldap(s):// uri:
+
+=item C<< Set($LDAPUser, 'uid=foo,ou=users,dc=example,dc=com'); >>
+
+Your LDAP username or DN. If unset, we'll attempt an anonymous bind.
+
+=item C<< Set($LDAPPassword, 'ldap pass'); >>
+
+Your LDAP password.
+
+=item C<< Set($LDAPBase, 'ou=People,o=Our Place'); >>
+
+Base object to search from.
+
+=item C<< Set($LDAPFilter, '(&(cn = users))'); >>
+
+The LDAP search filter to apply (in this case, find all the users).
+
+=item C<< Set($LDAPMapping... >>
+
+    Set($LDAPMapping, {Name         => 'uid',
+                       EmailAddress => 'mail',
+                       RealName     => 'cn',
+                       WorkPhone    => 'telephoneNumber',
+                       Organization => 'departmentName'});
+
+This provides the mapping of attributes in RT to attribute in LDAP.
+Only Name is required for RT.
+
+The LDAP attributes can also be an arrayref of LDAP fields
+
+    WorkPhone => [qw/CompanyPhone Extension/]
+
+which will be concatenated together with a space.
+
+The LDAP attribute can also be a subroutine reference
+that returns either an arrayref or a list of attributes.
+
+=item C<< Set($LDAPCreatePrivileged, 1); >>
+
+By default users are created as Unprivileged, but you can change this by
+setting C<$LDAPCreatePrivileged> to 1.
+
+=item C<< Set($LDAPGroupName,'My Imported Users'); >>
+
+The RT Group new and updated users belong to. By default, all users
+added or updated by the importer will belong to the 'Imported from LDAP'
+group.
+
+=item C<< Set($LDAPSkipAutogeneratedGroup, 1); >>
+
+Set this to true to prevent users from being automatically
+added to the group configured by C<$LDAPGroupName>.
+
+=item C<< Set($LDAPUpdateUsers, 1); >>
+
+By default, existing users are skipped.  If you
+turn on LDAPUpdateUsers, we will clobber existing
+data with data from LDAP.
+
+=item C<< Set($LDAPUpdateOnly, 1); >>
+
+By default, we create users who don't exist in RT but do
+match your LDAP filter and obey C<$LDAPUpdateUsers> for existing
+users.  This setting updates existing users, overriding
+C<$LDAPUpdateUsers>, but won't create new
+users who are found in LDAP but not in RT.
+
+=item C<< Set($LDAPGroupBase, 'ou=Groups,o=Our Place'); >>
+
+Where to search for groups to import.
+
+=item C<< Set($LDAPGroupFilter, '(&(cn = Groups))'); >>
+
+The search filter to apply.
+
+=item C<< Set($LDAPGroupMapping... >>
+
+    Set($LDAPGroupMapping, {Name               => 'cn',
+                            Member_Attr        => 'member',
+                            Member_Attr_Value  => 'dn' });
+
+A mapping of RT attributes to LDAP attributes to identify group members.
+Name will become the name of the group in RT, in this case pulling
+from the cn attribute on the LDAP group record returned.
+
+C<Member_Attr> is the field in the LDAP group record the importer should
+look at for group members. These values (there may be multiple members)
+will then be compared to the RT user name, which came from the LDAP
+user record.
+
+C<Member_Attr_Value>, which defaults to 'dn', specifies where on the LDAP
+user record the importer should look to compare the member value.
+A match between the member field on the group record and this
+identifier (dn or other LDAP field) on a user record means the
+user will be added to that group in RT.
+
+You can provide a C<Description> key which will be added as the group
+description in RT. The default description is 'Imported from LDAP'.
+
+=item C<< Set($LDAPSizeLimit, 1000); >>
+
+You can set this value if your LDAP server has result size limits.
+
+=back
+
+=head1 Mapping Groups Between RT and LDAP
+
+If you are using the importer, you likely want to manage access via
+LDAP by putting people in groups like 'DBAs' and 'IT Support', but
+also have groups for other non-RT related things. In this case, you
+won't want to create all of your LDAP groups in RT. To limit the groups
+that get mirrored, construct your C<$LDAPGroupFilter> as an OR (|) with
+all of the RT groups you want to mirror from LDAP. For example:
+
+    Set($LDAPGroupBase, 'OU=Groups,OU=Company,DC=COM');
+    Set($LDAPGroupFilter, '(|(CN=DBAs)(CN=IT Support))');
+
+The importer will then import only the groups that match. In this case,
+import means:
+
+=over
+
+=item * Verifying the group is in AD;
+
+=item * Creating the group in RT if it doesn't exist;
+
+=item * Populating the group with the members identified in AD;
+
+=back
+
+The import script will also issue a warning if a user isn't found in RT,
+but this should only happen when testing. When running with --import on,
+users are created before groups are processed, so all users (group
+members) should exist unless there are inconsistencies in your LDAP configuration.
+
+=head1 Running the Import
+
+Executing C<rtldapimport> will run a test that connects to your LDAP server
+and prints out a list of the users found. To see more about these users,
+and to see more general debug information, include the C<--debug> flag.
+
+That debug information is also sent to the RT log with the debug level.
+Errors are logged to the screen and to the RT log.
+
+Executing C<rtldapimport> with the C<--import> flag will cause it to import
+users into your RT database. It is recommended that you make a database
+backup before doing this. If your filters aren't set properly this could
+create a lot of users or groups in your RT instance.
+
+=head1 RT Versions
+
+The importer works with RT 3.8 and newer including RT 4.
+
+It may work with RT 3.6.
+
+=head1 LDAP Filters
+
+The L<ldapsearch|http://www.openldap.org/software/man.cgi?query=ldapsearch&manpath=OpenLDAP+2.0-Release>
+utility in openldap can be very helpful while refining your filters.
 
 =head1 METHODS
 
 =head2 connect_ldap
 
-Relies on the config variables $RT::LDAPHost,
-$RT::LDAPUser and $RT::LDAPPassword being set
+Relies on the config variables C<$RT::LDAPHost>,
+C<$RT::LDAPUser> and C<$RT::LDAPPassword> being set
 in your RT Config files.
 
- Set(LDAPHost,'my.ldap.host')
- Set(LDAPUSER,'me');
- Set(LDAPPassword,'mypass');
+ Set($LDAPHost,'my.ldap.host')
+ Set($LDAPUSER,'me');
+ Set($LDAPPassword,'mypass');
 
 LDAPUser and LDAPPassword can be blank,
 which will cause an anonymous bind.
 
-LDAPHost can be a hostname or an ldap:// ldaps:// uri
+LDAPHost can be a hostname or an ldap:// ldaps:// uri.
 
 =cut
 
@@ -73,7 +273,7 @@ sub connect_ldap {
 
 =head2 run_user_search
 
-Set up the appropriate arguments for a listing of users
+Set up the appropriate arguments for a listing of users.
 
 =cut
 
@@ -88,9 +288,9 @@ sub run_user_search {
 
 =head2 _run_search
 
-Executes a search using the provided base and filter
+Executes a search using the provided base and filter.
 
-Will connect to LDAP server using connect_ldap
+Will connect to LDAP server using C<connect_ldap>.
 
 Returns an array of L<Net::LDAP::Entry> objects, possibly consolidated from
 multiple LDAP pages.
@@ -163,23 +363,23 @@ sub _run_search {
 =head2 import_users import => 1|0
 
 Takes the results of the search from run_search
-and maps attributes from LDAP into RT::User attributes
-using $RT::LDAPMapping.
+and maps attributes from LDAP into C<RT::User> attributes
+using C<$RT::LDAPMapping>.
 Creates RT users if they don't already exist.
 
 With no arguments, only prints debugging information.
-Pass import => 1 to actually change data.
+Pass C<--import> to actually change data.
 
-RT::LDAPMapping should be set in your RT_SiteConfig
-file and looks like this.
+C<$RT::LDAPMapping>> should be set in your C<RT_SiteConfig.pm>
+file and look like this.
 
  Set($LDAPMapping, { RTUserField => LDAPField, RTUserField => LDAPField });
 
-RTUserField is the name of a field on an RT::User object
+RTUserField is the name of a field on an C<RT::User> object
 LDAPField can be a simple scalar and that attribute
-will be looked up in LDAP.  
+will be looked up in LDAP.
 
-It can also be an arrayref, in which case each of the 
+It can also be an arrayref, in which case each of the
 elements will be evaluated in turn.  Scalars will be
 looked up in LDAP and concatenated together with a single
 space.
@@ -191,7 +391,7 @@ If it is an arrayref, the values will be concatenated
 together with a single space.
 
 By default users are created as Unprivileged, but you can change this by
-setting $LDAPCreatePrivileged to 1.
+setting C<$LDAPCreatePrivileged> to 1.
 
 =cut
 
@@ -231,7 +431,7 @@ sub import_users {
 
 =head2 _import_user
 
-The user has run us with --import, so bring data in
+The user has run us with --import, so bring data in.
 
 =cut
 
@@ -308,7 +508,7 @@ sub _show_user_info {
 
 =head2 _check_ldap_mapping
 
-Returns true is there is an LDAPMapping configured,
+Returns true is there is an C<LDAPMapping> configured,
 returns false, logs an error and disconnects from
 ldap if there is no mapping.
 
@@ -331,8 +531,9 @@ sub _check_ldap_mapping {
 
 =head2 _build_user_object
 
-Utility method which wraps _build_object to provide sane defaults for building
-users.  It also tries to ensure a Name exists in the returned object.
+Utility method which wraps C<_build_object> to provide sane
+defaults for building users.  It also tries to ensure a Name
+exists in the returned object.
 
 =cut
 
@@ -350,7 +551,8 @@ sub _build_user_object {
 =head2 _build_object
 
 Builds up data from LDAP for importing
-Returns a hash of user or group data ready for RT::User::Create or RT::Group::Create
+Returns a hash of user or group data ready for
+C<RT::User::Create> or C<RT::Group::Create>.
 
 =cut
 
@@ -384,8 +586,8 @@ sub _build_object {
 
 =head3 _parse_ldap_mapping
 
-Internal helper function for import_user
-If we're passed an arrayref, it will recurse 
+Internal helper function for C<import_user>.
+If we're passed an arrayref, it will recurse
 over each of the elements in case one of them is
 another arrayref or subroutine.
 
@@ -418,16 +620,16 @@ sub _parse_ldap_mapping {
 
 =head2 create_rt_user
 
-Takes a hashref of args to pass to RT::User::Create
+Takes a hashref of args to pass to C<RT::User::Create>
 Will try loading the user and will only create a new
-user if it can't find an existing user with the Name
-or EmailAddress arg passed in.
+user if it can't find an existing user with the C<Name>
+or C<EmailAddress> arg passed in.
 
-If the $LDAPUpdateUsers variable is true, data in RT
+If the C<$LDAPUpdateUsers> variable is true, data in RT
 will be clobbered with data in LDAP.  Otherwise we
 will skip to the next user.
 
-If $LDAPUpdateOnly is true, we will not create new users
+If C<$LDAPUpdateOnly> is true, we will not create new users
 but we will update existing ones.
 
 =cut
@@ -498,9 +700,9 @@ sub _load_rt_user {
 
 =head2 add_user_to_group
 
-Adds new users to the group specified in the $LDAPGroupName
-variable (defaults to 'Imported from LDAP')
-You can avoid this if you set $LDAPSkipAutogeneratedGroup
+Adds new users to the group specified in the C<$LDAPGroupName>
+variable (defaults to 'Imported from LDAP').
+You can avoid this if you set C<$LDAPSkipAutogeneratedGroup>.
 
 =cut
 
@@ -536,8 +738,8 @@ sub add_user_to_group {
 
 =head2 setup_group
 
-Pulls the $LDAPGroupName object out of the DB or
-creates it if we ened to do so.
+Pulls the C<$LDAPGroupName> object out of the DB or
+creates it if we need to do so.
 
 =cut
 
@@ -563,7 +765,7 @@ Adds values to a Select (one|many) Custom Field.
 The Custom Field should already exist, otherwise
 this will throw an error and not import any data.
 
-This could probably use some caching
+This could probably use some caching.
 
 =cut
 
@@ -625,16 +827,16 @@ sub add_custom_field_value {
 
 =head2 import_groups import => 1|0
 
-Takes the results of the search from run_group_search
-and maps attributes from LDAP into RT::Group attributes
-using $RT::LDAPGroupMapping.
+Takes the results of the search from C<run_group_search>
+and maps attributes from LDAP into C<RT::Group> attributes
+using C<$RT::LDAPGroupMapping>.
 
-Creates groups if they don't exist
+Creates groups if they don't exist.
 
-Removes users from groups if they have been removed from the group on LDAP
+Removes users from groups if they have been removed from the group on LDAP.
 
 With no arguments, only prints debugging information.
-Pass import => 1 to actually change data.
+Pass C<--import> to actually change data.
 
 =cut
 
@@ -673,7 +875,7 @@ sub import_groups {
 
 =head3 run_group_search
 
-Set up the approviate arguments for a listing of users
+Set up the appropriate arguments for a listing of users.
 
 =cut
 
@@ -694,7 +896,7 @@ sub run_group_search {
 
 =head2 _import_group
 
-The user has run us with --import, so bring data in
+The user has run us with C<--import>, so bring data in.
 
 =cut
 
@@ -713,12 +915,12 @@ sub _import_group {
 
 =head2 create_rt_group
 
-Takes a hashref of args to pass to RT::Group::Create
+Takes a hashref of args to pass to C<RT::Group::Create>
 Will try loading the group and will only create a new
-group if it can't find an existing group with the Name
-or EmailAddress arg passed in.
+group if it can't find an existing group with the C<Name>
+or C<EmailAddress> arg passed in.
 
-If $LDAPUpdateOnly is true, we will not create new groups
+If C<$LDAPUpdateOnly> is true, we will not create new groups
 but we will update existing ones.
 
 There is currently no way to prevent Group data from being
@@ -774,7 +976,7 @@ sub create_rt_group {
 
 =head3 add_group_members
 
-Iterate over the list of values in the Member_Attr LDAP entry.
+Iterate over the list of values in the C<Member_Attr> LDAP entry.
 Look up the appropriate username from LDAP.
 Add those users to the group.
 Remove members of the RT Group who are no longer members
@@ -871,7 +1073,7 @@ sub _get_group_members_from_ldap {
 =head2 _show_group
 
 Show debugging information about the group record we're going to import
-when the groups reruns us with --import
+when the groups reruns us with C<--import>.
 
 =cut
 
@@ -917,9 +1119,9 @@ sub _show_group_info {
 
 =head3 disconnect_ldap
 
-Disconnects from the LDAP server
+Disconnects from the LDAP server.
 
-Takes no arguments, returns nothing
+Takes no arguments, returns nothing.
 
 =cut
 
@@ -937,9 +1139,8 @@ sub disconnect_ldap {
 
 =head3 screendebug
 
-We always log to the RT log file with level debug 
-
-This duplicates the messages to the screen
+We always log to the RT log file with level 'debug'. This duplicates
+the messages to the screen.
 
 =cut
 

-----------------------------------------------------------------------



More information about the Bps-public-commit mailing list