Passed
Push — release_2_1 ( 0722bc...36e192 )
by Tomasz
26:43
created

User::sendMailToUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 20
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
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
    /**
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 and obtain read-only access to admin areas.
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
    /**
133
     * This function tests if the current user has been configured as the system superadmin, i.e. if the user is allowed
134
     *  obtain read-only access to admin areas.
135
     *
136
     * @return boolean TRUE if the user is a support member, FALSE if not 
137
     */
138
    public function isSupport()
139
    {
140
        return in_array($this->userName, \config\Master::SUPPORT);
141
    }
142
143
    /**
144
     * This function tests if the current user is an ovner of a given IdP
145
     *
146
     * @param int $idp integer identifier of the IdP
147
     * @return boolean TRUE if the user is an owner, FALSE if not 
148
     */
149
    public function isIdPOwner($idp)
150
    {
151
        $temp = new IdP($idp);
152
        foreach ($temp->listOwners() as $oneowner) {
153
            if ($oneowner['ID'] == $this->userName) {
154
                return TRUE;
155
            }
156
        }
157
        return FALSE;
158
    }
159
    
160
    /**
161
     * This function lists all institution ids for which the user appears as admin
162
     * 
163
     * @return array if institution ids.
164
     */
165
    public function listOwnerships() {
166
        $dbHandle = \core\DBConnection::handle("INST");
167
        $query = $dbHandle->exec("SELECT institution_id FROM ownership WHERE user_id='".$this->userName."'");
168
        return array_column($query->fetch_all(), 0);
169
    }
170
171
    /**
172
     * shorthand function for email sending to the user
173
     * 
174
     * @param string $subject addressee of the mail
175
     * @param string $content content of the mail
176
     * @return boolean did it work?
177
     */
178
    public function sendMailToUser($subject, $content)
179
    {
180
181
        $mailaddr = $this->getAttributes("user:email");
182
        if (count($mailaddr) == 0) { // we don't know user's mail address
183
            return FALSE;
184
        }
185
        common\Entity::intoThePotatoes();
186
        $mail = \core\common\OutsideComm::mailHandle();
187
// who to whom?
188
        $mail->FromName = \config\Master::APPEARANCE['productname'] . " Notification System";
189
        $mail->addReplyTo(\config\Master::APPEARANCE['support-contact']['developer-mail'], \config\Master::APPEARANCE['productname'] . " " . _("Feedback"));
190
        $mail->addAddress($mailaddr[0]["value"]);
191
// what do we want to say?
192
        $mail->Subject = $subject;
193
        $mail->Body = $content;
194
195
        $sent = $mail->send();
196
        common\Entity::outOfThePotatoes();
197
        return $sent;
198
    }
199
200
    /**
201
     * NOOP in this class, only need to override abstract base class
202
     * 
203
     * @return void
204
     */
205
    public function updateFreshness()
206
    {
207
        // User is always fresh
208
    }
209
210
    const PROVIDER_STRINGS = [
211
        "eduPersonTargetedID" => "eduGAIN",
212
        "facebook_targetedID" => "Facebook",
213
        "google_eppn" => "Google",
214
        "linkedin_targetedID" => "LinkedIn",
215
        "twitter_targetedID" => "Twitter",
216
        "openid" => "Google (defunct)",
217
    ];
218
219
    /**
220
     * Some users apparently forget which eduGAIN/social ID they originally used
221
     * to log into CAT. We can try to help them: if they tell us the email
222
     * address by which they received the invitation token, then we can see if
223
     * any CAT IdPs are associated to an account which originally came in via
224
     * that email address. We then see which pretty-print auth provider name
225
     * was used
226
     * 
227
     * @param string $mail mail address to search with
228
     * @param string $lang language for the eduGAIN request
229
     * @return boolean|array the list of auth source IdPs we found for the mail, or FALSE if none found or invalid input
230
     */
231
    public static function findLoginIdPByEmail($mail, $lang)
232
    {
233
        $loggerInstance = new common\Logging();
234
        $listOfProviders = [];
235
        $matchedProviders = [];
236
        $skipCurl = 0;
237
        $realmail = filter_var($mail, FILTER_VALIDATE_EMAIL);
238
        if ($realmail === FALSE) {
239
            return FALSE;
240
        }
241
        $dbHandle = \core\DBConnection::handle("INST");
242
        $query = $dbHandle->exec("SELECT user_id FROM ownership WHERE orig_mail = ?", "s", $realmail);
243
244
        // SELECT -> resource, not boolean
245
        while ($oneRow = mysqli_fetch_object(/** @scrutinizer ignore-type */ $query)) {
246
            $matches = [];
247
            $lookFor = "";
248
            foreach (User::PROVIDER_STRINGS as $name => $prettyname) {
249
                if ($lookFor != "") {
250
                    $lookFor .= "|";
251
                }
252
                $lookFor .= "$name";
253
            }
254
            $finding = preg_match("/^(" . $lookFor . "):(.*)/", $oneRow->user_id, $matches);
255
            if ($finding === 0 || $finding === FALSE) {
256
                return FALSE;
257
            }
258
259
            $providerStrings = array_keys(User::PROVIDER_STRINGS);
260
            switch ($matches[1]) {
261
                case $providerStrings[0]: // eduGAIN needs to find the exact IdP behind it
262
                    $moreMatches = [];
263
                    $exactIdP = preg_match("/.*!(.*)$/", $matches[2], $moreMatches);
264
                    if ($exactIdP === 0 || $exactIdP === FALSE) {
265
                        break;
266
                    }
267
                    $idp = $moreMatches[1];
268
                    if (!in_array($idp, $matchedProviders)) {
269
                        $matchedProviders[] = $idp;
270
                        $name = $idp;
271
                        if ($skipCurl == 0) {
272
                            $url = \config\Diagnostics::EDUGAINRESOLVER['url'] . "?action=get_entity_name&type=idp&e_id=$idp&lang=$lang";
273
                            $ch = curl_init($url);
274
                            if ($ch === FALSE) {
275
                                $loggerInstance->debug(2, "Unable ask eduGAIN about IdP - CURL init failed!");
276
                                break;
277
                            }
278
                            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
279
                            curl_setopt($ch, CURLOPT_TIMEOUT, \config\Diagnostics::EDUGAINRESOLVER['timeout']);
280
                            $response = curl_exec($ch);
281
                            if (is_bool($response)) { // catch both FALSE and TRUE because we use CURLOPT_RETURNTRANSFER
282
                                $skipCurl = 1;
283
                            } else {
284
                                $name = json_decode($response);
285
                            }
286
                            curl_close($ch);
287
                        }
288
                        $listOfProviders[] = User::PROVIDER_STRINGS[$providerStrings[0]] . " - IdP: " . $name;
289
                    }
290
                    break;
291
                case $providerStrings[1]:
292
                case $providerStrings[2]:
293
                case $providerStrings[3]:
294
                case $providerStrings[4]:
295
                case $providerStrings[5]:
296
                    if (!in_array(User::PROVIDER_STRINGS[$matches[1]], $listOfProviders)) {
297
                        $listOfProviders[] = User::PROVIDER_STRINGS[$matches[1]];
298
                    }
299
                    break;
300
                default:
301
                    return FALSE;
302
            }
303
        }
304
        return $listOfProviders;
305
    }
306
}