1 |
package A3C::LDAP; |
2 |
|
3 |
use strict; |
4 |
use warnings; |
5 |
|
6 |
use Net::LDAP; |
7 |
use Data::Dump qw/dump/; |
8 |
use base qw(Jifty::Object Class::Accessor::Fast); |
9 |
our @config_fields = keys %{ Jifty->config->app('LDAP') }; |
10 |
Jifty->log->debug("using fields from configuration: ",dump( @config_fields )); |
11 |
__PACKAGE__->mk_accessors( qw(ldap current_search), @config_fields ); |
12 |
|
13 |
|
14 |
=head1 NAME |
15 |
|
16 |
A3C::LDAP |
17 |
|
18 |
=head1 DESCRIPTION |
19 |
|
20 |
This object turn L<Net::LDAP> into something with looks like |
21 |
L<Jifty::Collection> |
22 |
|
23 |
=head1 METHODS |
24 |
|
25 |
=head2 new |
26 |
|
27 |
my $ldap = A3C::LDAP->new; |
28 |
|
29 |
=cut |
30 |
|
31 |
sub new { |
32 |
my $class = shift; |
33 |
|
34 |
my $args = { @_ }; |
35 |
|
36 |
my $ldap_config = Jifty->config->app('LDAP'); |
37 |
Jifty->log->debug( "config->app(LDAP) = ",dump( $ldap_config ) ); |
38 |
|
39 |
foreach my $f ( @config_fields ) { |
40 |
if ( my $v = $ldap_config->{$f} ) { |
41 |
$args->{$f} = $v; |
42 |
} |
43 |
} |
44 |
|
45 |
# configuration sanity testing |
46 |
foreach ( qw/server dn password base objectClass link/ ) { |
47 |
die "missing required field $_ in LDAP from etc/config.yaml" unless $args->{$_}; |
48 |
} |
49 |
foreach ( qw/person organization/ ) { |
50 |
die "missing required field $_ in LDAP.objectClass.$_ from etc/config.yaml" unless $args->{objectClass}->{$_}; |
51 |
} |
52 |
foreach ( qw/person_filter display_from value_from/ ) { |
53 |
die "missing required field $_ in LDAP.link.$_ from etc/config.yaml" unless $args->{link}->{$_}; |
54 |
} |
55 |
|
56 |
my $ldap = Net::LDAP->new( $args->{server} ) or die "$@"; |
57 |
|
58 |
# an anonymous bind |
59 |
#$ldap->bind; |
60 |
$ldap->bind( $args->{dn}, password => $args->{password} ); |
61 |
|
62 |
Jifty->log->info("Connected to ", $args->{server}, " with DN ", $args->{dn}); |
63 |
|
64 |
$args->{ldap} = $ldap; |
65 |
|
66 |
$class->SUPER::new( $args ); |
67 |
} |
68 |
|
69 |
=head2 search |
70 |
|
71 |
my $msg = A3C::LDAP->search( |
72 |
base => 'dc=skole,dc=hr', |
73 |
filter => '(objectClass=hrEduOrg)', |
74 |
sizelimit => 10, |
75 |
); |
76 |
|
77 |
=cut |
78 |
|
79 |
sub search { |
80 |
my $self = shift; |
81 |
|
82 |
my $search = $self->ldap->search( @_ ); |
83 |
if ( $search->code != 0 ) { |
84 |
Jifty->log->error( $search->error, ' for ', dump( @_ ) ); |
85 |
} |
86 |
return $self->current_search( $search ); |
87 |
} |
88 |
|
89 |
=head2 next |
90 |
|
91 |
Syntaxtic shugar to look more like L<Jifty::DBI::Collection> |
92 |
|
93 |
my $entry = ldap->next; |
94 |
|
95 |
=cut |
96 |
|
97 |
sub next { |
98 |
my $self = shift; |
99 |
|
100 |
die "no current LDAP search" unless $self->current_search; |
101 |
|
102 |
return $self->current_search->shift_entry; |
103 |
} |
104 |
|
105 |
=head2 count |
106 |
|
107 |
my $search_results = $ldap->count; |
108 |
|
109 |
=cut |
110 |
|
111 |
sub count { |
112 |
my $self = shift; |
113 |
$self->current_search->count; |
114 |
} |
115 |
|
116 |
=head2 collection |
117 |
|
118 |
my $connection = $ldap->collection( |
119 |
# name of model to use |
120 |
$ldap->objectClass->{organization}, |
121 |
# optional params |
122 |
limit => $limit, |
123 |
filter => '(uid=foobar)', |
124 |
); |
125 |
|
126 |
=cut |
127 |
|
128 |
my $collection2filter = { |
129 |
'Person' => '(objectClass=hrEduPerson)', |
130 |
'Organization' => '(objectClass=hrEduOrg)', |
131 |
}; |
132 |
|
133 |
sub collection { |
134 |
my $self = shift; |
135 |
my $model = shift or die "no model?"; |
136 |
my $args = {@_}; |
137 |
|
138 |
$args->{limit} ||= 0; # unlimited by default |
139 |
|
140 |
my $filter = $collection2filter->{$model}; |
141 |
# die "unknown model $model" unless $filter; |
142 |
# fallback to model named as objectClass |
143 |
$filter ||= "(objectClass=$model)"; |
144 |
|
145 |
# add user filter |
146 |
$filter = '(&' . $filter . $args->{filter} . ')' if $args->{filter}; |
147 |
|
148 |
$self->search( |
149 |
base => $self->base, |
150 |
filter => $filter, |
151 |
sizelimit => $args->{limit}, |
152 |
); |
153 |
|
154 |
Jifty->log->info( |
155 |
"Searching LDAP for $model with $filter ", |
156 |
$args->{limit} ? 'limit ' . $args->{limit} . ' ' : '', |
157 |
'returned ', $self->count, ' results' |
158 |
); |
159 |
|
160 |
my $class = Jifty->app_class('Model', $model . 'Collection' ) or die "can't create ${model}Collection"; |
161 |
my $collection = $class->new() or die "can't $class->new"; |
162 |
|
163 |
while ( my $entry = $self->next ) { |
164 |
my $model_obj = Jifty->app_class('Model',$model)->new; |
165 |
my $additional; |
166 |
$self->model_from_entry( $model_obj, $entry, %$additional ); |
167 |
$collection->add_record( $model_obj ); |
168 |
} |
169 |
|
170 |
return $collection; |
171 |
} |
172 |
|
173 |
=head1 INTERNAL METHODS |
174 |
|
175 |
Following methods map directly into L<Net::LDAP> |
176 |
|
177 |
=head2 current_search |
178 |
|
179 |
Result of last C<< $ldap->search >> request |
180 |
|
181 |
=head2 model_from_entry |
182 |
|
183 |
$ldap->model_from_entry( $model, $entry, $additional ); |
184 |
|
185 |
This method will join repeatable attributes by magic marker, |
186 |
see C<XXX> in code! |
187 |
|
188 |
=cut |
189 |
|
190 |
sub model_from_entry { |
191 |
my ( $self, $model, $entry, $additional ) = @_; |
192 |
my $data; |
193 |
|
194 |
my @columns = map { $_->name } $model->columns; |
195 |
#warn "# columns = ",dump( @columns ); |
196 |
|
197 |
foreach my $attr ( $entry->attributes ) { |
198 |
if ( grep(/^\Q$attr\E$/, @columns ) ) { |
199 |
# $data->{$attr} = $entry->get_value( $attr ); |
200 |
my @var = $entry->get_value( $attr ); |
201 |
# warn "--- $attr = ",dump( @var ); |
202 |
# XXX this rolls repeatable values into single field |
203 |
my $var = join(' <*> ', @var); |
204 |
$data->{$attr} = $var; |
205 |
# } elsif ( $attr !~ m/^(objectClass)$/i ) { |
206 |
} else { |
207 |
Jifty->log->warn(ref($model)," doesn't have $attr"); |
208 |
} |
209 |
} |
210 |
|
211 |
Jifty->log->debug( ref($model), ' = ', dump( $data ) ); |
212 |
|
213 |
my ( $id, $message ) = $model->load_or_create( %$data, %$additional ); |
214 |
|
215 |
if ( $id ) { |
216 |
Jifty->log->info( $message || 'Added', ' ', ref($model), ' ', $model->id, ' ', $model->name ); |
217 |
} else { |
218 |
Jifty->log->error( ref($model), " ", $message ); |
219 |
} |
220 |
} |
221 |
|
222 |
|
223 |
|
224 |
1; |