Test Failed
Push — master ( f4077b...8d6a7b )
by Tomasz
12:00
created

UserManagement::listInstitutionsByAdmin()   D

Complexity

Conditions 18
Paths 172

Size

Total Lines 78
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 46
c 0
b 0
f 0
dl 0
loc 78
rs 4.2666
cc 18
nc 172
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This file contains the UserManagement class.
25
 *
26
 * @author Stefan Winter <[email protected]>
27
 * @author Tomasz Wolniewicz <[email protected]>
28
 * 
29
 * @license see LICENSE file in root directory
30
 * 
31
 * @package Developer
32
 */
33
/**
34
 * necessary includes
35
 */
36
37
namespace core;
38
39
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
40
41
/**
42
 * This class manages user privileges and bindings to institutions
43
 *
44
 * @author Stefan Winter <[email protected]>
45
 * @author Tomasz Wolniewicz <[email protected]>
46
 * 
47
 * @package Developer
48
 */
49
class UserManagement extends \core\common\Entity
50
{
51
52
    /**
53
     * our handle to the INST database
54
     * 
55
     * @var DBConnection
56
     */
57
    private $databaseHandle;
58
    public $currentInstitutions;
59
    public $newUser = false;
60
    public $hasPotenialNewInst = false;
61
62
    /**
63
     * Class constructor. Nothing special to be done when constructing.
64
     * 
65
     * @throws Exception
66
     */
67
    public function __construct()
68
    {
69
        parent::__construct();
70
        $handle = DBConnection::handle(self::$databaseType);
71
        if ($handle instanceof DBConnection) {
72
            $this->databaseHandle = $handle;
73
        } else {
74
            throw new Exception("This database type is never an array!");
75
        }
76
    }
77
78
    /**
79
     * database which this class queries by default
80
     * 
81
     * @var string
82
     */
83
    private static $databaseType = "INST";
84
85
    const TOKENSTATUS_OK_NEW = 1;
86
    const TOKENSTATUS_OK_EXISTING = 2;
87
    const TOKENSTATUS_FAIL_ALREADYCONSUMED = -1;
88
    const TOKENSTATUS_FAIL_EXPIRED = -2;
89
    const TOKENSTATUS_FAIL_NONEXISTING = -3;
90
91
    /**
92
     * Checks if a given invitation token exists and is valid in the invitations database
93
     * returns a string with the following values:
94
     * 
95
     * OK-NEW valid token exists, and is not attached to an existing institution. When consuming the token, a new inst will be created
96
     * OK-EXISTING valid token exists, and is attached to an existing institution. When consuming the token, user will be added as an admin
97
     * FAIL-NONEXISTINGTOKEN this token does not exist at all in the database
98
     * FAIL-ALREADYCONSUMED the token exists, but has been used before
99
     * FAIL-EXPIRED the token exists, but has expired
100
     * 
101
     * @param string $token the invitation token
102
     * @return int
103
     */
104
    public function checkTokenValidity($token)
105
    {
106
        $check = $this->databaseHandle->exec("SELECT invite_token, cat_institution_id 
107
                           FROM invitations 
108
                           WHERE invite_token = ? AND invite_created >= TIMESTAMPADD(DAY, -1, NOW()) AND used = 0", "s", $token);
109
        // SELECT -> resource, not boolean
110
        if ($tokenCheck = mysqli_fetch_object(/** @scrutinizer ignore-type */ $check)) {
111
            if ($tokenCheck->cat_institution_id === NULL) {
112
                return self::TOKENSTATUS_OK_NEW;
113
            }
114
            return self::TOKENSTATUS_OK_EXISTING;
115
        }
116
        // if we haven't returned from the function yet, it is an invalid token... 
117
        // be a little verbose what's wrong with it
118
        $checkReason = $this->databaseHandle->exec("SELECT invite_token, used FROM invitations WHERE invite_token = ?", "s", $token);
119
        // SELECT -> resource, not boolean
120
        if ($invalidTokenCheck = mysqli_fetch_object(/** @scrutinizer ignore-type */ $checkReason)) {
121
            if ($invalidTokenCheck->used == 1) {
122
                return self::TOKENSTATUS_FAIL_ALREADYCONSUMED;
123
            }
124
            return self::TOKENSTATUS_FAIL_EXPIRED;
125
        }
126
        return self::TOKENSTATUS_FAIL_NONEXISTING;
127
    }
128
129
    /**
130
     * This function creates a new IdP in the database based on a valid invitation token - or adds a new administrator
131
     * to an existing one. The institution is created for the logged-in user (second argument) who presents the token (first 
132
     * argument). The tokens are created via createToken().
133
     * 
134
     * @param string $token The invitation token (must exist in the database and be valid). 
135
     * @param string $owner Persistent User ID who becomes the administrator of the institution
136
     * @return IdP 
137
     */
138
    public function createIdPFromToken(string $token, string $owner)
139
    {
140
        new CAT(); // be sure that Entity's static members are initialised
141
        common\Entity::intoThePotatoes();
142
        // the token either has cat_institution_id set -> new admin for existing inst
143
        // or contains a number of parameters from external DB -> set up new inst
144
        $instinfo = $this->databaseHandle->exec("SELECT cat_institution_id, country, name, invite_issuer_level, invite_dest_mail, external_db_uniquehandle, invite_fortype 
145
                             FROM invitations 
146
                             WHERE invite_token = ? AND invite_created >= TIMESTAMPADD(DAY, -1, NOW()) AND used = 0", "s", $token);
147
        // SELECT -> resource, no boolean
148
        if ($invitationDetails = mysqli_fetch_assoc(/** @scrutinizer ignore-type */ $instinfo)) {
149
            if ($invitationDetails['cat_institution_id'] !== NULL) { // add new admin to existing IdP
150
                // we can't rely on a unique key on this table (user IDs 
151
                // possibly too long), so run a query to find there's an
152
                // tuple already; and act accordingly
153
                $catId = $invitationDetails['cat_institution_id'];
154
                $level = $invitationDetails['invite_issuer_level'];
155
                $destMail = $invitationDetails['invite_dest_mail'];
156
                $existing = $this->databaseHandle->exec("SELECT user_id FROM ownership WHERE user_id = ? AND institution_id = ?", "si", $owner, $catId);
157
                // SELECT -> resource, not boolean
158
                if (mysqli_num_rows(/** @scrutinizer ignore-type */ $existing) > 0) {
159
                    $this->databaseHandle->exec("UPDATE ownership SET blesslevel = ?, orig_mail = ? WHERE user_id = ? AND institution_id = ?", "sssi", $level, $destMail, $owner, $catId);
160
                } else {
161
                    $this->databaseHandle->exec("INSERT INTO ownership (user_id, institution_id, blesslevel, orig_mail) VALUES(?, ?, ?, ?)", "siss", $owner, $catId, $level, $destMail);
162
                }
163
                $this->loggerInstance->writeAudit((string) $owner, "OWN", "IdP " . $invitationDetails['cat_institution_id'] . " - added user as owner");
164
                common\Entity::outOfThePotatoes();
165
                return new IdP($invitationDetails['cat_institution_id']);
166
            }
167
            // create new IdP
168
            $fed = new Federation($invitationDetails['country']);
169
            // find the best name for the entity: C if specified, otherwise English, otherwise whatever
170
            if ($invitationDetails['external_db_uniquehandle'] != NULL) {
171
                $idp = $this->newIdPFromExternal($invitationDetails['external_db_uniquehandle'], $fed, $invitationDetails, $owner);
172
            } else {
173
                $bestnameguess = $invitationDetails['name'];
174
                $idp = new IdP($fed->newIdP($invitationDetails['invite_fortype'], $owner, $invitationDetails['invite_issuer_level'], $invitationDetails['invite_dest_mail'], $bestnameguess));
175
                $idp->addAttribute("general:instname", 'C', $bestnameguess);
176
            }
177
            $this->loggerInstance->writeAudit($owner, "NEW", "IdP " . $idp->identifier . " - created from invitation");
178
179
            // in case we have more admins in the queue which were invited to 
180
            // administer the same inst but haven't redeemed their invitations 
181
            // yet, then we will have to rewrite the invitations to point to the
182
            // newly created actual IdP rather than the placeholder entry in the
183
            // invitations table
184
            // which other pending invites do we have?
185
186
            $otherPending = $this->databaseHandle->exec("SELECT id
187
                             FROM invitations 
188
                             WHERE invite_created >= TIMESTAMPADD(DAY, -1, NOW()) AND used = 0 AND name = ? AND country = ? AND ( cat_institution_id IS NULL OR external_db_uniquehandle IS NULL ) ", "ss", $invitationDetails['name'], $invitationDetails['country']);
189
            // SELECT -> resource, no boolean
190
            while ($pendingDetail = mysqli_fetch_object(/** @scrutinizer ignore-type */ $otherPending)) {
191
                $this->databaseHandle->exec("UPDATE invitations SET cat_institution_id = " . $idp->identifier . " WHERE id = " . $pendingDetail->id);
192
            }
193
            common\Entity::outOfThePotatoes();
194
            return $idp;
195
        }
196
    }
197
198
    /**
199
     * create new institution based on the edxternalDB data 
200
     * @param string $extId - the eduroam database identifier
201
     * @param object $fed - the CAT federation object where the institution should be created
202
     * @param type $owner
0 ignored issues
show
Bug introduced by
The type core\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
203
     * @return type
204
     */
205
    public function createIdPFromExternal($extId, $fed, $owner)
206
    {
207
        $cat = new CAT();
208
        $ROid = strtoupper($fed->tld).'01';
209
        $externalinfo = $cat->getExternalDBEntityDetails($extId, $ROid);
210
        $invitationDetails = [
211
            'invite_fortype' => $externalinfo['type'],
212
            'invite_issuer_level' => "FED",
213
            'invite_dest_mail' => $_SESSION['auth_email']
214
        ];
215
        $idp = $this->newIdPFromExternal($extId, $fed, $invitationDetails, $owner, $externalinfo);
216
        $this->loggerInstance->writeAudit($owner, "NEW", "IdP " . $idp->identifier . " - created from auto-registration of $extId");
217
        return $idp;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $idp returns the type core\IdP which is incompatible with the documented return type core\type.
Loading history...
218
    }
219
    
220
    /*
221
     * This is the common part of the code for createIdPFromToken and createIdPFromExternal
222
     */
223
    private function newIdPFromExternal($extId, $fed, $invitationDetails, $owner, $externalinfo = [])
224
    {
225
        // see if we had a C language, and if not, pick a good candidate 
226
        if ($externalinfo == []) {
227
            $cat = new CAT();
228
            $ROid = strtoupper($fed->tld).'01';
229
            $externalinfo = $cat->getExternalDBEntityDetails($extId, $ROid);
230
        }
231
        print "<p>";
232
        print_r($externalinfo);
233
        print "<p>";
234
        print_r($owner);
235
        $bestnameguess = $externalinfo['names']['C'] ?? $externalinfo['names']['en'] ?? reset($externalinfo['names']);
236
        $idp = new IdP($fed->newIdP($invitationDetails['invite_fortype'], $owner, $invitationDetails['invite_issuer_level'], $invitationDetails['invite_dest_mail'], $bestnameguess));
237
        foreach ($externalinfo['names'] as $instlang => $instname) {
238
            $idp->addAttribute("general:instname", $instlang, $instname);
239
        }
240
        $idp->setExternalDBId($extId, strtolower($fed->tld));
241
        $idp->addAttribute("general:instname", 'C', $bestnameguess);
242
        return $idp;
243
    }
244
245
    /**
246
     * Adds a new administrator to an existing IdP
247
     * @param IdP    $idp  institution to which the admin is to be added.
248
     * @param string $user persistent user ID that is to be added as an admin.
249
     * @return boolean This function always returns TRUE.
250
     */
251
    public function addAdminToIdp($idp, $user)
252
    {
253
        $existing = $this->databaseHandle->exec("SELECT user_id FROM ownership WHERE user_id = ? AND institution_id = ?", "si", $user, $idp->identifier);
254
        // SELECT -> resource, not boolean
255
        if (mysqli_num_rows(/** @scrutinizer ignore-type */ $existing) == 0) {
256
            $this->databaseHandle->exec("INSERT INTO ownership (institution_id,user_id,blesslevel,orig_mail) VALUES(?, ?, 'FED', 'SELF-APPOINTED')", "is", $idp->identifier, $user);
257
        }
258
        return TRUE;
259
    }
260
261
    /**
262
     * Deletes an administrator from the IdP. If the IdP and user combination doesn't match, nothing happens.
263
     * @param IdP    $idp  institution from which the admin is to be deleted.
264
     * @param string $user persistent user ID that is to be deleted as an admin.
265
     * @return boolean This function always returns TRUE.
266
     */
267
    public function removeAdminFromIdP($idp, $user)
268
    {
269
        $this->databaseHandle->exec("DELETE from ownership WHERE institution_id = $idp->identifier AND user_id = ?", "s", $user);
270
        return TRUE;
271
    }
272
273
    /**
274
     * Invalidates a token so that it can't be used any more. Tokens automatically expire after 24h, but can be invalidated
275
     * earlier, e.g. after having been used to create an institution. If the token doesn't exist in the DB or is already invalidated,
276
     * nothing happens.
277
     * 
278
     * @param string $token the token to invalidate
279
     * @return boolean This function always returns TRUE.
280
     */
281
    public function invalidateToken($token)
282
    {
283
        $this->databaseHandle->exec("UPDATE invitations SET used = 1 WHERE invite_token = ?", "s", $token);
284
        return TRUE;
285
    }
286
287
    /**
288
     * Creates a new invitation token. The token's main purpose is to be sent out by mail. The function either can generate a token for a new 
289
     * administrator of an existing institution, or for a new institution. In the latter case, the institution only actually gets 
290
     * created in the DB if the token is actually consumed via createIdPFromToken().
291
     * 
292
     * @param boolean $isByFedadmin   is the invitation token created for a federation admin (TRUE) or from an existing inst admin (FALSE)
293
     * @param array   $for            identifiers (typically email addresses) for which the invitation is created
294
     * @param mixed   $instIdentifier either an instance of the IdP class (for existing institutions to invite new admins) or a string (new institution - this is the inst name then)
295
     * @param string  $externalId     if the IdP to be created is related to an external DB entity, this parameter contains that ID
296
     * @param string  $country        if the institution is new (i.e. $inst is a string) this parameter needs to specify the federation of the new inst
297
     * @param string  $partType       the type of participant
298
     * @return mixed The function returns either the token (as string) or FALSE if something went wrong
299
     * @throws Exception
300
     */
301
    public function createTokens($isByFedadmin, $for, $instIdentifier, $externalId = 0, $country = 0, $partType = 0)
302
    {
303
        $level = ($isByFedadmin ? "FED" : "INST");
304
        $tokenList = [];
305
        foreach ($for as $oneDest) {
306
            $token = bin2hex(random_bytes(40));
307
            if ($instIdentifier instanceof IdP) {
308
                $this->databaseHandle->exec("INSERT INTO invitations (invite_fortype, invite_issuer_level, invite_dest_mail, invite_token,cat_institution_id) VALUES(?, ?, ?, ?, ?)", "ssssi", $instIdentifier->type, $level, $oneDest, $token, $instIdentifier->identifier);
309
                $tokenList[$token] = $oneDest;
310
            } else if (func_num_args() == 5) {
311
                $ROid = strtoupper($country).'01';
312
                $cat = new CAT();
313
                $extinfo = $cat->getExternalDBEntityDetails($externalId, $ROid);
314
                $extCountry = $extinfo['country'];
315
                $extType = $extinfo['type'];
316
                $this->databaseHandle->exec("INSERT INTO invitations (invite_fortype, invite_issuer_level, invite_dest_mail, invite_token,name,country, external_db_uniquehandle) VALUES(?, ?, ?, ?, ?, ?, ?)", "sssssss", $extType, $level, $oneDest, $token, $instIdentifier, $extCountry, $externalId);
317
                $tokenList[$token] = $oneDest;
318
            } else if (func_num_args() == 6) { // string name, and country set - whole new IdP
319
                $this->databaseHandle->exec("INSERT INTO invitations (invite_fortype, invite_issuer_level, invite_dest_mail, invite_token,name,country) VALUES(?, ?, ?, ?, ?, ?)", "ssssss", $partType, $level, $oneDest, $token, $instIdentifier, $country);
320
                $tokenList[$token] = $oneDest;
321
            } else {
322
                throw new Exception("The invitation is somehow ... wrong.");
323
            }
324
        }
325
        if (count($for) != count($tokenList)) {
326
            throw new Exception("Creation of a new token failed!");
327
        }
328
        return $tokenList;
329
    }
330
331
    /**
332
     * Retrieves all pending invitations for an institution or for a federation.
333
     * 
334
     * @param int $idpIdentifier the identifier of the institution. If not set, returns invitations for not-yet-created insts
335
     * @return array if idp_identifier is set: an array of strings (mail addresses); otherwise an array of tuples (country;name;mail)
336
     */
337
    public function listPendingInvitations($idpIdentifier = 0)
338
    {
339
        $retval = [];
340
        $invitations = $this->databaseHandle->exec("SELECT cat_institution_id, country, name, invite_issuer_level, invite_dest_mail, invite_token , TIMESTAMPADD(DAY, 1, invite_created) as expiry
341
                                        FROM invitations 
342
                                        WHERE cat_institution_id " . ( $idpIdentifier != 0 ? "= $idpIdentifier" : "IS NULL") . " AND invite_created >= TIMESTAMPADD(DAY, -1, NOW()) AND used = 0");
343
        // SELECT -> resource, not boolean
344
        $this->loggerInstance->debug(4, "Retrieving pending invitations for " . ($idpIdentifier != 0 ? "IdP $idpIdentifier" : "IdPs awaiting initial creation" ) . ".\n");
345
        while ($invitationQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $invitations)) {
346
            $retval[] = ["country" => $invitationQuery->country, "name" => $invitationQuery->name, "mail" => $invitationQuery->invite_dest_mail, "token" => $invitationQuery->invite_token, "expiry" => $invitationQuery->expiry];
347
        }
348
        return $retval;
349
    }
350
351
    /** Retrieves all invitations which have expired in the last hour.
352
     * 
353
     * @return array of expired invitations
354
     */
355
    public function listRecentlyExpiredInvitations()
356
    {
357
        $retval = [];
358
        $invitations = $this->databaseHandle->exec("SELECT cat_institution_id, country, name, invite_issuer_level, invite_dest_mail, invite_token 
359
                                        FROM invitations 
360
                                        WHERE invite_created >= TIMESTAMPADD(HOUR, -25, NOW()) AND invite_created < TIMESTAMPADD(HOUR, -24, NOW()) AND used = 0");
361
        // SELECT -> resource, not boolean
362
        while ($expInvitationQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $invitations)) {
363
            $this->loggerInstance->debug(4, "Retrieving recently expired invitations (expired in last hour)\n");
364
            if ($expInvitationQuery->cat_institution_id == NULL) {
365
                $retval[] = ["country" => $expInvitationQuery->country, "level" => $expInvitationQuery->invite_issuer_level, "name" => $expInvitationQuery->name, "mail" => $expInvitationQuery->invite_dest_mail];
366
            } else {
367
                $retval[] = ["country" => $expInvitationQuery->country, "level" => $expInvitationQuery->invite_issuer_level, "name" => "Existing IdP", "mail" => $expInvitationQuery->invite_dest_mail];
368
            }
369
        }
370
        return $retval;
371
    }
372
373
    /**
374
     * For a given persistent user identifier, returns an array of institution identifiers (not the actual objects!) for which this
375
     * user is the/a administrator and also do comparisons to the eduroam DB results.
376
     * If the federation autoregister-synced flag is set if it turns out that the eduroam DB
377
     * lists the email of the current logged-in admin as an admin of an existing CAT institution
378
     * and this institution is synced to the matching external institutuin then this admin
379
     * will be automatically added tho the institution and the 'existing' part of $this->currentInstitutions
380
     * will be updated. This identifier will also be listed to $this->currentInstitutions['resynced']
381
     * 
382
     * If the federation autoregister-new-inst flag is set and there are exeternal institututions which could be
383
     * candidated for creating them in CAT - add the identifiers of these institutuins to this->currentInstitutions[new']
384
     * 
385
     * @param boolean $applyAutoSync controls if automatic additions if the user to the admins should be performed
386
     * @return array array of institution IDs
387
     */ 
388
    public function listInstitutionsByAdmin($applyAutoSync = false)
389
    {
390
        $userId = $_SESSION['user'];
391
        // get the list of local identifers of institutions managed by this user
392
        // it will be returned as $this->currentInstitutions
393
        $this->getCurrentInstitutionsByAdmin();
394
        if (count($this->currentInstitutions) == 0) {
395
            $this->newUser = true;
396
        }
397
        
398
        // check if selfservice_registration is set to eduGAIN - if not then return
399
        if (\config\ConfAssistant::CONSORTIUM['selfservice_registration'] !== 'eduGAIN') {
400
            return $this->currentInstitutions;
401
        }
402
        // now add additional institutions based on the external DB 
403
        // proceed only if user has been authenticated fron an eduGAIN IdP
404
        $user = new \core\User($userId);
405
        if ($user->edugain !== true) {
406
            return $this->currentInstitutions;            
407
        }
408
        $email = $_SESSION['auth_email'];
409
        $externalDB = \core\CAT::determineExternalConnection();
410
        // get the list of identifiers in the external DB with this user listed as the admin and linked to CAT institutions
411
        $extInstList = $externalDB->listExternalEntitiesByUserEmail($email);
0 ignored issues
show
Bug introduced by
The method listExternalEntitiesByUserEmail() does not exist on core\ExternalNothing. Did you maybe mean listExternalEntities()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

411
        /** @scrutinizer ignore-call */ 
412
        $extInstList = $externalDB->listExternalEntitiesByUserEmail($email);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
412
        $extInstListTmp = $extInstList;
413
        // we begin by removing entites in $extInstList which are already managed by this user and synced -
414
        // these require not further checking
415
        foreach ($extInstListTmp as $country => $extInstCountryList) {
416
            for($i = 0; $i < count($extInstCountryList); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
417
                $extInst = $extInstCountryList[$i];
418
                if ($extInst['inst_id'] != NULL && in_array($extInst['inst_id'], $this->currentInstitutions['existing'])) {
419
                    unset($extInstList[$country][$i]);
420
                }
421
            }
422
            if (count($extInstList[$country]) == 0) {
423
                unset($extInstList[$country]);
424
            }
425
        }
426
        
427
        if ($extInstList == []) {
428
            return $this->currentInstitutions; 
429
        }
430
        
431
        foreach ($extInstList as $country => $extInstCountryList) {
432
            $fed = new Federation($country);
433
            $autoSyncedFlag = $fed->getAttributes('fed:autoregister-synced');
434
            $newInstFlag = $fed->getAttributes('fed:autoregister-new-inst');
435
            foreach ($extInstCountryList as $extInst) {
436
                $this->loggerInstance->debug(4, "Testing ".$extInst['external_db_id']."\n");
437
                // is institution synced, if so we add this admin
438
                if ($extInst['inst_id'] != NULL && $autoSyncedFlag != []) {
439
                    $this->currentInstitutions['resynced'][] = $extInst['inst_id'];
440
                    if ($applyAutoSync) {
441
                        $this->loggerInstance->debug(4, "Adding admin to ".$extInst['inst_id']."\n");
442
                        $this->currentInstitutions['existing'][] = $extInst['inst_id'];
443
                        $query = "INSERT INTO ownership (user_id, institution_id, blesslevel, orig_mail) VALUES (?, ?, 'FED', ?)";
444
                        $this->databaseHandle->exec($query, 'sis', $userId, $extInst['inst_id'], $email);
445
                    }
446
                }
447
                
448
                // this institution is not synced, perhaps we could create a new one in CAT
449
                if ($extInst['inst_id'] == NULL && $newInstFlag != []) {
450
                    $this->loggerInstance->debug(4, "Testing ".$extInst['external_db_id']." for potential new inst\n");
451
                    // run checks against creating dupplicates in CAT DB
452
                    $disectedNames = \core\ExternalEduroamDBData::dissectCollapsedInstitutionNames($extInst['name']);
453
                    $names = $disectedNames['joint'];
454
                    $realms = \core\ExternalEduroamDBData::dissectCollapsedInstitutionRealms($extInst['realm']);
455
                    $foundMatch = $this->checkForSimilarInstitutions($names, $realms);
0 ignored issues
show
Bug introduced by
$realms of type array|string[] is incompatible with the type core\arrau expected by parameter $realmsToTest of core\UserManagement::checkForSimilarInstitutions(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

455
                    $foundMatch = $this->checkForSimilarInstitutions($names, /** @scrutinizer ignore-type */ $realms);
Loading history...
456
                    $this->loggerInstance->debug(4, $foundMatch, "checkForSimilarInstitutions returned: ","\n");
457
                    if ($foundMatch == 0) {
458
                        $this->currentInstitutions['new'][] = [$extInst['external_db_id'], $disectedNames['perlang'], $country];
459
                    }
460
                }
461
                
462
            }    
463
        }
464
        $this->loggerInstance->debug(4,$this->currentInstitutions['new'],"\n","\n");
465
        return $this->currentInstitutions;
466
    }
467
    
468
    /**
469
     * Tests if the institution with these identifier does not yet exist in CAT. 
470
     * This is done by testing the admins "new" institutions, this way we also make sure
471
     * that this admin is actually also allowed to create the new one
472
     * 
473
     * @return int 1 or 0. 1 means we are free to create the inst.
474
     */
475
    
476
    public function checkForCatMatch($extId, $ROid) {
477
        $this->listInstitutionsByAdmin();
478
        foreach ($this->currentInstitutions['new'] as $newInst) {
479
            if ($extId == $newInst[0] && $ROid == strtoupper($newInst[2]).'01') {
480
                return 0;
481
            }
482
        }
483
        return 1;
484
    }
485
    
486
    /**
487
     * get the list of current institutions of the given admin
488
     * 
489
     * This method does not rerurn anything but sets $this->currentInstitutions
490
     * it only fillsh the 'existing' block, leaving the other two for other methods
491
     * to deal with
492
     */
493
    private function getCurrentInstitutionsByAdmin() {
494
        $returnarray = [
495
            'existing' => [],
496
            'resynced' => [],
497
            'new' => []
498
        ];
499
        $userId = $_SESSION['user'];
500
        // get the list of local identifers of institutions managed by this user
501
        $query = "SELECT ownership.institution_id as inst_id
0 ignored issues
show
Unused Code introduced by
The assignment to $query is dead and can be removed.
Loading history...
502
                  FROM ownership JOIN institution
503
                     ON ownership.institution_id = institution.inst_id
504
                     WHERE ownership.user_id = ? ORDER BY ownership.institution_id";
505
        $institutions = $this->databaseHandle->exec("SELECT ownership.institution_id as inst_id FROM ownership WHERE user_id = ? ORDER BY institution_id", "s", $userId);
506
        // SELECT -> resource, not boolean
507
        $catInstList = $institutions->fetch_all();
508
        foreach ($catInstList as $inst) {
509
            $returnarray['existing'][] = $inst[0];
510
        }
511
        $this->currentInstitutions = $returnarray;
512
    }
513
514
    /**
515
     * given arrays of realms and names check if there already are institutions in CAT that 
516
     * could be a match - this is for ellimination and against creating duplicates
517
     * still this is not perfect, no realms given and institutions with a slightly different
518
     * name will return no-match and thus open possibility for dupplicates
519
     * 
520
     * @param array $namesToTest
521
     * @param arrau $realmsToTest
0 ignored issues
show
Bug introduced by
The type core\arrau was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
522
     * @return int - 1 - a match was found, 0 - no match found
523
     */
524
    private function checkForSimilarInstitutions($namesToTest, $realmsToTest) {
525
        //generate a list of all existing realms
526
        $realmsList = [];
527
        $query = 'SELECT DISTINCT realm FROM profile';
528
        $realmsResult = $this->databaseHandle->exec($query);
529
        while ($anonId = $realmsResult->fetch_row()) {
530
            $realmsList[] = mb_strtolower(preg_replace('/^.*@/', '', $anonId[0]), 'UTF-8');
531
        }
532
        // now test realms
533
        $results = array_intersect($realmsToTest, $realmsList);
0 ignored issues
show
Bug introduced by
$realmsToTest of type core\arrau is incompatible with the type array expected by parameter $array of array_intersect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

533
        $results = array_intersect(/** @scrutinizer ignore-type */ $realmsToTest, $realmsList);
Loading history...
534
        if (count($results) !== 0) {
535
            return 1;
536
        }
537
        
538
        // generate a list of all institution names
539
        $query = "SELECT DISTINCT CONVERT(option_value USING utf8mb4) FROM institution_option WHERE option_name='general:instname'";
540
        $namesResult = $this->databaseHandle->exec($query);
541
        $namesList = [];
542
        while ($name = $namesResult->fetch_row()) {
543
            $namesList[] = mb_strtolower($name[0], 'UTF-8');
544
        }
545
546
        // now test names
547
        $results = array_intersect($namesToTest, $namesList);
548
        if (count($results) !== 0) {
549
            return 1;
550
        }
551
        return 0;
552
    }
553
}