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

User   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 139
c 0
b 0
f 0
dl 0
loc 291
rs 8.8798

10 Methods

Rating   Name   Duplication   Size   Complexity  
A updateFreshness() 0 2 1
A sendMailToUser() 0 20 2
A isSupport() 0 3 1
B __construct() 0 36 7
A isIdPOwner() 0 9 3
A isFederationAdmin() 0 15 5
A isFromEduGAIN() 0 17 3
D findLoginIdPByEmail() 0 74 20
A isSuperadmin() 0 3 1
A listOwnerships() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use User, and based on these observations, apply Extract Interface, too.

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
    /**
55
     * Class constructor. The required argument is a user's persistent identifier as was returned by the authentication source.
56
     * 
57
     * @param string $userId User Identifier as per authentication source
58
     */
59
    public function __construct($userId)
60
    {
61
        $this->databaseType = "USER";
62
        parent::__construct(); // database handle is now available
63
        $this->attributes = [];
64
        $this->entityOptionTable = "user_options";
65
        $this->entityIdColumn = "user_id";
66
        $this->identifier = 0; // not used
67
        $this->userName = $userId;
68
        $this->edugain = $this->isFromEduGAIN();
69
        $optioninstance = Options::instance();
70
71
        if (\config\ConfAssistant::CONSORTIUM['name'] == "eduroam" && isset(\config\ConfAssistant::CONSORTIUM['deployment-voodoo']) && \config\ConfAssistant::CONSORTIUM['deployment-voodoo'] == "Operations Team") { // SW: APPROVED
72
// e d u r o a m DB doesn't follow the usual approach
73
// we could get multiple rows below (if administering multiple
74
// federations), so consolidate all into the usual options
75
            $info = $this->databaseHandle->exec("SELECT email, common_name, role, realm FROM view_admin WHERE eptid = ?", "s", $this->userName);
76
            $visited = FALSE;
77
            // SELECT -> resource, not boolean
78
            while ($userDetailQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $info)) {
79
                if (!$visited) {
80
                    $mailOptinfo = $optioninstance->optionType("user:email");
81
                    $this->attributes[] = ["name" => "user:email", "lang" => NULL, "value" => $userDetailQuery->email, "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $mailOptinfo['flag']];
82
                    $realnameOptinfo = $optioninstance->optionType("user:realname");
83
                    $this->attributes[] = ["name" => "user:realname", "lang" => NULL, "value" => $userDetailQuery->common_name, "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $realnameOptinfo['flag']];
84
                    $visited = TRUE;
85
                }
86
                if ($userDetailQuery->role == "fedadmin") {
87
                    $optinfo = $optioninstance->optionType("user:fedadmin");
88
                    $this->attributes[] = ["name" => "user:fedadmin", "lang" => NULL, "value" => strtoupper($userDetailQuery->realm), "level" => Options::LEVEL_USER, "row_id" => 0, "flag" => $optinfo['flag']];
89
                }
90
            }
91
        } else {
92
            $this->attributes = $this->retrieveOptionsFromDatabase("SELECT DISTINCT option_name, option_lang, option_value, row_id
93
                                                FROM $this->entityOptionTable
94
                                                WHERE $this->entityIdColumn = ?", "User");
95
        }
96
    }
97
98
    /**
99
     * This function checks whether a user is a federation administrator. When called without argument, it only checks if the
100
     * user is a federation administrator of *any* federation. When given a parameter (ISO shortname of federation), it checks
101
     * if the user administers this particular federation.
102
     * 
103
     * @param string $federation optional: federation to be checked
104
     * @return boolean TRUE if the user is federation admin, FALSE if not 
105
     */
106
    public function isFederationAdmin($federation = 0)
107
    {
108
        $feds = $this->getAttributes("user:fedadmin");
109
        if (count($feds) == 0) { // not a fedadmin at all
110
            return FALSE;
111
        }
112
        if ($federation === 0) { // fedadmin for one; that's all we want to know
113
            return TRUE;
114
        }
115
        foreach ($feds as $fed) { // check if authz is for requested federation
116
            if (strtoupper($fed['value']) == strtoupper($federation)) {
117
                return TRUE;
118
            }
119
        }
120
        return FALSE; // no luck so far? Not the admin we are looking for.
121
    }
122
123
    /**
124
     * This function tests if the current user has been configured as the system superadmin, i.e. if the user is allowed
125
     * to execute the 112365365321.php script and obtain read-only access to admin areas.
126
     *
127
     * @return boolean TRUE if the user is a superadmin, FALSE if not 
128
     */
129
    public function isSuperadmin()
130
    {
131
        return in_array($this->userName, \config\Master::SUPERADMINS);
132
    }
133
    
134
    
135
    /**
136
     * This function tests if the current user has been configured as the system superadmin, i.e. if the user is allowed
137
     *  obtain read-only access to admin areas.
138
     *
139
     * @return boolean TRUE if the user is a support member, FALSE if not 
140
     */
141
    public function isSupport()
142
    {
143
        return in_array($this->userName, \config\Master::SUPPORT);
144
    }
145
146
    /**
147
     * This function tests if the current user is an ovner of a given IdP
148
     *
149
     * @param int $idp integer identifier of the IdP
150
     * @return boolean TRUE if the user is an owner, FALSE if not 
151
     */
152
    public function isIdPOwner($idp)
153
    {
154
        $temp = new IdP($idp);
155
        foreach ($temp->listOwners() as $oneowner) {
156
            if ($oneowner['ID'] == $this->userName) {
157
                return TRUE;
158
            }
159
        }
160
        return FALSE;
161
    }
162
    
163
    /** This function tests if user's IdP is listed in eduGAIN - it uses an external 
164
     *  call to technical eduGAIN API
165
     * 
166
     * @return boolean true if the IdP is listed, false otherwise
167
     * 
168
     */
169
    public function isFromEduGAIN()
170
    {
171
        $loggerInstance = new common\Logging();
172
        $entityId = preg_replace('/^.*=!/','', $_SESSION['user']);
173
        $url = \config\Diagnostics::EDUGAINRESOLVER['url'] . "?action=get_entity_name&type=idp&e_id=$entityId";
174
        $ch = curl_init($url);
175
        if ($ch === FALSE) {
176
            $loggerInstance->debug(2, "Unable ask eduGAIN about IdP - CURL init failed!");
177
            return false;
178
        }
179
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
180
        curl_setopt($ch, CURLOPT_TIMEOUT, \config\Diagnostics::EDUGAINRESOLVER['timeout']);
181
        $response = curl_exec($ch);
182
        if ($response === "null") {
183
            return false;
184
        } else {
185
            return true;
186
        }
187
    }
188
    
189
    /**
190
     * This function lists all institution ids for which the user appears as admin
191
     * 
192
     * @return array if institution ids.
193
     */
194
    public function listOwnerships() {
195
        $dbHandle = \core\DBConnection::handle("INST");
196
        $query = $dbHandle->exec("SELECT institution_id FROM ownership WHERE user_id='".$this->userName."'");
197
        return array_column($query->fetch_all(), 0);
198
    }
199
200
    /**
201
     * shorthand function for email sending to the user
202
     * 
203
     * @param string $subject addressee of the mail
204
     * @param string $content content of the mail
205
     * @return boolean did it work?
206
     */
207
    public function sendMailToUser($subject, $content)
208
    {
209
210
        $mailaddr = $this->getAttributes("user:email");
211
        if (count($mailaddr) == 0) { // we don't know user's mail address
212
            return FALSE;
213
        }
214
        common\Entity::intoThePotatoes();
215
        $mail = \core\common\OutsideComm::mailHandle();
216
// who to whom?
217
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
218
        $mail->addReplyTo(\config\Master::APPEARANCE['support-contact']['developer-mail'], \config\Master::APPEARANCE['productname'] . " " . _("Feedback"));
219
        $mail->addAddress($mailaddr[0]["value"]);
220
// what do we want to say?
221
        $mail->Subject = $subject;
222
        $mail->Body = $content;
223
224
        $sent = $mail->send();
225
        common\Entity::outOfThePotatoes();
226
        return $sent;
227
    }
228
229
    /**
230
     * NOOP in this class, only need to override abstract base class
231
     * 
232
     * @return void
233
     */
234
    public function updateFreshness()
235
    {
236
        // User is always fresh
237
    }
238
239
    const PROVIDER_STRINGS = [
240
        "eduPersonTargetedID" => "eduGAIN",
241
        "facebook_targetedID" => "Facebook",
242
        "google_eppn" => "Google",
243
        "linkedin_targetedID" => "LinkedIn",
244
        "twitter_targetedID" => "Twitter",
245
        "openid" => "Google (defunct)",
246
    ];
247
248
    /**
249
     * Some users apparently forget which eduGAIN/social ID they originally used
250
     * to log into CAT. We can try to help them: if they tell us the email
251
     * address by which they received the invitation token, then we can see if
252
     * any CAT IdPs are associated to an account which originally came in via
253
     * that email address. We then see which pretty-print auth provider name
254
     * was used
255
     * 
256
     * @param string $mail mail address to search with
257
     * @param string $lang language for the eduGAIN request
258
     * @return boolean|array the list of auth source IdPs we found for the mail, or FALSE if none found or invalid input
259
     */
260
    public static function findLoginIdPByEmail($mail, $lang)
261
    {
262
        $loggerInstance = new common\Logging();
263
        $listOfProviders = [];
264
        $matchedProviders = [];
265
        $skipCurl = 0;
266
        $realmail = filter_var($mail, FILTER_VALIDATE_EMAIL);
267
        if ($realmail === FALSE) {
268
            return FALSE;
269
        }
270
        $dbHandle = \core\DBConnection::handle("INST");
271
        $query = $dbHandle->exec("SELECT user_id FROM ownership WHERE orig_mail = ?", "s", $realmail);
272
273
        // SELECT -> resource, not boolean
274
        while ($oneRow = mysqli_fetch_object(/** @scrutinizer ignore-type */ $query)) {
275
            $matches = [];
276
            $lookFor = "";
277
            foreach (User::PROVIDER_STRINGS as $name => $prettyname) {
278
                if ($lookFor != "") {
279
                    $lookFor .= "|";
280
                }
281
                $lookFor .= "$name";
282
            }
283
            $finding = preg_match("/^(" . $lookFor . "):(.*)/", $oneRow->user_id, $matches);
284
            if ($finding === 0 || $finding === FALSE) {
285
                return FALSE;
286
            }
287
288
            $providerStrings = array_keys(User::PROVIDER_STRINGS);
289
            switch ($matches[1]) {
290
                case $providerStrings[0]: // eduGAIN needs to find the exact IdP behind it
291
                    $moreMatches = [];
292
                    $exactIdP = preg_match("/.*!(.*)$/", $matches[2], $moreMatches);
293
                    if ($exactIdP === 0 || $exactIdP === FALSE) {
294
                        break;
295
                    }
296
                    $idp = $moreMatches[1];
297
                    if (!in_array($idp, $matchedProviders)) {
298
                        $matchedProviders[] = $idp;
299
                        $name = $idp;
300
                        if ($skipCurl == 0) {
301
                            $url = \config\Diagnostics::EDUGAINRESOLVER['url'] . "?action=get_entity_name&type=idp&e_id=$idp&lang=$lang";
302
                            $ch = curl_init($url);
303
                            if ($ch === FALSE) {
304
                                $loggerInstance->debug(2, "Unable ask eduGAIN about IdP - CURL init failed!");
305
                                break;
306
                            }
307
                            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
308
                            curl_setopt($ch, CURLOPT_TIMEOUT, \config\Diagnostics::EDUGAINRESOLVER['timeout']);
309
                            $response = curl_exec($ch);
310
                            if (is_bool($response)) { // catch both FALSE and TRUE because we use CURLOPT_RETURNTRANSFER
311
                                $skipCurl = 1;
312
                            } else {
313
                                $name = json_decode($response);
314
                            }
315
                            curl_close($ch);
316
                        }
317
                        $listOfProviders[] = User::PROVIDER_STRINGS[$providerStrings[0]] . " - IdP: " . $name;
318
                    }
319
                    break;
320
                case $providerStrings[1]:
321
                case $providerStrings[2]:
322
                case $providerStrings[3]:
323
                case $providerStrings[4]:
324
                case $providerStrings[5]:
325
                    if (!in_array(User::PROVIDER_STRINGS[$matches[1]], $listOfProviders)) {
326
                        $listOfProviders[] = User::PROVIDER_STRINGS[$matches[1]];
327
                    }
328
                    break;
329
                default:
330
                    return FALSE;
331
            }
332
        }
333
        return $listOfProviders;
334
    }
335
}