Commit 05f15db9 authored by Thomas Sibley's avatar Thomas Sibley
Browse files

Merge branch 'object-cf-values'

parents 05d216b4 95a1def8
......@@ -13,6 +13,8 @@ inc/Module/Install/RTx.pm
inc/Module/Install/Substitute.pm
inc/Module/Install/Win32.pm
inc/Module/Install/WriteAll.pm
inc/Module/Install/ReadmeFromPod.pm
INSTALL
INSTALL.SKIP
lib/RT/Extension/LDAPImport.pm
Makefile.PL
......@@ -27,3 +29,4 @@ t/pod-coverage.t
t/pod.t
t/user-import-privileged.t
t/user-import.t
t/user-import-cfs.t
......@@ -28,4 +28,4 @@ requires:
Test::More: 0
resources:
license: http://dev.perl.org/licenses/
version: 0.32_03
version: 0.32_04
......@@ -74,6 +74,22 @@ CONFIGURATION
The LDAP attribute can also be a subroutine reference that returns
either an arrayref or a list of attributes.
The keys in the mapping (i.e. the RT fields, the left hand side) may
be a user custom field name prefixed with "UserCF.", for example
"'UserCF.Employee Number' => 'employeeId'". Note that this only adds
values at the moment, which on single value CFs will remove any old
value first. Multiple value CFs may behave not quite how you expect.
If the attribute no longer exists on a user in LDAP, it will be
cleared on the RT side as well.
You may also prefix any RT custom field name with "CF." inside your
mapping to add available values to a Select custom field. This
effectively takes user attributes in LDAP and adds the values as
selectable options in a CF. It does not set a CF value on any RT
object (User, Ticket, Queue, etc). You might use this to populate a
ticket Location CF with all the locations of your users so that
tickets can be associated with the locations in use.
"Set($LDAPCreatePrivileged, 1);"
By default users are created as Unprivileged, but you can change
this by setting $LDAPCreatePrivileged to 1.
......@@ -298,6 +314,15 @@ METHODS
This could probably use some caching.
update_object_custom_field_values
Adds CF values to an object (currently only users). The Custom Field
should already exist, otherwise this will throw an error and not import
any data.
Note that this code only adds values at the moment, which on single
value CFs will remove any old value first. Multiple value CFs may behave
not quite how you expect.
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
......
package RT::Extension::LDAPImport;
# XXX TODO: For historical reasons, this should become 0.33 after 0.32_xx dev releases are done.
our $VERSION = '0.32_03';
our $VERSION = '0.32_04';
use warnings;
use strict;
......@@ -102,6 +102,20 @@ 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.
The keys in the mapping (i.e. the RT fields, the left hand side) may be a user
custom field name prefixed with C<UserCF.>, for example C<< 'UserCF.Employee
Number' => 'employeeId' >>. Note that this only B<adds> values at the moment,
which on single value CFs will remove any old value first. Multiple value CFs
may behave not quite how you expect. If the attribute no longer exists on a
user in LDAP, it will be cleared on the RT side as well.
You may also prefix any RT custom field name with C<CF.> inside your mapping to
add available values to a Select custom field. This effectively takes user
attributes in LDAP and adds the values as selectable options in a CF. It does
B<not> set a CF value on any RT object (User, Ticket, Queue, etc). You might
use this to populate a ticket Location CF with all the locations of your users
so that tickets can be associated with the locations in use.
=item C<< Set($LDAPCreatePrivileged, 1); >>
By default users are created as Unprivileged, but you can change this by
......@@ -449,6 +463,7 @@ sub _import_user {
$self->add_user_to_group( %args );
$self->add_custom_field_value( %args );
$self->update_object_custom_field_values( %args, object => $args{user} );
return 1;
}
......@@ -542,7 +557,7 @@ exists in the returned object.
sub _build_user_object {
my $self = shift;
my $user = $self->_build_object(
skip => qr/(?i)^CF\./,
skip => qr/(?i)^(?:User)?CF\./,
mapping => $RT::LDAPMapping,
@_
);
......@@ -827,6 +842,52 @@ sub add_custom_field_value {
}
=head3 update_object_custom_field_values
Adds CF values to an object (currently only users). The Custom Field should
already exist, otherwise this will throw an error and not import any data.
Note that this code only B<adds> values at the moment, which on single value
CFs will remove any old value first. Multiple value CFs may behave not quite
how you expect.
=cut
sub update_object_custom_field_values {
my $self = shift;
my %args = @_;
my $obj = $args{object};
foreach my $rtfield ( keys %{$RT::LDAPMapping} ) {
# XXX TODO: accept GroupCF when we call this from group_import too
next unless $rtfield =~ /^UserCF\.(.+)$/i;
my $cf_name = $1;
my $ldap_attribute = $RT::LDAPMapping->{$rtfield};
my @attributes = $self->_parse_ldap_mapping($ldap_attribute);
unless (@attributes) {
$self->_error("Invalid LDAP mapping for $rtfield ".Dumper($ldap_attribute));
next;
}
my $value = join ' ',
grep { defined and length }
map { scalar $args{ldap_entry}->get_value($_) }
@attributes;
if (($obj->FirstCustomFieldValue($cf_name) || '') eq ($value || '')) {
$self->_debug($obj->Name . ": Value '$value' is already set for '$cf_name'");
next;
}
$self->_debug($obj->Name . ": Adding object value '$value' for '$cf_name'");
next unless $args{import};
my ($ok, $msg) = $obj->AddCustomFieldValue( Field => $cf_name, Value => $value );
$self->_error($obj->Name . ": Couldn't add value '$value' for '$cf_name': $msg")
unless $ok;
}
}
=head2 import_groups import => 1|0
Takes the results of the search from C<run_group_search>
......@@ -912,6 +973,7 @@ sub _import_group {
my ($group_obj, $created) = $self->create_rt_group( %args, group => $group );
return if $args{import} and not $group_obj;
$self->add_group_members( %args, name => $group->{Name}, group => $group_obj, ldap_entry => $ldap_entry, new => $created );
# XXX TODO: support OCFVs for groups too
return;
}
......
use strict;
use warnings;
use lib 't/lib';
use RT::Extension::LDAPImport::Test tests => 7 + 13*3 + 3 + 2*2 + 1;
eval { require Net::LDAP::Server::Test; 1; } or do {
plan skip_all => 'Unable to test without Net::Server::LDAP::Test';
};
use Net::LDAP::Entry;
use RT::User;
{
my $cf = RT::CustomField->new(RT->SystemUser);
my ($ok, $msg) = $cf->Create(
Name => 'Employee Number',
LookupType => 'RT::User',
Type => 'FreeformSingle',
Disabled => 0,
);
ok $cf->Id, $msg;
my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
($ok, $msg) = $ocf->Create( CustomField => $cf->Id );
ok $ocf->Id, $msg;
}
my $importer = RT::Extension::LDAPImport->new;
isa_ok($importer,'RT::Extension::LDAPImport');
my $ldap_port = 1024 + int rand(10000) + $$ % 1024;
ok( my $server = Net::LDAP::Server::Test->new( $ldap_port, auto_schema => 1 ),
"spawned test LDAP server on port $ldap_port");
my $ldap = Net::LDAP->new("localhost:$ldap_port");
$ldap->bind();
my @ldap_entries;
for ( 1 .. 13 ) {
my $username = "testuser$_";
my $dn = "uid=$username,ou=foo,dc=bestpractical,dc=com";
my $entry = {
cn => "Test User $_ ".int rand(200),
mail => "$username\@invalid.tld",
uid => $username,
employeeId => $_,
objectClass => 'User',
};
push @ldap_entries, { dn => $dn, %$entry };
$ldap->add( $dn, attr => [%$entry] );
}
RT->Config->Set('LDAPHost',"ldap://localhost:$ldap_port");
RT->Config->Set('LDAPMapping',
{Name => 'uid',
EmailAddress => 'mail',
RealName => 'cn',
'UserCF.Employee Number' => 'employeeId',});
RT->Config->Set('LDAPBase','ou=foo,dc=bestpractical,dc=com');
RT->Config->Set('LDAPFilter','(objectClass=User)');
$importer->screendebug(1) if ($ENV{TEST_VERBOSE});
# check that we don't import
ok($importer->import_users());
{
my $users = RT::Users->new($RT::SystemUser);
for my $username (qw/RT_System root Nobody/) {
$users->Limit( FIELD => 'Name', OPERATOR => '!=', VALUE => $username, ENTRYAGGREGATOR => 'AND' );
}
is($users->Count,0);
}
# check that we do import
ok($importer->import_users( import => 1 ));
for my $entry (@ldap_entries) {
my $user = RT::User->new($RT::SystemUser);
$user->LoadByCols( EmailAddress => $entry->{mail},
Realname => $entry->{cn},
Name => $entry->{uid} );
ok($user->Id, "Found $entry->{cn} as ".$user->Id);
ok(!$user->Privileged, "User created as Unprivileged");
is($user->FirstCustomFieldValue('Employee Number'), $entry->{employeeId}, "cf is good");
}
# import again, check that it was cleared
{
my $delete = $ldap_entries[0];
$ldap->modify( $delete->{dn}, delete => ['employeeId'] );
delete $delete->{employeeId};
my $update = $ldap_entries[1];
$ldap->modify( $update->{dn}, replace => ['employeeId' => 42] );
$update->{employeeId} = 42;
ok($importer->import_users( import => 1 ));
for my $entry (@ldap_entries[0,1]) {
my $user = RT::User->new($RT::SystemUser);
$user->LoadByCols( EmailAddress => $entry->{mail},
Realname => $entry->{cn},
Name => $entry->{uid} );
ok($user->Id, "Found $entry->{cn} as ".$user->Id);
is($user->FirstCustomFieldValue('Employee Number'), $entry->{employeeId}, "cf is updated");
}
}
# can't unbind earlier or the server will die
$ldap->unbind;
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment