Merge branch 'master' of github.com:kivitendo/kivitendo-erp
[kivitendo-erp.git] / SL / Auth / LDAP.pm
1 package SL::Auth::LDAP;
2
3 use English '-no_match_vars';
4
5 use Scalar::Util qw(weaken);
6 use SL::Auth::Constants qw(:all);
7
8 use strict;
9
10 sub new {
11   $main::lxdebug->enter_sub();
12
13   if (!defined eval "require Net::LDAP;") {
14     die 'The module "Net::LDAP" is not installed.';
15   }
16
17   my $type = shift;
18   my $self = {};
19
20   $self->{auth} = shift;
21   weaken $self->{auth};
22
23   bless $self, $type;
24
25   $main::lxdebug->leave_sub();
26
27   return $self;
28 }
29
30 sub reset {
31   my ($self) = @_;
32   $self->{ldap}     = undef;
33   $self->{dn_cache} = { };
34 }
35
36 sub _connect {
37   $main::lxdebug->enter_sub();
38
39   my $self = shift;
40   my $cfg  = $self->{auth}->{LDAP_config};
41
42   if ($self->{ldap}) {
43     $main::lxdebug->leave_sub();
44
45     return $self->{ldap};
46   }
47
48   my $port      = $cfg->{port} || 389;
49   $self->{ldap} = Net::LDAP->new($cfg->{host}, 'port' => $port);
50
51   if (!$self->{ldap}) {
52     $main::form->error($main::locale->text('The LDAP server "#1:#2" is unreachable. Please check config/kivitendo.conf.', $cfg->{host}, $port));
53   }
54
55   if ($cfg->{tls}) {
56     my $mesg = $self->{ldap}->start_tls('verify' => 'none');
57     if ($mesg->is_error()) {
58       $main::form->error($main::locale->text('The connection to the LDAP server cannot be encrypted (SSL/TLS startup failure). Please check config/kivitendo.conf.'));
59     }
60   }
61
62   if ($cfg->{bind_dn}) {
63     my $mesg = $self->{ldap}->bind($cfg->{bind_dn}, 'password' => $cfg->{bind_password});
64     if ($mesg->is_error()) {
65       $main::form->error($main::locale->text('Binding to the LDAP server as "#1" failed. Please check config/kivitendo.conf.', $cfg->{bind_dn}));
66     }
67   }
68
69   $main::lxdebug->leave_sub();
70
71   return $self->{ldap};
72 }
73
74 sub _get_filter {
75   $main::lxdebug->enter_sub();
76
77   my $self   = shift;
78   my $login  = shift;
79
80   my ($cfg, $filter);
81
82   $cfg    =  $self->{auth}->{LDAP_config};
83
84   $filter =  "$cfg->{filter}";
85   $filter =~ s|^\s+||;
86   $filter =~ s|\s+$||;
87
88   $login  =~ s|\\|\\\\|g;
89   $login  =~ s|\(|\\\(|g;
90   $login  =~ s|\)|\\\)|g;
91   $login  =~ s|\*|\\\*|g;
92   $login  =~ s|\x00|\\00|g;
93
94   if ($filter =~ m|<\%login\%>|) {
95     substr($filter, $LAST_MATCH_START[0], $LAST_MATCH_END[0] - $LAST_MATCH_START[0]) = $login;
96
97   } elsif ($filter) {
98     if ((substr($filter, 0, 1) ne '(') || (substr($filter, -1, 1) ne ')')) {
99       $filter = "($filter)";
100     }
101
102     $filter = "(&${filter}($cfg->{attribute}=${login}))";
103
104   } else {
105     $filter = "$cfg->{attribute}=${login}";
106
107   }
108
109   $main::lxdebug->leave_sub();
110
111   return $filter;
112 }
113
114 sub _get_user_dn {
115   $main::lxdebug->enter_sub();
116
117   my $self   = shift;
118   my $ldap   = shift;
119   my $login  = shift;
120
121   $self->{dn_cache} ||= { };
122
123   if ($self->{dn_cache}->{$login}) {
124     $main::lxdebug->leave_sub();
125     return $self->{dn_cache}->{$login};
126   }
127
128   my $cfg    = $self->{auth}->{LDAP_config};
129
130   my $filter = $self->_get_filter($login);
131
132   my $mesg   = $ldap->search('base' => $cfg->{base_dn}, 'scope' => 'sub', 'filter' => $filter);
133
134   if ($mesg->is_error() || (0 == $mesg->count())) {
135     $main::lxdebug->leave_sub();
136     return undef;
137   }
138
139   my $entry                   = $mesg->entry(0);
140   $self->{dn_cache}->{$login} = $entry->dn();
141
142   $main::lxdebug->leave_sub();
143
144   return $self->{dn_cache}->{$login};
145 }
146
147 sub authenticate {
148   $main::lxdebug->enter_sub();
149
150   my $self       = shift;
151   my $login      = shift;
152   my $password   = shift;
153   my $is_crypted = shift;
154
155   if ($is_crypted) {
156     $main::lxdebug->leave_sub();
157     return ERR_BACKEND;
158   }
159
160   my $ldap = $self->_connect();
161
162   if (!$ldap) {
163     $main::lxdebug->leave_sub();
164     return ERR_BACKEND;
165   }
166
167   my $dn = $self->_get_user_dn($ldap, $login);
168
169   $main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: dn $dn");
170
171   if (!$dn) {
172     $main::lxdebug->leave_sub();
173     return ERR_BACKEND;
174   }
175
176   my $mesg = $ldap->bind($dn, 'password' => $password);
177
178   $main::lxdebug->message(LXDebug->DEBUG2(), "LDAP authenticate: bind mesg " . $mesg->error());
179
180   $main::lxdebug->leave_sub();
181
182   return $mesg->is_error() ? ERR_PASSWORD : OK;
183 }
184
185 sub can_change_password {
186   return 0;
187 }
188
189 sub requires_cleartext_password {
190   return 1;
191 }
192
193 sub change_password {
194   return ERR_BACKEND;
195 }
196
197 sub verify_config {
198   $main::lxdebug->enter_sub();
199
200   my $form   = $main::form;
201   my $locale = $main::locale;
202
203   my $self = shift;
204   my $cfg  = $self->{auth}->{LDAP_config};
205
206   if (!$cfg) {
207     $form->error($locale->text('config/kivitendo.conf: Key "authentication/ldap" is missing.'));
208   }
209
210   if (!$cfg->{host} || !$cfg->{attribute} || !$cfg->{base_dn}) {
211     $form->error($locale->text('config/kivitendo.conf: Missing parameters in "authentication/ldap". Required parameters are "host", "attribute" and "base_dn".'));
212   }
213
214   $main::lxdebug->leave_sub();
215 }
216
217 1;