Passed
Pull Request — develop (#52)
by Peter
11:22
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 9
Bugs 0 Features 0
Metric Value
eloc 40
c 9
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
     */
35
    public function send()
36
    {
37
        $options = $this->getOptions();
38
        $projectId = $options['firebase.projectId'];
39
        $credentialsFile = $options['firebase.credentialsFile'];
40
        $cacheTokens = $options['firebase.cacheTokens'] ?? false;
41
        $tokenCacheDir = $options['firebase.tokenCacheDir'] ?? __DIR__;
42
        $translatedAddress = $this->getAddress();
43
        $alertText = $this->getText();
44
        $url = $this->getCustomProperty('challenge');
45
46
        $this->_sendFirebase($translatedAddress, $alertText, $url, $projectId, $credentialsFile, $cacheTokens, $tokenCacheDir);
47
    }
48
49
    /**
50
     * @throws Tiqr_Message_Exception_SendFailure
51
     */
52
    private function getGoogleAccessToken($credentialsFile, $cacheTokens, $tokenCacheDir )
53
    {
54
        $client = new Google_Client();
55
        $client->setLogger($this->logger);
56
        // Try to add a file based cache for accesstokens, if configured
57
        if ($cacheTokens) {
58
            //set up the cache
59
            $filesystemAdapter = new Local($tokenCacheDir);
60
            $filesystem = new Filesystem($filesystemAdapter);
61
            $pool = new FilesystemCachePool($filesystem);
62
63
            //set up a callback to log token refresh
64
            $logger=$this->logger;
65
            $tokenCallback = function ($cacheKey, $accessToken) use ($logger) {
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) use ($logger) {

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
                $logger->info(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
        try {
74
            $client->setAuthConfig($credentialsFile);
75
        } catch (\Google\Exception $e) {
76
            throw new Tiqr_Message_Exception_SendFailure(sprintf("Error setting Google credentials for FCM : %s", $e->getMessage()), true, $e);
77
        }
78
        $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
79
        $client->fetchAccessTokenWithAssertion();
80
        $token = $client->getAccessToken();
81
        return $token['access_token'];
82
    }
83
84
    /**
85
     * Send a message to a device using the firebase API key.
86
     *
87
     * @param  $deviceToken     string device ID
88
     * @param  $alert           string alert message
89
     * @param  $challenge       string tiqr challenge url
90
     * @param  $projectId       string the id of the firebase project
91
     * @param  $credentialsFile string The location of the firebase secret json
92
     * @param  $cacheTokens     bool Enable caching the accesstokens for accessing the Google API
93
     * @param  $tokenCacheDir   string Location for storing the accesstoken cache
94
     * @param  $retry           boolean is this a 2nd attempt
95
     * @throws Tiqr_Message_Exception_SendFailure
96
     */
97
    private function _sendFirebase(string $deviceToken, string $alert, string $challenge, string $projectId, string $credentialsFile, bool $cacheTokens, string $tokenCacheDir, bool $retry=false)
98
    {
99
        $apiurl = sprintf('https://fcm.googleapis.com/v1/projects/%s/messages:send',$projectId);
100
101
        $fields = [
102
            'message' => [
103
                'token' => $deviceToken,
104
                'data' => [
105
                    'challenge' => $challenge,
106
                    'text'      => $alert,
107
                ],
108
                "android" => [
109
                    "ttl" => "300s",
110
                ],
111
            ],
112
        ];
113
114
        try {
115
            $headers = array(
116
                'Authorization: Bearer ' . $this->getGoogleAccessToken($credentialsFile, $cacheTokens, $tokenCacheDir),
117
                'Content-Type: application/json',
118
            );
119
        } catch (\Google\Exception $e) {
120
            throw new Tiqr_Message_Exception_SendFailure(sprintf("Error getting Goosle access token : %s", $e->getMessage()), true);
121
        }
122
123
        $ch = curl_init();
124
        curl_setopt($ch, CURLOPT_URL, $apiurl);
125
        curl_setopt($ch, CURLOPT_POST, true);
126
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
127
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
128
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
129
        $result = curl_exec($ch);
130
        $errors = curl_error($ch);
131
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
132
        $remoteip = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
133
        curl_close($ch);
134
135
        if ($result === false) {
136
            throw new Tiqr_Message_Exception_SendFailure("Server unavailable", true);
137
        }
138
139
        if (!empty($errors)) {
140
            throw new Tiqr_Message_Exception_SendFailure("Http error occurred: ". $errors, true);
141
        }
142
143
        // Wait and retry once in case of a 502 Bad Gateway error
144
        if ($statusCode === 502 && !($retry)) {
145
            sleep(2);
146
            $this->_sendFirebase($deviceToken, $alert, $challenge, $projectId, $credentialsFile,  $cacheTokens,  $tokenCacheDir, true);
147
            return;
148
        }
149
150
        if ($statusCode !== 200) {
151
            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

151
            throw new Tiqr_Message_Exception_SendFailure(sprintf('Invalid status code : %s. Server : %s. Response : "%s".', $statusCode, $remoteip, /** @scrutinizer ignore-type */ $result), true);
Loading history...
152
        }
153
154
        // handle errors, ignoring registration_id's
155
        $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

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