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