Test Failed
Push — master ( 1b57e8...312ba7 )
by Tomasz
10:03
created

User::sendMailToCATadmins()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 8
rs 9.6111
cc 5
nc 4
nop 2
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 class manages user privileges and bindings to institutions
25
 *
26
 * @author Stefan Winter <[email protected]>
27
 * @author Tomasz Wolniewicz <[email protected]>
28
 * 
29
 * @package Developer
30
 */
31
/**
32
 * necessary includes
33
 */
34
35
namespace core;
36
37
/**
38
 * This class represents a known CAT User (i.e. an institution and/or federation adiministrator).
39
 * @author Stefan Winter <[email protected]>
40
 * 
41
 * @package Developer
42
 */
43
class User extends EntityWithDBProperties
44
{
45
46
    /**
47
     *
48
     * @var string
49
     */
50
    public $userName;
51
    
52
    public $edugain = false;
53
    
54
    public $roles = [];
55
56
    /**
57
     * Class constructor. The required argument is a user's persistent identifier as was returned by the authentication source.
58
     * 
59
     * @param string $userId User Identifier as per authentication source
60
     */
61
    public function __construct($userId)
62
    {
63
        $this->databaseType = "USER";
64
        parent::__construct(); // database handle is now available
65
        $this->attributes = [];
66
        $this->entityOptionTable = "user_options";
67
        $this->entityIdColumn = "user_id";
68
        $this->identifier = 0; // not used
69
        $this->userName = $userId;
70
        $optioninstance = Options::instance();
71
72
        if (\config\ConfAssistant::CONSORTIUM['name'] == "eduroam" && isset(\config\ConfAssistant::CONSORTIUM['deployment-voodoo']) && \config\ConfAssistant::CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
73
// e d u r o a m DB doesn't follow the usual approach
74
// we could get multiple rows below (if administering multiple
75
// federations), so consolidate all into the usual options
76
            $info = $this->databaseHandle->exec("SELECT email, common_name, role, realm FROM view_admin WHERE eptid = ?", "s", $this->userName);
77
            $visited = false;
78
            // SELECT -> resource, not boolean
79
            while ($userDetailQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $info)) {
80
                if (!$visited) {
81
                    $mailOptinfo = $optioninstance->optionType("user:email");
82
                    $this->attributes[] = ["name" => "user:email", "lang" => NULL, "value" => $userDetailQuery->email, "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $mailOptinfo['flag']];
83
                    $realnameOptinfo = $optioninstance->optionType("user:realname");
84
                    $this->attributes[] = ["name" => "user:realname", "lang" => NULL, "value" => $userDetailQuery->common_name, "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $realnameOptinfo['flag']];
85
                    $visited = TRUE;
86
                }
87
                if ($userDetailQuery->role == "fedadmin") {
88
                    $optinfo = $optioninstance->optionType("user:fedadmin");
89
                    $this->attributes[] = ["name" => "user:fedadmin", "lang" => NULL, "value" => strtoupper($userDetailQuery->realm), "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $optinfo['flag']];
90
                }
91
            }
92
        } else {
93
            $this->attributes = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name, option_lang, option_value, row_id
94
                                                FROM $this->entityOptionTable
95
                                                WHERE $this->entityIdColumn = ?", "User");
96
        }
97
        $this->getAdminRoles();
98
    }
99
    
100
    /**
101
     * This dunction loads the roles table with all user's functions.
102
     */
103
    
104
    private function getAdminRoles() {
105
        if (in_array($this->userName, \config\Master::SUPERADMINS)) {
106
            $this->roles[] = 'superadmin';
107
        }
108
        if (in_array($this->userName, \config\Master::SUPPORT)) {
109
            $this->roles[] = 'support';
110
        }
111
        if (count($this->getAttributes("user:fedadmin")) > 0) {
112
            $this->roles[] = 'fedadmin';
113
        }
114
        if (count($this->listOwnerships()) > 0) {
115
            $this->roles[] = 'instadmin';
116
        }
117
    }
118
119
    /**
120
     * This function checks whether a user is a federation administrator. When called without argument, it only checks if the
121
     * user is a federation administrator of *any* federation. When given a parameter (ISO shortname of federation), it checks
122
     * if the user administers this particular federation.
123
     * 
124
     * @param string $federation optional: federation to be checked
125
     * @return boolean TRUE if the user is federation admin, false if not 
126
     */
127
    public function isFederationAdmin($federation = 0)
128
    {
129
        $feds = $this->getAttributes("user:fedadmin");
130
        if (count($feds) == 0) { // not a fedadmin at all
131
            return false;
132
        }
133
        if ($federation === 0) { // fedadmin for one; that's all we want to know
134
            return TRUE;
135
        }
136
        foreach ($feds as $fed) { // check if authz is for requested federation
137
            if (strtoupper($fed['value']) == strtoupper($federation)) {
138
                return TRUE;
139
            }
140
        }
141
        return false; // no luck so far? Not the admin we are looking for.
142
    }
143
144
    /**
145
     * This function tests if the current user has been configured as the system superadmin, i.e. if the user is allowed
146
     * to execute the 112365365321.php script and obtain read-only access to admin areas.
147
     *
148
     * @return boolean TRUE if the user is a superadmin, false if not 
149
     */
150
    public function isSuperadmin()
151
    {
152
        return in_array('superadmin', $this->roles);
153
    }
154
    
155
    
156
    /**
157
     * This function tests if the current user has been configured as the system superadmin, i.e. if the user is allowed
158
     *  obtain read-only access to admin areas.
159
     *
160
     * @return boolean TRUE if the user is a support member, false if not 
161
     */
162
    public function isSupport()
163
    {
164
        return in_array('support', $this->roles);
165
    }
166
167
    /**
168
     * This function tests if the current user is an ovner of a given IdP
169
     *
170
     * @param int $idp integer identifier of the IdP
171
     * @return boolean TRUE if the user is an owner, false if not 
172
     */
173
    public function isIdPOwner($idp)
174
    {
175
        $temp = new IdP($idp);
176
        foreach ($temp->listOwners() as $oneowner) {
177
            if ($oneowner['ID'] == $this->userName) {
178
                return TRUE;
179
            }
180
        }
181
        return false;
182
    }
183
    
184
    /** This function tests if user's IdP is listed in eduGAIN - it uses an external 
185
     *  call to technical eduGAIN API
186
     * 
187
     * @return boolean true if the IdP is listed, false otherwise
188
     * 
189
     */
190
    public function isFromEduGAIN()
191
    {
192
        $_SESSION['eduGAIN'] = false;
193
        preg_match('/!([^!]+)$/', $_SESSION['user'], $matches);
194
        if (!isset($matches[1])) {
195
            return false;
196
        }
197
        $entityId = $matches[1];
198
        $url = \config\Diagnostics::EDUGAINRESOLVER['url'] . "?action=get_entity_name&type=idp&opt=2&e_id=$entityId";
199
        \core\common\Logging::debug_s(4, $url, "URL: ","\n");
200
        $ch = curl_init($url);
201
        if ($ch === false) {
202
            $loggerInstance->debug(2, "Unable ask eduGAIN about IdP - CURL init failed!");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $loggerInstance seems to be never defined.
Loading history...
203
            return false;
204
        }
205
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
206
        curl_setopt($ch, CURLOPT_TIMEOUT, \config\Diagnostics::EDUGAINRESOLVER['timeout']);
207
        $response = curl_exec($ch);
208
        \core\common\Logging::debug_s(4, $response, "RESP\n", "\n");
209
        if (function_exists('json_validate')) {
210
            if (json_validate($response) === false) {
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_validate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

210
            if (json_validate(/** @scrutinizer ignore-type */ $response) === false) {
Loading history...
211
                \core\common\Logging::debug_s(2, "eduGAIN resolver did not return valid json\n");
212
                return false;
213
            }
214
        }
215
        $responseDetails = json_decode($response, true);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

215
        $responseDetails = json_decode(/** @scrutinizer ignore-type */ $response, true);
Loading history...
216
        if ($responseDetails == null || !isset($responseDetails['status'])) {
217
            \core\common\Logging::debug_s(2, $response, "EDUGAINRESOLVER returned incorrect response:\n", "\n");
218
            return false;
219
        }
220
        if ($responseDetails['status'] !== 1) {
221
            \core\common\Logging::debug_s(4, "EDUGAINRESOLVER returned status 0\n");
222
            return false;            
223
        }
224
        if ($responseDetails['name'] === null) {
225
            \core\common\Logging::debug_s(4,"User not in eduGAIN\n");
226
            return false;            
227
        }
228
        \core\common\Logging::debug_s(4,"User in eduGAIN\n");
229
        $_SESSION['eduGAIN'] = $responseDetails['regauth'];
230
        return true;
231
    }
232
    
233
    /**
234
     * This function lists all institution ids for which the user appears as admin
235
     * 
236
     * @return array if institution ids.
237
     */
238
    public function listOwnerships() {
239
        $dbHandle = \core\DBConnection::handle("INST");
240
        $query = $dbHandle->exec("SELECT institution_id FROM ownership WHERE user_id='".$this->userName."'");
241
        return array_column($query->fetch_all(), 0);
242
    }
243
244
    /**
245
     * shorthand function for email sending to the user
246
     * 
247
     * @param string $subject addressee of the mail
248
     * @param string $content content of the mail
249
     * @return boolean did it work?
250
     */
251
    public function sendMailToUser($subject, $content)
252
    {
253
        $mailaddr = $this->getAttributes("user:email");
254
        if (count($mailaddr) == 0) { // we don't know user's mail address
255
            return false;
256
        }
257
        return $this::doMailing($mailaddr[0]["value"], $subject, $content);
258
    }
259
260
    /**
261
     * sending mail to CAT admins (if defined in Master), mainly for debugging purposes
262
     * 
263
     * @param string $subject
264
     * @param string $content
265
     * @return boolean did it work?
266
     */
267
    public static function sendMailToCATadmins($subject, $content) {
268
        if (!isset(\config\Master::APPEARANCE['cat-admin-mail']) ||  \config\Master::APPEARANCE['cat-admin-mail'] === []) {
269
            return;
270
        }
271
        foreach (\config\Master::APPEARANCE['cat-admin-mail'] as $mailaddr) {
272
            $sent = User::doMailing($mailaddr, $subject, $content);
273
            if (!$sent) {
274
                \core\common\Logging::debug_s(2, $mailaddr, "Mailing to: ", " failed\n");
275
            }
276
        }
277
    }
278
279
    /**
280
     * shorthand function for actual email sending to the user
281
     * 
282
     * @param array $mailaddr the mail address ro mail to
283
     * @param string $subject addressee of the mail
284
     * @param string $content content of the mail
285
     * @return boolean did it work?
286
     */    
287
    private static function doMailing($mailaddr, $subject, $content) {
288
        common\Entity::intoThePotatoes();
289
        $mail = \core\common\OutsideComm::mailHandle();
290
// who to whom?
291
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
292
        $mail->addReplyTo(\config\Master::APPEARANCE['support-contact']['developer-mail'], \config\Master::APPEARANCE['productname'] . " " . _("Feedback"));
293
        $mail->addAddress($mailaddr);
0 ignored issues
show
Bug introduced by
$mailaddr of type array is incompatible with the type string expected by parameter $address of PHPMailer\PHPMailer\PHPMailer::addAddress(). ( Ignorable by Annotation )

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

293
        $mail->addAddress(/** @scrutinizer ignore-type */ $mailaddr);
Loading history...
294
// what do we want to say?
295
        $mail->Subject = $subject;
296
        $mail->Body = $content;
297
298
        $sent = $mail->send();
299
        common\Entity::outOfThePotatoes();
300
        return $sent;        
301
    }
302
303
    /**
304
     * NOOP in this class, only need to override abstract base class
305
     * 
306
     * @return void
307
     */
308
    public function updateFreshness()
309
    {
310
        // User is always fresh
311
    }
312
313
    const PROVIDER_STRINGS = [
314
        "eduPersonTargetedID" => "eduGAIN",
315
        "pairwise-id" => "eduGAIN",
316
        "facebook_targetedID" => "Facebook",
317
        "google_eppn" => "Google",
318
        "linkedin_targetedID" => "LinkedIn",
319
        "twitter_targetedID" => "Twitter",
320
        "openid" => "Google (defunct)",
321
    ];
322
323
    /**
324
     * Some users apparently forget which eduGAIN/social ID they originally used
325
     * to log into CAT. We can try to help them: if they tell us the email
326
     * address by which they received the invitation token, then we can see if
327
     * any CAT IdPs are associated to an account which originally came in via
328
     * that email address. We then see which pretty-print auth provider name
329
     * was used
330
     * 
331
     * @param string $mail mail address to search with
332
     * @param string $lang language for the eduGAIN request
333
     * @return boolean|array the list of auth source IdPs we found for the mail, or false if none found or invalid input
334
     */
335
    public static function findLoginIdPByEmail($mail, $lang)
336
    {
337
        $loggerInstance = new common\Logging();
338
        $listOfProviders = [];
339
        $matchedProviders = [];
340
        $skipCurl = 0;
341
        $realmail = filter_var($mail, FILTER_VALIDATE_EMAIL);
342
        if ($realmail === false) {
343
            return false;
344
        }
345
        $dbHandle = \core\DBConnection::handle("INST");
346
        $query = $dbHandle->exec("SELECT user_id FROM ownership WHERE orig_mail = ?", "s", $realmail);
347
348
        // SELECT -> resource, not boolean
349
        while ($oneRow = mysqli_fetch_object(/** @scrutinizer ignore-type */ $query)) {
350
            $matches = [];
351
            $lookFor = "";
352
            foreach (User::PROVIDER_STRINGS as $name => $prettyname) {
353
                if ($lookFor != "") {
354
                    $lookFor .= "|";
355
                }
356
                $lookFor .= "$name";
357
            }
358
            $finding = preg_match("/^(" . $lookFor . "):(.*)/", $oneRow->user_id, $matches);
359
            if ($finding === 0 || $finding === false) {
360
                return false;
361
            }
362
363
            $providerStrings = array_keys(User::PROVIDER_STRINGS);
364
            switch ($matches[1]) {
365
                case $providerStrings[0]: // eduGAIN needs to find the exact IdP behind it
366
                case $providerStrings[1]:
367
                    $moreMatches = [];
368
                    $exactIdP = preg_match("/.*!([^!]*)$/", $matches[2], $moreMatches);
369
                    if ($exactIdP === 0 || $exactIdP === false) {
370
                        break;
371
                    }
372
                    $idp = $moreMatches[1];
373
                    if (!in_array($idp, $matchedProviders)) {
374
                        $matchedProviders[] = $idp;
375
                        $name = $idp;
376
                        if ($skipCurl == 0) {
377
                            $url = \config\Diagnostics::EDUGAINRESOLVER['url'] . "?action=get_entity_name&opt=2&type=idp&e_id=$idp&lang=$lang";
378
                            $ch = curl_init($url);
379
                            if ($ch === false) {
380
                                $loggerInstance->debug(2, "Unable ask eduGAIN about IdP - CURL init failed!");
381
                                break;
382
                            }
383
                            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
384
                            curl_setopt($ch, CURLOPT_TIMEOUT, \config\Diagnostics::EDUGAINRESOLVER['timeout']);
385
                            $response = curl_exec($ch);
386
                            if (function_exists('json_validate')) {
387
                                if (json_validate($response) === false) {
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $json of json_validate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

387
                                if (json_validate(/** @scrutinizer ignore-type */ $response) === false) {
Loading history...
388
                                    \core\common\Logging::debug_s(2, "eduGAIN resolver did not return valid json\n");
389
                                    return false;
390
                                }
391
                            }
392
                            if (is_bool($response)) { // catch both false and TRUE because we use CURLOPT_RETURNTRANSFER
393
                                $skipCurl = 1;
394
                            } else {
395
                                $responseDetails = json_decode($response, true);
396
                                if (isset($responseDetails['status']) && $responseDetails['status'] === 1 && $responseDetails['name'] !== null) {
397
                                    $name = $responseDetails['name'];
398
                                }
399
                            }
400
                            curl_close($ch);
401
                        }
402
                        $listOfProviders[] = User::PROVIDER_STRINGS[$providerStrings[0]] . " - IdP: " . $name;
403
                    }
404
                    break;
405
                case $providerStrings[2]:
406
                case $providerStrings[2]:
407
                case $providerStrings[3]:
408
                case $providerStrings[4]:
409
                case $providerStrings[6]:
410
                    if (!in_array(User::PROVIDER_STRINGS[$matches[1]], $listOfProviders)) {
411
                        $listOfProviders[] = User::PROVIDER_STRINGS[$matches[1]];
412
                    }
413
                    break;
414
                default:
415
                    return false;
416
            }
417
        }
418
        \core\common\Logging::debug_s(4,$listOfProviders, "PROVIDERS:\n", "\n");
419
        return $listOfProviders;
420
    }
421
}