Passed
Push — release_2_1 ( c5a9af...7447ed )
by Tomasz
29:40
created

User::isFederationAdmin()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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