Passed
Pull Request — develop (#52)
by Peter
11:00
created

Tiqr_Message_FCM::_sendFirebase()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 40
c 8
b 0
f 0
dl 0
loc 62
ccs 0
cts 52
cp 0
rs 7.7244
cc 9
nc 8
nop 8
crap 90

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * This file is part of the tiqr project.
4
 *
5
 * The tiqr project aims to provide an open implementation for
6
 * authentication using mobile devices. It was initiated by
7
 * SURFnet and developed by Egeniq.
8
 *
9
 * More information: http://www.tiqr.org
10
 *
11
 * @author Joost van Dijk <[email protected]>
12
 *
13
 * @package tiqr
14
 *
15
 * @license New BSD License - See LICENSE file for details.
16
 *
17
 * @copyright (C) 2010-2024 SURF BV
18
 */
19
use League\Flysystem\Adapter\Local;
20
use League\Flysystem\Filesystem;
21
use Cache\Adapter\Filesystem\FilesystemCachePool;
22
23
/**
24
 * Android Cloud To Device Messaging message.
25
 *
26
 * @author peter
27
 */
28
class Tiqr_Message_FCM extends Tiqr_Message_Abstract
29
{
30
    /**
31
     * Send message.
32
     *
33
     * @throws Tiqr_Message_Exception_SendFailure
34
     * @throws \Google\Exception
35
     */
36
    public function send()
37
    {
38
        $options = $this->getOptions();
39
        $projectId = $options['firebase.projectId'];
40
        $credentialsFile = $options['firebase.credentialsFile'];
41
        $cacheTokens = $options['firebase.cacheTokens'] ?? false;
42
        $tokenCacheDir = $options['firebase.tokenCacheDir'] ?? __DIR__;
43
        $translatedAddress = $this->getAddress();
44
        $alertText = $this->getText();
45
        $url = $this->getCustomProperty('challenge');
46
47
        $this->_sendFirebase($translatedAddress, $alertText, $url, $projectId, $credentialsFile, $cacheTokens, $tokenCacheDir);
48
    }
49
50
    /**
51
     * @throws \Google\Exception
52
     * @throws Tiqr_Message_Exception_SendFailure
53
     */
54
    private function getGoogleAccessToken($credentialsFile, $cacheTokens, $tokenCacheDir )
55
    {
56
        $client = new \Google_Client();
57
        // Try to add a file based cache for accesstokens, if configured
58
        if ($cacheTokens) {
59
            //set up the cache
60
            $filesystemAdapter = new Local($tokenCacheDir);
61
            $filesystem = new Filesystem($filesystemAdapter);
62
            $pool = new FilesystemCachePool($filesystem);
63
64
            //set up a callback to log token refresh
65
            $tokenCallback = function ($cacheKey, $accessToken) {
0 ignored issues
show
Unused Code introduced by
The parameter $accessToken is not used and could be removed. ( Ignorable by Annotation )

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

65
            $tokenCallback = function ($cacheKey, /** @scrutinizer ignore-unused */ $accessToken) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
66
                $this->logger->debug(sprintf('New access token received at cache key %s', $cacheKey));
67
            };
68
            $client->setTokenCallback($tokenCallback);
69
            $client->setCache($pool);
70
        } else {
71
            $this->logger->warning("Cache for oAuth tokens is disabled");
72
        }
73
        $client->setAuthConfig($credentialsFile);
74
        $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
75
        $client->fetchAccessTokenWithAssertion();
76
        $token = $client->getAccessToken();
77
        return $token['access_token'];
78
    }
79
80
    /**
81
     * Send a message to a device using the firebase API key.
82
     *
83
     * @param  $deviceToken     string device ID
84
     * @param  $alert           string alert message
85
     * @param  $challenge       string tiqr challenge url
86
     * @param  $projectId       string the id of the firebase project
87
     * @param  $credentialsFile string The location of the firebase secret json
88
     * @param  $cacheTokens     bool Enable caching the accesstokens for accessing the Google API
89
     * @param  $tokenCacheDir   string Location for storing the accesstoken cache
90
     * @param  $retry           boolean is this a 2nd attempt
91
     * @throws Tiqr_Message_Exception_SendFailure
92
     */
93
    private function _sendFirebase(string $deviceToken, string $alert, string $challenge, string $projectId, string $credentialsFile, bool $cacheTokens, string $tokenCacheDir, bool $retry=false)
94
    {
95
        $apiurl = 'https://fcm.googleapis.com/v1/projects/'.$projectId.'/messages:send';
96
97
        $fields = [
98
            'message' => [
99
                'token' => $deviceToken,
100
                'data' => [
101
                    'challenge' => $challenge,
102
                    'text'      => $alert,
103
                ],
104
                "android" => [
105
                    "ttl" => "300s",
106
                ],
107
            ],
108
        ];
109
110
        try {
111
            $headers = array(
112
                'Authorization: Bearer ' . $this->getGoogleAccessToken($credentialsFile, $cacheTokens, $tokenCacheDir),
113
                'Content-Type: application/json',
114
            );
115
        } catch (\Google\Exception $e) {
116
            throw new Tiqr_Message_Exception_SendFailure(sprintf("Error getting Goosle access token : %s", $e->getMessage()), true);
117
        }
118
119
        $ch = curl_init();
120
        curl_setopt($ch, CURLOPT_URL, $apiurl);
121
        curl_setopt($ch, CURLOPT_POST, true);
122
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
123
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
124
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
125
        $result = curl_exec($ch);
126
        $errors = curl_error($ch);
127
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
128
        $remoteip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
129
        curl_close($ch);
130
131
        if ($result === false) {
132
            throw new Tiqr_Message_Exception_SendFailure("Server unavailable", true);
133
        }
134
135
        if (!empty($errors)) {
136
            throw new Tiqr_Message_Exception_SendFailure("Http error occurred: ". $errors, true);
137
        }
138
139
        // Wait and retry once in case of a 502 Bad Gateway error
140
        if ($statusCode === 502 && !($retry)) {
141
            sleep(2);
142
            $this->_sendFirebase($deviceToken, $alert, $challenge, $projectId, $credentialsFile,  $cacheTokens,  $tokenCacheDir, true);
143
            return;
144
        }
145
146
        if ($statusCode !== 200) {
147
            throw new Tiqr_Message_Exception_SendFailure(sprintf('Invalid status code : %s. Server : %s. Response : "%s".', $statusCode, $remoteip, $result), true);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

147
            throw new Tiqr_Message_Exception_SendFailure(sprintf('Invalid status code : %s. Server : %s. Response : "%s".', $statusCode, $remoteip, /** @scrutinizer ignore-type */ $result), true);
Loading history...
148
        }
149
150
        // handle errors, ignoring registration_id's
151
        $response = json_decode($result, true);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

151
        $response = json_decode(/** @scrutinizer ignore-type */ $result, true);
Loading history...
152
        foreach ($response as $k => $v) {
153
            if ($k=="error") {
154
                throw new Tiqr_Message_Exception_SendFailure(sprintf("Error in FCM response: %s", $result), true);
155
            }
156
        }
157
    }
158
}
159