Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'documentation-updates'

  • Loading branch information...
commit a756cbfe5c5774356f810c6186a544087f74b46d 2 parents d524ce7 + 8289123
@tsibley tsibley authored
View
41 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.
View
1  Makefile.PL
@@ -5,6 +5,7 @@ author ('Kevin Falcone <falcone@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');
View
456 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@rt.cpan.org", or through the web interface
+ at <http://rt.cpan.org>.
-DEPENDENCIES
+AUTHOR
+ Kevin Falcone "<falcone@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.
View
138 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
+
View
315 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
Please sign in to comment.
Something went wrong with that request. Please try again.