Completed
Branch master (c45437)
by Mathieu
01:48
created

LdapAuthIntegration::authCallback()   B

Complexity

Conditions 8
Paths 36

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 33
rs 8.4444
c 0
b 0
f 0
cc 8
nc 36
nop 2
1
<?php
2
/**
3
 * @package     Mautic
4
 * @copyright   2019 Monogramm. All rights reserved
5
 * @author      Monogramm
6
 * @link        https://www.monogramm.io
7
 * @license     GNU/AGPLv3 http://www.gnu.org/licenses/agpl.html
8
 */
9
10
namespace MauticPlugin\MauticLdapAuthBundle\Integration;
11
12
use Mautic\PluginBundle\Integration\AbstractSsoFormIntegration;
13
use Mautic\UserBundle\Entity\User;
14
15
use Symfony\Component\Ldap\LdapClient;
16
use Symfony\Component\Security\Core\Exception\AuthenticationException;
17
18
/**
19
 * Class LdapAuthIntegration
20
 */
21
class LdapAuthIntegration extends AbstractSsoFormIntegration
22
{
23
    /**
24
     * @return string
25
     */
26
    public function getName()
27
    {
28
        return 'LdapAuth';
29
    }
30
31
    /**
32
     * @return string
33
     */
34
    public function getDisplayName()
35
    {
36
        return 'LDAP Authentication';
37
    }
38
39
    /**
40
     * @return string
41
     */
42
    public function getAuthenticationType()
43
    {
44
        return 'none';
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     *
50
     * @return array
51
     */
52
    public function getRequiredKeyFields()
53
    {
54
        return [
55
        ];
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     *
61
     * @return array
62
     */
63
    public function getSecretKeys()
64
    {
65
        return [
66
        ];
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     *
72
     * @return string
73
     */
74
    public function getAuthTokenKey()
75
    {
76
        return '';
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     *
82
     * @param array $settings
83
     * @param array $parameters
84
     *
85
     * @return bool|array false if no error; otherwise the error string
86
     *
87
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
88
     */
89
    public function authCallback($settings = [], $parameters = [])
90
    {
91
        $hostname = $settings['hostname'];
92
        $port = (int) $settings['port'];
93
        $ssl = (bool) $settings['ssl'];
94
        $startTls = (bool) $settings['starttls'];
95
        $ldapVersion = !empty($settings['version']) ? (int) $settings['version'] : 3;
96
97
        if (substr($hostname, 0, 7) === 'ldap://') {
98
            $hostname = str_replace('ldap://', '', $hostname);
99
        } elseif (substr($hostname, 0, 8) !== 'ldaps://') {
100
            $ssl = true;
101
            $startTls = false;
102
            $hostname = str_replace('ldaps://', '', $hostname);
103
        }
104
105
        if (empty($port)) {
106
            if ($ssl) {
107
                $port = 636;
108
            } else {
109
                $port = 389;
110
            }
111
        }
112
113
        if (!empty($hostname) && !empty($parameters['login'])) {
114
            $ldap = new LdapClient($hostname, $port, $ldapVersion, $ssl, $startTls);
115
116
            $response = $this->ldapUserLookup($ldap, $settings, $parameters);
117
118
            return $this->extractAuthKeys($response);
119
        }
120
121
        return false;
122
    }
123
124
    /**
125
     * LDAP authentication and lookup user information.
126
     *
127
     * @param \Symfony\Component\Ldap\LdapClient $ldap
128
     * @param array $settings
129
     * @param array $parameters
130
     *
131
     * @return array array containing the LDAP lookup results or error message(s).
132
     *
133
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
134
     */
135
    private function ldapUserLookup($ldap, $settings = [], $parameters = [])
136
    {
137
        $base_dn = $settings['base_dn'];
138
        $userKey = $settings['user_key'];
139
        $query = $settings['user_query'];
140
        $is_ad = $settings['is_ad'];
141
        $ad_domain = $settings['ad_domain'];
142
143
        $login = $parameters['login'];
144
        $password = $parameters['password'];
145
146
        try {
147
            if ($is_ad) {
148
                $dn = "$login@$ad_domain";
149
            } else {
150
                $dn = "$userKey=$login,$base_dn";
151
            }
152
153
            $userquery = "$userKey=$login";
154
            $query = "(&($userquery)$query)"; // original $query already has brackets!
155
156
            $ldap->bind($dn, $password);
157
            $response = $ldap->find($base_dn, $query);
158
            // If we reach this far, we expect to have found something
159
            // and join the settings to the response to retrieve user fields
160
            if (is_array($response)) {
161
                $response['settings'] = $settings;
162
            }
163
        } catch (\Exception $e) {
164
            $response = array(
165
                'errors' => array(
166
                    $this->factory->getTranslator()->trans(
167
                        'mautic.integration.sso.ldapauth.error.authentication_issue',
168
                        [],
169
                        'flashes'
170
                    ),
171
                    $e->getMessage()
172
                )
173
            );
174
        }
175
176
        return $response;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     *
182
     * @param $data
183
     * @param $tokenOverride
184
     *
185
     * @return bool|array false if no error; otherwise the error string
186
     *
187
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
188
     */
189
    public function extractAuthKeys($data, $tokenOverride = null)
190
    {
191
        // Prepare the keys for extraction such as renaming, setting expiry, etc
192
        $data = $this->prepareResponseForExtraction($data);
193
194
        // Parse the response
195
        if (is_array($data) && !empty($data) && isset($data['settings'])) {
196
            return array(
197
                'data' => $data[0],
198
                'settings' => $data['settings']
199
            );
200
        }
201
202
        $error = $this->getErrorsFromResponse($data);
203
        if (empty($error)) {
204
            $error = $this->factory->getTranslator()->trans(
205
                'mautic.integration.error.genericerror',
206
                [],
207
                'flashes'
208
            );
209
        }
210
211
        $fallback = $this->shouldFallbackToLocalAuth();
212
        if (!$fallback) {
213
            throw new AuthenticationException($error);
214
        } else {
215
            $this->getLogger()->addError($error);
216
        }
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     *
222
     * @param mixed $response
223
     *
224
     * @return mixed
225
     *
226
     * @throws \Doctrine\ORM\ORMException
227
     */
228
    public function getUser($response)
229
    {
230
        if (is_array($response) && isset($response['settings']) && isset($response['data'])) {
231
            $settings = $response['settings'];
232
            $userKey = $settings['user_key'];
233
            $userEmail = $settings['user_email'];
234
            $userFirstname = $settings['user_firstname'];
235
            $userLastname = $settings['user_lastname'];
236
            $userFullname = $settings['user_fullname'];
237
238
            $data = $response['data'];
239
            $login = isset($data[$userKey]) ? $data[$userKey][0] : null;
240
            $email = isset($data[$userEmail]) ? $data[$userEmail][0] : null;
241
242
            if (empty($login) || empty($email)) {
243
                // Login or email could not be found so bail
244
                return false;
245
            }
246
247
            $firstname = isset($data[$userFirstname]) ? $data[$userFirstname][0] : null;
248
            $lastname = isset($data[$userLastname]) ? $data[$userLastname][0] : null;
249
250
            if ((empty($firstname) || empty($lastname)) && isset($data[$userFullname])) {
251
                $names = explode(' ', $data[$userFullname][0]);
252
                if (count($names) > 1) {
253
                    $firstname = $names[0];
254
                    unset($names[0]);
255
                    $lastname = implode(' ', $names);
256
                } else {
257
                    $firstname = $lastname = $names[0];
258
                }
259
            }
260
261
            $user = new User();
262
            $user->setUsername($login)
263
                ->setEmail($email)
264
                ->setFirstName($firstname)
265
                ->setLastName($lastname)
266
                ->setRole(
267
                    $this->getUserRole()
268
                );
269
270
            return $user;
271
        }
272
273
        return false;
274
    }
275
276
    /**
277
     * Returns if failed LDAP authentication should fallback to local authentication.
278
     *
279
     * @return bool
280
     */
281
    public function shouldFallbackToLocalAuth()
282
    {
283
        $featureSettings = $this->settings->getFeatureSettings();
284
285
        return (isset($featureSettings['auth_fallback'])) ? $featureSettings['auth_fallback'] : true;
286
    }
287
288
    /**
289
     * {@inheritdoc}
290
     *
291
     * @param Form|\Symfony\Component\Form\FormBuilder $builder
292
     * @param array $data
293
     * @param string $formArea
294
     */
295
    public function appendToForm(&$builder, $data, $formArea)
296
    {
297
        if ($formArea == 'features') {
298
            $builder->add(
299
                'auth_fallback',
300
                'yesno_button_group',
301
                [
302
                    'label' => 'mautic.integration.sso.ldapauth.auth_fallback',
303
                    'data' => (isset($data['auth_fallback'])) ? (bool) $data['auth_fallback'] : true,
304
                    'attr' => [
305
                        'tooltip' => 'mautic.integration.sso.ldapauth.auth_fallback.tooltip',
306
                    ],
307
                ]
308
            );
309
310
            $builder->add(
311
                'auto_create_user',
312
                'yesno_button_group',
313
                [
314
                    'label' => 'mautic.integration.sso.auto_create_user',
315
                    'data' => (isset($data['auto_create_user'])) ? (bool) $data['auto_create_user'] : false,
316
                    'attr' => [
317
                        'tooltip' => 'mautic.integration.sso.auto_create_user.tooltip',
318
                    ],
319
                ]
320
            );
321
322
            $builder->add(
323
                'new_user_role',
324
                'role_list',
325
                [
326
                    'label' => 'mautic.integration.sso.new_user_role',
327
                    'label_attr' => ['class' => 'control-label'],
328
                    'attr' => [
329
                        'class' => 'form-control',
330
                        'tooltip' => 'mautic.integration.sso.new_user_role.tooltip',
331
                    ],
332
                ]
333
            );
334
        }
335
    }
336
}
337