Passed
Pull Request — 1.11.x (#5763)
by Angel Fernando Quiroz
08:35
created

AzureActiveDirectory::formatUserData()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 36
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 26
c 2
b 0
f 0
dl 0
loc 36
rs 9.1928
cc 5
nc 8
nop 2
1
<?php
2
/* For license terms, see /license.txt */
3
4
use Chamilo\UserBundle\Entity\User;
5
use TheNetworg\OAuth2\Client\Provider\Azure;
6
7
/**
8
 * AzureActiveDirectory plugin class.
9
 *
10
 * @author Angel Fernando Quiroz Campos <[email protected]>
11
 *
12
 * @package chamilo.plugin.azure_active_directory
13
 */
14
class AzureActiveDirectory extends Plugin
15
{
16
    public const SETTING_ENABLE = 'enable';
17
    public const SETTING_APP_ID = 'app_id';
18
    public const SETTING_APP_SECRET = 'app_secret';
19
    public const SETTING_BLOCK_NAME = 'block_name';
20
    public const SETTING_FORCE_LOGOUT_BUTTON = 'force_logout';
21
    public const SETTING_MANAGEMENT_LOGIN_ENABLE = 'management_login_enable';
22
    public const SETTING_MANAGEMENT_LOGIN_NAME = 'management_login_name';
23
    public const SETTING_PROVISION_USERS = 'provisioning';
24
    public const SETTING_UPDATE_USERS = 'update_users';
25
    public const SETTING_GROUP_ID_ADMIN = 'group_id_admin';
26
    public const SETTING_GROUP_ID_SESSION_ADMIN = 'group_id_session_admin';
27
    public const SETTING_GROUP_ID_TEACHER = 'group_id_teacher';
28
    public const SETTING_EXISTING_USER_VERIFICATION_ORDER = 'existing_user_verification_order';
29
    public const SETTING_TENANT_ID = 'tenant_id';
30
    public const SETTING_DEACTIVATE_NONEXISTING_USERS = 'deactivate_nonexisting_users';
31
32
    public const URL_TYPE_AUTHORIZE = 'login';
33
    public const URL_TYPE_LOGOUT = 'logout';
34
35
    public const EXTRA_FIELD_ORGANISATION_EMAIL = 'organisationemail';
36
    public const EXTRA_FIELD_AZURE_ID = 'azure_id';
37
    public const EXTRA_FIELD_AZURE_UID = 'azure_uid';
38
39
    public const API_PAGE_SIZE = 999;
40
41
    /**
42
     * AzureActiveDirectory constructor.
43
     */
44
    protected function __construct()
45
    {
46
        $settings = [
47
            self::SETTING_ENABLE => 'boolean',
48
            self::SETTING_APP_ID => 'text',
49
            self::SETTING_APP_SECRET => 'text',
50
            self::SETTING_BLOCK_NAME => 'text',
51
            self::SETTING_FORCE_LOGOUT_BUTTON => 'boolean',
52
            self::SETTING_MANAGEMENT_LOGIN_ENABLE => 'boolean',
53
            self::SETTING_MANAGEMENT_LOGIN_NAME => 'text',
54
            self::SETTING_PROVISION_USERS => 'boolean',
55
            self::SETTING_UPDATE_USERS => 'boolean',
56
            self::SETTING_GROUP_ID_ADMIN => 'text',
57
            self::SETTING_GROUP_ID_SESSION_ADMIN => 'text',
58
            self::SETTING_GROUP_ID_TEACHER => 'text',
59
            self::SETTING_EXISTING_USER_VERIFICATION_ORDER => 'text',
60
            self::SETTING_TENANT_ID => 'text',
61
            self::SETTING_DEACTIVATE_NONEXISTING_USERS => 'boolean',
62
        ];
63
64
        parent::__construct('2.4', 'Angel Fernando Quiroz Campos, Yannick Warnier', $settings);
65
    }
66
67
    /**
68
     * Instance the plugin.
69
     *
70
     * @staticvar null $result
71
     *
72
     * @return $this
73
     */
74
    public static function create()
75
    {
76
        static $result = null;
77
78
        return $result ? $result : $result = new self();
79
    }
80
81
    /**
82
     * @return string
83
     */
84
    public function get_name()
85
    {
86
        return 'azure_active_directory';
87
    }
88
89
    /**
90
     * @return Azure
91
     */
92
    public function getProvider()
93
    {
94
        $provider = new Azure([
95
            'clientId' => $this->get(self::SETTING_APP_ID),
96
            'clientSecret' => $this->get(self::SETTING_APP_SECRET),
97
            'redirectUri' => api_get_path(WEB_PLUGIN_PATH).'azure_active_directory/src/callback.php',
98
        ]);
99
100
        return $provider;
101
    }
102
103
    public function getProviderForApiGraph(): Azure
104
    {
105
        $provider = $this->getProvider();
106
        $provider->urlAPI = "https://graph.microsoft.com/v1.0/";
107
        $provider->resource = "https://graph.microsoft.com/";
108
        $provider->tenant = $this->get(AzureActiveDirectory::SETTING_TENANT_ID);
109
        $provider->authWithResource = false;
110
111
        return $provider;
112
    }
113
114
    /**
115
     * @param string $urlType Type of URL to generate
116
     *
117
     * @return string
118
     */
119
    public function getUrl($urlType)
120
    {
121
        if (self::URL_TYPE_LOGOUT === $urlType) {
122
            $provider = $this->getProvider();
123
124
            return $provider->getLogoutUrl(
125
                api_get_path(WEB_PATH)
126
            );
127
        }
128
129
        return api_get_path(WEB_PLUGIN_PATH).$this->get_name().'/src/callback.php';
130
    }
131
132
    /**
133
     * Create extra fields for user when installing.
134
     */
135
    public function install()
136
    {
137
        UserManager::create_extra_field(
138
            self::EXTRA_FIELD_ORGANISATION_EMAIL,
139
            ExtraField::FIELD_TYPE_TEXT,
140
            $this->get_lang('OrganisationEmail'),
141
            ''
142
        );
143
        UserManager::create_extra_field(
144
            self::EXTRA_FIELD_AZURE_ID,
145
            ExtraField::FIELD_TYPE_TEXT,
146
            $this->get_lang('AzureId'),
147
            ''
148
        );
149
        UserManager::create_extra_field(
150
            self::EXTRA_FIELD_AZURE_UID,
151
            ExtraField::FIELD_TYPE_TEXT,
152
            $this->get_lang('AzureUid'),
153
            ''
154
        );
155
    }
156
157
    public function getExistingUserVerificationOrder(): array
158
    {
159
        $defaultOrder = [1, 2, 3];
160
161
        $settingValue = $this->get(self::SETTING_EXISTING_USER_VERIFICATION_ORDER);
162
        $selectedOrder = array_filter(
163
            array_map(
164
                'trim',
165
                explode(',', $settingValue)
166
            )
167
        );
168
        $selectedOrder = array_map('intval', $selectedOrder);
169
        $selectedOrder = array_filter(
170
            $selectedOrder,
171
            function ($position) use ($defaultOrder): bool {
172
                return in_array($position, $defaultOrder);
173
            }
174
        );
175
176
        if ($selectedOrder) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $selectedOrder of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
177
            return $selectedOrder;
178
        }
179
180
        return $defaultOrder;
181
    }
182
183
    public function getUserIdByVerificationOrder(array $azureUserData, string $azureUidKey = 'objectId'): ?int
184
    {
185
        $selectedOrder = $this->getExistingUserVerificationOrder();
186
187
        $extraFieldValue = new ExtraFieldValue('user');
188
        $positionsAndFields = [
189
            1 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
190
                AzureActiveDirectory::EXTRA_FIELD_ORGANISATION_EMAIL,
191
                $azureUserData['mail']
192
            ),
193
            2 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
194
                AzureActiveDirectory::EXTRA_FIELD_AZURE_ID,
195
                $azureUserData['mailNickname']
196
            ),
197
            3 => $extraFieldValue->get_item_id_from_field_variable_and_field_value(
198
                AzureActiveDirectory::EXTRA_FIELD_AZURE_UID,
199
                $azureUserData[$azureUidKey]
200
            ),
201
        ];
202
203
        foreach ($selectedOrder as $position) {
204
            if (!empty($positionsAndFields[$position]) && isset($positionsAndFields[$position]['item_id'])) {
205
                return (int) $positionsAndFields[$position]['item_id'];
206
            }
207
        }
208
209
        return null;
210
    }
211
212
    /**
213
     * @throws Exception
214
     */
215
    public function registerUser(
216
        array $azureUserInfo,
217
        string $azureUidKey = 'objectId'
218
    ) {
219
        if (empty($azureUserInfo)) {
220
            throw new Exception('Groups info not found.');
221
        }
222
223
        $userId = $this->getUserIdByVerificationOrder($azureUserInfo, $azureUidKey);
224
225
        if (empty($userId)) {
226
            // If we didn't find the user
227
            if ($this->get(self::SETTING_PROVISION_USERS) !== 'true') {
228
                throw new Exception('User not found when checking the extra fields from '.$azureUserInfo['mail'].' or '.$azureUserInfo['mailNickname'].' or '.$azureUserInfo[$azureUidKey].'.');
229
            }
230
231
            [
232
                $firstNme,
233
                $lastName,
234
                $username,
235
                $email,
236
                $phone,
237
                $authSource,
238
                $active,
239
                $extra,
240
            ] = $this->formatUserData($azureUserInfo, $azureUidKey);
241
242
            // If the option is set to create users, create it
243
            $userId = UserManager::create_user(
244
                $firstNme,
245
                $lastName,
246
                STUDENT,
247
                $email,
248
                $username,
249
                '',
250
                null,
251
                null,
252
                $phone,
253
                null,
254
                $authSource,
255
                null,
256
                $active,
257
                null,
258
                $extra,
259
                null,
260
                null
261
            );
262
263
            if (!$userId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userId of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
264
                throw new Exception(get_lang('UserNotAdded').' '.$azureUserInfo['userPrincipalName']);
265
            }
266
267
            return $userId;
268
        }
269
270
        if ($this->get(self::SETTING_UPDATE_USERS) === 'true') {
271
            [
272
                $firstNme,
273
                $lastName,
274
                $username,
275
                $email,
276
                $phone,
277
                $authSource,
278
                $active,
279
                $extra,
280
            ] = $this->formatUserData($azureUserInfo, $azureUidKey);
281
282
            $userId = UserManager::update_user(
283
                $userId,
284
                $firstNme,
285
                $lastName,
286
                $username,
287
                '',
288
                $authSource,
289
                $email,
290
                STUDENT,
291
                null,
292
                $phone,
293
                null,
294
                null,
295
                $active,
296
                null,
297
                0,
298
                $extra
299
            );
300
301
            if (!$userId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userId of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
302
                throw new Exception(get_lang('CouldNotUpdateUser').' '.$azureUserInfo['userPrincipalName']);
303
            }
304
        }
305
306
        return $userId;
307
    }
308
309
    /**
310
     * @return array<string, string|false>
311
     */
312
    public function getGroupUidByRole(): array
313
    {
314
        $groupUidList = [
315
            'admin' => $this->get(self::SETTING_GROUP_ID_ADMIN),
316
            'sessionAdmin' => $this->get(self::SETTING_GROUP_ID_SESSION_ADMIN),
317
            'teacher' => $this->get(self::SETTING_GROUP_ID_TEACHER),
318
        ];
319
320
        return array_filter($groupUidList);
321
    }
322
323
    /**
324
     * @return array<string, callable>
325
     */
326
    public function getUpdateActionByRole(): array
327
    {
328
        return [
329
            'admin' => function (User $user) {
330
                $user->setStatus(COURSEMANAGER);
331
332
                UserManager::addUserAsAdmin($user, false);
333
            },
334
            'sessionAdmin' => function (User $user) {
335
                $user->setStatus(SESSIONADMIN);
336
337
                UserManager::removeUserAdmin($user, false);
338
            },
339
            'teacher' => function (User $user) {
340
                $user->setStatus(COURSEMANAGER);
341
342
                UserManager::removeUserAdmin($user, false);
343
            },
344
        ];
345
    }
346
347
    /**
348
     * @throws Exception
349
     */
350
    private function formatUserData(
351
        array $azureUserInfo,
352
        string $azureUidKey
353
    ): array {
354
        $phone = null;
355
356
        if (isset($azureUserInfo['telephoneNumber'])) {
357
            $phone = $azureUserInfo['telephoneNumber'];
358
        } elseif (isset($azureUserInfo['businessPhones'][0])) {
359
            $phone = $azureUserInfo['businessPhones'][0];
360
        } elseif (isset($azureUserInfo['mobilePhone'])) {
361
            $phone = $azureUserInfo['mobilePhone'];
362
        }
363
364
        // If the option is set to create users, create it
365
        $firstNme = $azureUserInfo['givenName'];
366
        $lastName = $azureUserInfo['surname'];
367
        $email = $azureUserInfo['mail'];
368
        $username = $azureUserInfo['userPrincipalName'];
369
        $authSource = 'azure';
370
        $active = ($azureUserInfo['accountEnabled'] ? 1 : 0);
371
        $extra = [
372
            'extra_'.self::EXTRA_FIELD_ORGANISATION_EMAIL => $azureUserInfo['mail'],
373
            'extra_'.self::EXTRA_FIELD_AZURE_ID => $azureUserInfo['mailNickname'],
374
            'extra_'.self::EXTRA_FIELD_AZURE_UID => $azureUserInfo[$azureUidKey],
375
        ];
376
377
        return [
378
            $firstNme,
379
            $lastName,
380
            $username,
381
            $email,
382
            $phone,
383
            $authSource,
384
            $active,
385
            $extra,
386
        ];
387
    }
388
}
389