Passed
Push — master ( 58805a...5272c2 )
by Stefan
06:31
created

Logopath::__construct()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 83
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
c 1
b 0
f 0
dl 0
loc 83
rs 8.4525
cc 5
nc 12
nop 0

How to fix   Long Method   

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
namespace core\diag;
24
25
/**
26
 * This class evaluates the evidence of previous Telepath and/or Sociopath runs
27
 * and figures out whom to send emails to, and with that content. It then sends
28
 * these emails.
29
 */
30
class Logopath extends AbstractTest
31
{
32
33
    /**
34
     * storing the end user's email, if he has given it to us
35
     * @var string|boolean
36
     */
37
    private $userEmail;
38
39
    /**
40
     * maybe the user has some additional evidence directly on his device?
41
     * @var string|boolean
42
     */
43
    private $additionalScreenshot;
44
45
    /**
46
     * the list of mails to send
47
     * @var array
48
     */
49
    private $mailStack;
50
51
    /*
52
     * categories of people to contact
53
     */
54
    const TARGET_EDUROAM_OT = 0;
55
    const TARGET_NRO_IDP = 1;
56
    const TARGET_NRO_SP = 2;
57
    const TARGET_IDP = 3;
58
    const TARGET_SP = 4;
59
    const TARGET_ENDUSER = 5;
60
61
    /** we start all our mails with a common prefix, internationalised
62
     *
63
     * @var string
64
     */
65
    private $subjectPrefix;
66
67
    /** and we end with a greeting/disclaimer
68
     *
69
     * @var string
70
     */
71
    private $finalGreeting;
72
73
    /**
74
     * We need to vet user inputs.
75
     * @var \web\lib\common\InputValidation
76
     */
77
    private $validatorInstance;
78
79
    /**
80
     * will be filled with the exact emails to send, by determineMailsToSend()
81
     * @var array
82
     */
83
    private $mailQueue;
84
85
    /**
86
     *
87
     * @var array
88
     */
89
    private $concreteRecipients;
90
91
    /*
92
     *  cases to consider
93
     */
94
    const IDP_EXISTS_BUT_NO_DATABASE = 100;
95
    const IDP_SUSPECTED_PROBLEM_INTERACTIVE_FORCED = 101;
96
    const IDP_SUSPECTED_PROBLEM_INTERACTION_EVIDENCED = 102;
97
98
    /*
99
     * types of supplemental string data to send 
100
     */
101
    
102
    
103
    /**
104
     * initialise the class: maintain state of existing evidence, and get translated versions of email texts etc.
105
     */
106
    public function __construct()
107
    {
108
        parent::__construct();
109
        \core\common\Entity::intoThePotatoes();
110
        $this->userEmail = FALSE;
111
        $this->additionalScreenshot = FALSE;
112
113
        $this->mailQueue = [];
114
        $this->concreteRecipients = [];
115
116
        $this->validatorInstance = new \web\lib\common\InputValidation();
117
118
        $this->possibleFailureReasons = $_SESSION["SUSPECTS"] ?? []; // if we know nothing, don't talk to anyone
119
        $this->additionalFindings = $_SESSION["EVIDENCE"] ?? [];
120
121
        $this->subjectPrefix = _("[eduroam Diagnostics]") . " ";
122
        $this->finalGreeting = "\n"
123
                . _("(This service is in an early stage. We apologise if this is a false alert. If this is the case, please send an email report to [email protected], forwarding the entire message (including the 'SUSPECTS' and 'EVIDENCE' data at the end), and explain why this is a false positive.)")
124
                . "\n"
125
                . _("Yours sincerely,") . "\n"
126
                . "\n"
127
                . _("Ed U. Roam, the eduroam diagnostics algorithm");
128
129
        $this->mailStack = [
130
            Logopath::IDP_EXISTS_BUT_NO_DATABASE => [
131
                "to" => [Logopath::NRO_IDP],
0 ignored issues
show
Bug introduced by
The constant core\diag\Logopath::NRO_IDP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
132
                "cc" => [Logopath::EDUROAM_OT],
0 ignored issues
show
Bug introduced by
The constant core\diag\Logopath::EDUROAM_OT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
133
                "bcc" => [],
134
                "reply-to" => [Logopath::EDUROAM_OT],
135
                "subject" => _("[POLICYVIOLATION NATIONAL] IdP with no entry in eduroam database"),
136
                "body" => _("Dear NRO administrator,") . "\n"
137
                . "\n"
138
                . wordwrap(sprintf(_("an end-user requested diagnostics for realm %s. Real-time connectivity checks determined that the realm exists, but we were unable to find an IdP with that realm in the eduroam database."), $this->additionalFindings['REALM'])) . "\n"
139
                . "\n"
140
                . _("By not listing IdPs in the eduroam database, you are violating the eduroam policy.") . "\n"
141
                . "\n"
142
                . _("Additionally, this creates operational issues. In particular, we are unable to direct end users to their IdP for further diagnosis/instructions because there are no contact points for that IdP in the database.") . "\n"
143
                . "\n"
144
                . _("Please stop the policy violation ASAP by listing the IdP which is associated to this realm.")
145
                . "\n",
146
            ],
147
            Logopath::IDP_SUSPECTED_PROBLEM_INTERACTIVE_FORCED => [
148
                "to" => [Logopath::TARGET_IDP],
149
                "cc" => [],
150
                "bcc" => [],
151
                "reply-to" => [Logopath::TARGET_ENDUSER],
152
                "subject" => _("[TECHNICAL PROBLEM] Administrator suspects technical problem with your IdP"),
153
                "body" => _("Dear IdP administrator,") . "\n"
154
                . "\n"
155
                . sprintf(_("an organisation administrator requested diagnostics for realm %s. "), $this->additionalFindings['REALM']) 
156
                . "\n"
157
                . _("Real-time connectivity checks determined that the realm appears to be working in acceptable parameters, but the administrator insisted to contact you with the supplemental information below.") . "\n"
158
                . "\n",
159
            ],
160
            Logopath::IDP_SUSPECTED_PROBLEM_INTERACTION_EVIDENCED => [
161
                "to" => [Logopath::TARGET_IDP],
162
                "cc" => [],
163
                "bcc" => [],
164
                "reply-to" => [Logopath::TARGET_ENDUSER],
165
                "subject" => _("[TECHNICAL PROBLEM] Administrator suspects technical problem with your IdP"),
166
                "body" => _("Dear IdP administrator,") . "\n"
167
                . "\n"
168
                . sprintf(_("an organisation administrator requested diagnostics for realm %s. "), $this->additionalFindings['REALM']) 
169
                . "\n"
170
                . _("Real-time connectivity checks determined that the realm indeed has an operational problem at this point in time. Please see the supplemental information below.") . "\n"
171
                . "\n",
172
            ],
173
        ];
174
        
175
        // add exalted human-readable information to main mail body
176
        foreach ($this->mailStack as $oneEntry)
177
        if (isset($this->additionalFindings['INTERACTIVE_ENDUSER_AUTH_TIMESTAMP'])) {
178
            $oneEntry .= _("Authentication/Attempt Timestamp of user session:") ." ".$this->additionalFindings['INTERACTIVE_ENDUSER_AUTH_TIMESTAMP']."\n";
179
        }
180
        if (isset($this->additionalFindings['INTERACTIVE_ENDUSER_MAC'])) {
181
            $oneEntry .= _("MAC address of end user in question:") ." ".$this->additionalFindings['INTERACTIVE_ENDUSER_MAC']."\n";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $oneEntry seems to be defined by a foreach iteration on line 176. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
182
        }
183
        if (isset($this->additionalFindings['INTERACTIVE_ADDITIONAL_COMMENTS'])) {
184
            $oneEntry .= _("Additional Comments:") ." ".$this->additionalFindings['INTERACTIVE_ADDITIONAL_COMMENTS']."\n";
185
        }
186
        
187
        
188
        \core\common\Entity::outOfThePotatoes();
189
        
190
    }
191
192
    /**
193
     * if the system asked the user for his email and he's willing to give it to
194
     * us, store it with this function
195
     * 
196
     * @param string $userEmail the end-users email to store
197
     * @return void
198
     */
199
    public function addUserEmail($userEmail)
200
    {
201
// returns FALSE if it was not given or bogus, otherwise stores this as mail target
202
        $this->userEmail = $this->validatorInstance->email($userEmail);
203
    }
204
205
    /**
206
     * if the system asked the user for a screenshot and he's willing to give one
207
     * to us, store it with this function
208
     * 
209
     * @param string $binaryData the submitted binary data, to be vetted
210
     * @return void
211
     */
212
    public function addScreenshot($binaryData)
213
    {
214
        if ($this->validatorInstance->image($binaryData) === TRUE) {
215
            // on CentOS and RHEL 8, look for Gmagick, else Imagick
216
            if (strpos(php_uname("r"), "el8") !== FALSE) {
217
                $magick = new \Gmagick();
218
            } else {
219
                $magick = new \Imagick();
220
            }
221
            $magick->readimageblob($binaryData);
222
            $magick->setimageformat("png");
223
            $this->additionalScreenshot = $magick->getimageblob();
0 ignored issues
show
Bug introduced by
The method getimageblob() does not exist on Gmagick. Did you maybe mean getimagedelay()? ( Ignorable by Annotation )

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

223
            /** @scrutinizer ignore-call */ 
224
            $this->additionalScreenshot = $magick->getimageblob();

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...
224
        } else {
225
            // whatever we got, it didn't parse as an image
226
            $this->additionalScreenshot = FALSE;
227
        }
228
    }
229
230
    /**
231
     * looks at probabilities and evidence, and decides which mail scenario(s) to send
232
     * 
233
     * @return void
234
     */
235
    private function determineMailsToSend()
236
    {
237
        $this->mailQueue = [];
238
// check for IDP_EXISTS_BUT_NO_DATABASE
239
        if (!in_array(AbstractTest::INFRA_NONEXISTENTREALM, $this->possibleFailureReasons) && $this->additionalFindings[AbstractTest::INFRA_NONEXISTENTREALM]['DATABASE_STATUS']['ID2'] < 0) {
240
            $this->mailQueue[] = Logopath::IDP_EXISTS_BUT_NO_DATABASE;
241
        }
242
243
        if (in_array(AbstractTest::INFRA_IDP_ADMIN_DETERMINED_EVIDENCED, $this->possibleFailureReasons)) {
244
            $this->mailQueue[] = Logopath::IDP_SUSPECTED_PROBLEM_INTERACTIVE_EVIDENCED;
0 ignored issues
show
Bug introduced by
The constant core\diag\Logopath::IDP_...M_INTERACTIVE_EVIDENCED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
245
        }
246
        if (in_array(AbstractTest::INFRA_IDP_ADMIN_DETERMINED_FORCED, $this->possibleFailureReasons)) {
247
            $this->mailQueue[] = Logopath::IDP_SUSPECTED_PROBLEM_INTERACTIVE_FORCED;
248
        }
249
        if(in_array($needle, $haystack))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $haystack seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $needle seems to be never defined.
Loading history...
250
// after collecting all the conditions, find the target entities in all
251
// the mails, and check if they resolve to a known mail address. If they
252
// do not, this triggers more mails about missing contact info.
253
254
        $abstractRecipients = [];
255
        foreach ($this->mailQueue as $mail) {
256
            $abstractRecipients = array_unique(array_merge($this->mailStack[$mail]['to'], $this->mailStack[$mail]['cc'], $this->mailStack[$mail]['bcc'], $this->mailStack[$mail]['reply-to']));
257
        }
258
// who are those guys? Here is significant legwork in terms of DB lookup
259
        $this->concreteRecipients = [];
260
        foreach ($abstractRecipients as $oneRecipient) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $abstractRecipients does not seem to be defined for all execution paths leading up to this point.
Loading history...
261
            switch ($oneRecipient) {
262
                case Logopath::TARGET_EDUROAM_OT:
263
                    $this->concreteRecipients[Logopath::TARGET_EDUROAM_OT] = ["[email protected]"];
264
                    break;
265
                case Logopath::TARGET_ENDUSER:
266
// will be filled when sending, from $this->userEmail
267
// hence the +1 below
268
                    break;
269
                case Logopath::TARGET_IDP:
270
                    // CAT contacts, if existing
271
                    if ($this->additionalFindings['INFRA_NONEXISTENT_REALM']['DATABASE_STATUS']['ID1'] > 0) {
272
                        $profile = \core\ProfileFactory::instantiate($this->additionalFindings['INFRA_NONEXISTENT_REALM']['DATABASE_STATUS']['ID1']);
273
274
                        foreach ($profile->getAttributes("support:email") as $oneMailAddress) {
275
                            // CAT contacts are always public
276
                            $this->concreteRecipients[Logopath::IDP_PUBLIC][] = $oneMailAddress;
0 ignored issues
show
Bug introduced by
The constant core\diag\Logopath::IDP_PUBLIC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
277
                        }
278
                    }
279
                    // DB contacts, if existing
280
                    if ($this->additionalFindings['INFRA_NONEXISTENT_REALM']['DATABASE_STATUS']['ID2'] > 0) {
281
                        $cat = new \core\CAT();
282
                        $info = $cat->getExternalDBEntityDetails($this->additionalFindings['INFRA_NONEXISTENT_REALM']['DATABASE_STATUS']['ID2']);
283
                        foreach ($info['admins'] as $infoElement) {
284
                            if (isset($infoElement['email'])) {
285
                                // until DB Spec 2.0 is out and used, consider all DB contacts as private
286
                                $this->concreteRecipients[Logopath::IDP_PRIVATE][] = $infoElement['email'];
0 ignored issues
show
Bug introduced by
The constant core\diag\Logopath::IDP_PRIVATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
287
                            }
288
                        }
289
                    }
290
                    break;
291
                case Logopath::TARGET_NRO_IDP: // same code for both, fall through
292
                case Logopath::TARGET_NRO_SP:
293
                    $target = ($oneRecipient == Logopath::TARGET_NRO_IDP ? $this->additionalFindings['INFRA_NRO_IdP'] : $this->additionalFindings['INFRA_NRO_SP']);
294
                    $fed = new \core\Federation($target);
295
                    $adminList = $fed->listFederationAdmins();
0 ignored issues
show
Unused Code introduced by
The assignment to $adminList is dead and can be removed.
Loading history...
296
                    // TODO: we only have those who are signed up for CAT currently, and by their ePTID.
297
                    // in touch with OT to get all, so that we can get a list of emails
298
                    break;
299
                case Logopath::TARGET_SP:
300
                    // TODO: needs a DB view on SPs in eduroam DB, in touch with OT
301
                    break;
302
            }
303
        }
304
// now see if we lack pertinent recipient info, and add corresponding
305
// mails to the list
306
        if (count($abstractRecipients) != count($this->concreteRecipients) + 1) {
307
            // there is a discrepancy, do something ...
308
            // we need to add a mail to the next higher hierarchy level as escalation
309
            // but may also have to remove the lower one because we don't know the guy.
310
        }
311
    }
312
313
    /**
314
     * sees if it is useful to ask the user for his contact details or screenshots
315
     * @return boolean
316
     */
317
    public function isEndUserContactUseful()
318
    {
319
        $contactUseful = FALSE;
320
        $this->determineMailsToSend();
321
        foreach ($this->mailQueue as $oneMail) {
322
            if (in_array(Logopath::TARGET_ENDUSER, $this->mailStack[$oneMail]['to']) ||
323
                    in_array(Logopath::TARGET_ENDUSER, $this->mailStack[$oneMail]['cc']) ||
324
                    in_array(Logopath::TARGET_ENDUSER, $this->mailStack[$oneMail]['bcc']) ||
325
                    in_array(Logopath::TARGET_ENDUSER, $this->mailStack[$oneMail]['reply-to'])) {
326
                $contactUseful = TRUE;
327
            }
328
        }
329
        return $contactUseful;
330
    }
331
332
    const CATEGORYBINDING = ['to' => 'addAddress', 'cc' => 'addCC', 'bcc' => 'addBCC', 'reply-to' => 'addReplyTo'];
333
334
    /**
335
     * sends the mails. Only call this after either determineMailsToSend() or
336
     * isEndUserContactUseful(), otherwise it will do nothing.
337
     * 
338
     * @return void
339
     */
340
    public function weNeedToTalk()
341
    {
342
        $this->determineMailsToSend();
343
        foreach ($this->mailQueue as $oneMail) {
344
            $theMail = $this->mailStack[$oneMail];
345
            // if user interaction would have been good, but the user didn't 
346
            // leave his mail address, remove him/her from the list of recipients
347
            foreach (Logopath::CATEGORYBINDING as $index => $functionName) {
348
                if (in_array(Logopath::TARGET_ENDUSER, $theMail[$index]) && $this->userEmail === FALSE) {
349
                    $theMail[$index] = array_diff($theMail[$index], [Logopath::TARGET_ENDUSER]);
350
                }
351
            }
352
353
            $handle = \core\common\OutsideComm::mailHandle();
354
            // let's identify outselves
355
            $handle->FromName = \config\Master::APPEARANCE['productname'] . " Real-Time Diagnostics System";
356
            // add recipients
357
            foreach (Logopath::CATEGORYBINDING as $arrayName => $functionName) {
358
                foreach ($theMail[$arrayName] as $onePrincipal) {
359
                    foreach ($this->concreteRecipients[$onePrincipal] as $oneConcrete) {
360
                        $handle->{$functionName}($oneConcrete);
361
                    }
362
                }
363
            }
364
            // and add what to say
365
            $handle->Subject = $theMail['subject'];
366
            $handle->Body = $theMail['body'];
367
            if (is_string($this->additionalScreenshot)) {
368
                $handle->addStringAttachment($this->additionalScreenshot, "screenshot.png", "base64", "image/png", "attachment");
369
            }
370
            $handle->send();
371
        }
372
    }
373
}