Passed
Pull Request — master (#10)
by
unknown
07:53
created

LdapAuthIntegration::getSupportedFeatures()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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