Commit 82891237 authored by Jim Brandt's avatar Jim Brandt Committed by Thomas Sibley
Browse files

Documentation updates.

Moved install instructions to INSTALL.

README is now generated from the pod via ReadmeFromPod.

Some more updates to pod in main file.
parent 8c4e83f2
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.
......@@ -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');
......
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.
#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';