Logopath::determineMailsToSend()   F
last analyzed

Complexity

Conditions 20
Paths 416

Size

Total Lines 71
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
c 0
b 0
f 0
dl 0
loc 71
rs 0.8111
cc 20
nc 416
nop 0

How to fix   Long Method    Complexity   

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
     * storing the end user's email, if he has given it to us
34
     * @var string|boolean
35
     */
36
    private $userEmail;
37
38
    /**
39
     * maybe the user has some additional evidence directly on his device?
40
     * @var string|boolean
41
     */
42
    private $additionalScreenshot;
43
44
    /**
45
     * the list of mails to send
46
     * @var array
47
     */
48
    private $mailStack;
49
50
    /*
51
     * categories of people to contact
52
     */
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
95
    const IDP_EXISTS_BUT_NO_DATABASE = 100;
96
    const IDP_SUSPECTED_PROBLEM_INTERACTIVE_FORCED = 101;
97
    const IDP_SUSPECTED_PROBLEM_INTERACTIVE_EVIDENCED = 102;
98
99
    /*
100
     * types of supplemental string data to send 
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
        parent::__construct();
108
        \core\common\Entity::intoThePotatoes();
109
        $this->userEmail = FALSE;
110
        $this->additionalScreenshot = FALSE;
111
112
        $this->mailQueue = [];
113
        $this->concreteRecipients = [];
114
115
        $this->validatorInstance = new \web\lib\common\InputValidation();
116
117
        $this->possibleFailureReasons = $_SESSION["SUSPECTS"] ?? []; // if we know nothing, don't talk to anyone
118
        $this->additionalFindings = $_SESSION["EVIDENCE"] ?? [];
119
120
        $this->subjectPrefix = _("[eduroam Diagnostics]") . " ";
121
        $this->finalGreeting = "\n"
122
                . _("(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.)")
123
                . "\n"
124
                . _("Yours sincerely,") . "\n"
125
                . "\n"
126
                . _("Ed U. Roam, the eduroam diagnostics algorithm");
127
128
        $this->mailStack = [
129
            Logopath::IDP_EXISTS_BUT_NO_DATABASE => [
130
                "to" => [Logopath::TARGET_NRO_IDP],
131
                "cc" => [Logopath::TARGET_EDUROAM_OT],
132
                "bcc" => [],
133
                "reply-to" => [Logopath::TARGET_EDUROAM_OT],
134
                "subject" => _("[POLICYVIOLATION NATIONAL] IdP with no entry in eduroam database"),
135
                "body" => _("Dear NRO administrator,") . "\n"
136
                . "\n"
137
                . 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"
138
                . "\n"
139
                . _("By not listing IdPs in the eduroam database, you are violating the eduroam policy.") . "\n"
140
                . "\n"
141
                . _("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"
142
                . "\n"
143
                . _("Please stop the policy violation ASAP by listing the IdP which is associated to this realm.")
144
                . "\n",
145
            ],
146
            Logopath::IDP_SUSPECTED_PROBLEM_INTERACTIVE_FORCED => [
147
                "to" => [Logopath::TARGET_IDP],
148
                "cc" => [],
149
                "bcc" => [],
150
                "reply-to" => [Logopath::TARGET_ENDUSER],
151
                "subject" => _("[TECHNICAL PROBLEM] Administrator suspects technical problem with your IdP"),
152
                "body" => _("Dear IdP administrator,") . "\n"
153
                . "\n"
154
                . sprintf(_("an organisation administrator requested diagnostics for realm %s. "), $this->additionalFindings['REALM'])
155
                . "\n"
156
                . _("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"
157
                . "\n",
158
            ],
159
            Logopath::IDP_SUSPECTED_PROBLEM_INTERACTIVE_EVIDENCED => [
160
                "to" => [Logopath::TARGET_IDP],
161
                "cc" => [],
162
                "bcc" => [],
163
                "reply-to" => [Logopath::TARGET_ENDUSER],
164
                "subject" => _("[TECHNICAL PROBLEM] Administrator suspects technical problem with your IdP"),
165
                "body" => _("Dear IdP administrator,") . "\n"
166
                . "\n"
167
                . sprintf(_("an organisation administrator requested diagnostics for realm %s. "), $this->additionalFindings['REALM'])
168
                . "\n"
169
                . _("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"
170
                . "\n",
171
            ],
172
        ];
173
174
        // add exalted human-readable information to main mail body
175
        foreach ($this->mailStack as $oneEntry) {
176
            if (isset($this->additionalFindings['INTERACTIVE_ENDUSER_AUTH_TIMESTAMP'])) {
177
                $oneEntry["body"] .= _("Authentication/Attempt Timestamp of user session:") . " " . $this->additionalFindings['INTERACTIVE_ENDUSER_AUTH_TIMESTAMP'] . "\n";
178
            }
179
            if (isset($this->additionalFindings['INTERACTIVE_ENDUSER_MAC'])) {
180
                $oneEntry["body"] .= _("MAC address of end user in question:") . " " . $this->additionalFindings['INTERACTIVE_ENDUSER_MAC'] . "\n";
181
            }
182
            if (isset($this->additionalFindings['INTERACTIVE_ADDITIONAL_COMMENTS'])) {
183
                $oneEntry["body"] .= _("Additional Comments:") . " " . $this->additionalFindings['INTERACTIVE_ADDITIONAL_COMMENTS'] . "\n";
184
            }
185
        }
186
187
        \core\common\Entity::outOfThePotatoes();
188
    }
189
190
    /**
191
     * if the system asked the user for his email and he's willing to give it to
192
     * us, store it with this function
193
     * 
194
     * @param string $userEmail the end-users email to store
195
     * @return void
196
     */
197
    public function addUserEmail($userEmail) {
198
// returns FALSE if it was not given or bogus, otherwise stores this as mail target
199
        $this->userEmail = $this->validatorInstance->email($userEmail);
200
    }
201
202
    /**
203
     * if the system asked the user for a screenshot and he's willing to give one
204
     * to us, store it with this function
205
     * 
206
     * @param string $binaryData the submitted binary data, to be vetted
207
     * @return void
208
     */
209
    public function addScreenshot($binaryData) {
210
        if ($this->validatorInstance->image($binaryData) === TRUE) {
211
            if (class_exists('\\Gmagick')) { 
212
                $magick = new \Gmagick(); 
213
            } else {
214
                $magick = new \Imagick();
215
            }
216
            $magick->readimageblob($binaryData);
217
            $magick->setimageformat("png");
218
            $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

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