Skip to content
Browse files

Merge branch 'object-cf-values'

  • Loading branch information...
2 parents 05d216b + 95a1def commit 05f15db97932d4da80403a09ad02f7cc9d252cf6 @tsibley tsibley committed May 24, 2012
Showing with 200 additions and 3 deletions.
  1. +3 −0 MANIFEST
  2. +1 −1 META.yml
  3. +25 −0 README
  4. +64 −2 lib/RT/Extension/LDAPImport.pm
  5. +107 −0 t/user-import-cfs.t
View
3 MANIFEST
@@ -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
View
2 META.yml
@@ -28,4 +28,4 @@ requires:
Test::More: 0
resources:
license: http://dev.perl.org/licenses/
-version: 0.32_03
+version: 0.32_04
View
25 README
@@ -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
View
66 lib/RT/Extension/LDAPImport.pm
@@ -1,7 +1,7 @@
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;
}
View
107 t/user-import-cfs.t
@@ -0,0 +1,107 @@
+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;

0 comments on commit 05f15db

Please sign in to comment.
Something went wrong with that request. Please try again.