Completed
Pull Request — master (#10)
by
unknown
08:09
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
19
use Symfony\Component\Ldap\LdapClient;
20
use Symfony\Component\Security\Core\Exception\AuthenticationException;
21
22
/**
23
 * Class LdapAuthIntegration
24
 */
25
class LdapAuthIntegration extends AbstractSsoFormIntegration
26
{
27
    /**
28
     * @return string
29
     */
30
    public function getName()
31
    {
32
        return 'LdapAuth';
33
    }
34
35
    /**
36
     * @return string
37
     */
38
    public function getDisplayName()
39
    {
40
        return 'LDAP Authentication';
41
    }
42
43
    /**
44
     * @return string
45
     */
46
    public function getAuthenticationType()
47
    {
48
        return 'none';
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     *
54
     * @return array
55
     */
56
    public function getRequiredKeyFields()
57
    {
58
        return [
59
        ];
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     *
65
     * @return array
66
     */
67
    public function getSecretKeys()
68
    {
69
        return [
70
        ];
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     *
76
     * @return string
77
     */
78
    public function getAuthTokenKey()
79
    {
80
        return '';
81
    }
82
    /**
83
     * @return array
84
     */
85
    public function getSupportedFeatures()
86
    {
87
        return [
88
            'sso_service',
89
        ];
90
    }
91
    /**
92
     * {@inheritdoc}
93
     *
94
     * @param array $settings
95
     * @param array $parameters
96
     *
97
     * @return bool|array false if no error; otherwise the error string
98
     *
99
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
100
     */
101
    public function authCallback($settings = [], $parameters = [])
102
    {
103
        //die('authcallback');
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
        $base_dn = $settings['base_dn'];
150
        $userKey = $settings['user_key'];
151
        $query = $settings['user_query'];
152
        $is_ad = $settings['is_ad'];
153
        $ad_domain = $settings['ad_domain'];
154
        $basic_auth= false;
155
156
        if (isset($_SERVER["PHP_AUTH_USER"]) && !empty($_SERVER["PHP_AUTH_USER"])) {
157
            $basic_auth= true;
158
            $login = $_SERVER["PHP_AUTH_USER"];
159
        }else
160
            $login = $parameters['login'];
161
        $password = $parameters['password'];
162
163
        try {
164
            if ($is_ad) {
165
                if (!$basic_auth)
166
                    $dn = "$login@$ad_domain";
167
                else{
168
                    $dn = "cs-LDAPReader@$ad_domain";
169
                    $password = "LDre2012";
170
                }
171
            } else {
172
                $dn = "$userKey=$login,$base_dn";
173
            }
174
175
            $userquery = "$userKey=$login";
176
            $query = "(&($userquery)$query)"; // original $query already has brackets!
177
            $ldap->bind($dn, $password);
178
            $response = $ldap->find($base_dn, $query);
179
180
            // If we reach this far, we expect to have found something
181
            // and join the settings to the response to retrieve user fields
182
            if (is_array($response)) {
183
                $response['settings'] = $settings;
184
            }
185
        } catch (\Exception $e) {
186
            $response = array(
187
                'errors' => array(
188
                    $this->factory->getTranslator()->trans(
189
                        'mautic.integration.sso.ldapauth.error.authentication_issue',
190
                        [],
191
                        'flashes'
192
                    ),
193
                    $e->getMessage()
194
                )
195
            );
196
        }
197
198
        return $response;
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     *
204
     * @param $data
205
     * @param $tokenOverride
206
     *
207
     * @return bool|array false if no error; otherwise the error string
208
     *
209
     * @throws \Symfony\Component\Security\Core\Exception\AuthenticationException
210
     */
211
    public function extractAuthKeys($data, $tokenOverride = null)
212
    {
213
        // Prepare the keys for extraction such as renaming, setting expiry, etc
214
        $data = $this->prepareResponseForExtraction($data);
215
216
        // Parse the response
217
        if (is_array($data) && !empty($data) && isset($data['settings'])) {
218
            return array(
219
                'data' => $data[0],
220
                'settings' => $data['settings']
221
            );
222
        }
223
224
        $error = $this->getErrorsFromResponse($data);
225
        if (empty($error)) {
226
            $error = $this->factory->getTranslator()->trans(
227
                'mautic.integration.error.genericerror',
228
                [],
229
                'flashes'
230
            );
231
        }
232
233
        $fallback = $this->shouldFallbackToLocalAuth();
234
        if (!$fallback) {
235
            throw new AuthenticationException($error);
236
        } else {
237
            $this->getLogger()->addError($error);
238
        }
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     *
244
     * @param mixed $response
245
     *
246
     * @return mixed
247
     *
248
     * @throws \Doctrine\ORM\ORMException
249
     */
250
    public function getUser($response)
251
    {
252
        if (is_array($response) && isset($response['settings']) && isset($response['data'])) {
253
            $settings = $response['settings'];
254
            $userKey = $settings['user_key'];
255
            $userEmail = $settings['user_email'];
256
            //fallback to principal in case of email missing
257
            $fbEmail = 'userprincipalname';
258
            $userFirstname = $settings['user_firstname'];
259
            $userLastname = $settings['user_lastname'];
260
            $userFullname = $settings['user_fullname'];
261
262
            $data = $response['data'];
263
264
            $login = self::arrayGet($data, $userKey, [null])[0];
265
            $email = self::arrayGet($data, $userEmail, [null])[0];
266
            if (empty($email))$email = self::arrayGet($data, $fbEmail, [null])[0];
267
            if (empty($login) || empty($email)) {
268
                // Login or email could not be found so bail
269
                return false;
270
            }
271
272
            $firstname = self::arrayGet($data, $userFirstname, [null])[0];
273
            $lastname = self::arrayGet($data, $userLastname, [null])[0];
274
275
            if ((empty($firstname) || empty($lastname)) && isset($data[$userFullname])) {
276
                $names = explode(' ', $data[$userFullname][0]);
277
                if (count($names) > 1) {
278
                    $firstname = $names[0];
279
                    unset($names[0]);
280
                    $lastname = implode(' ', $names);
281
                } else {
282
                    $firstname = $lastname = $names[0];
283
                }
284
            }
285
286
            $user = new User();
287
            $user->setUsername($login)
288
                ->setEmail($email)
289
                ->setFirstName($firstname)
290
                ->setLastName($lastname)
291
                ->setRole(
292
                    $this->getUserRole()
293
                );
294
            return $user;
295
        }
296
297
        return false;
298
    }
299
300
    /**
301
     * Get a value from an array or return default value if not set.
302
     *
303
     * @param array $array source array
304
     * @param string $key key to get from array
305
     * @param mixed $default default value if key not set in array
306
     *
307
     * @return mixed a value from array or default value.
308
     */
309
    private function arrayGet($array, $key, $default = null)
310
    {
311
        return isset($array[$key]) ? $array[$key] : $default;
312
    }
313
314
    /**
315
     * Returns if failed LDAP authentication should fallback to local authentication.
316
     *
317
     * @return bool
318
     */
319
    public function shouldFallbackToLocalAuth()
320
    {
321
        $featureSettings = $this->settings->getFeatureSettings();
322
323
        return (isset($featureSettings['auth_fallback'])) ? $featureSettings['auth_fallback'] : true;
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     *
329
     * @param Form|\Symfony\Component\Form\FormBuilder $builder
330
     * @param array $data
331
     * @param string $formArea
332
     */
333
    public function appendToForm(&$builder, $data, $formArea)
334
    {
335
        if ($formArea == 'features') {
336
            $builder->add(
337
                'auth_fallback',
338
                'yesno_button_group',
339
                [
340
                    'label' => 'mautic.integration.sso.ldapauth.auth_fallback',
341
                    'data' => (isset($data['auth_fallback'])) ? (bool) $data['auth_fallback'] : true,
342
                    'attr' => [
343
                        'tooltip' => 'mautic.integration.sso.ldapauth.auth_fallback.tooltip',
344
                    ],
345
                ]
346
            );
347
348
            $builder->add(
349
                'auto_create_user',
350
                'yesno_button_group',
351
                [
352
                    'label' => 'mautic.integration.sso.auto_create_user',
353
                    'data' => (isset($data['auto_create_user'])) ? (bool) $data['auto_create_user'] : false,
354
                    'attr' => [
355
                        'tooltip' => 'mautic.integration.sso.auto_create_user.tooltip',
356
                    ],
357
                ]
358
            );
359
360
            $builder->add(
361
                'new_user_role',
362
                'role_list',
363
                [
364
                    'label' => 'mautic.integration.sso.new_user_role',
365
                    'label_attr' => ['class' => 'control-label'],
366
                    'attr' => [
367
                        'class' => 'form-control',
368
                        'tooltip' => 'mautic.integration.sso.new_user_role.tooltip',
369
                    ],
370
                ]
371
            );
372
        }
373
    }
374
}
375