Passed
Pull Request — develop (#52)
by Peter
04:43
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
        // Try to add a file based cache for accesstokens, if configured
56
        if ($cacheTokens) {
57
            //set up the cache
58
            $filesystemAdapter = new Local($tokenCacheDir);
59
            $filesystem = new Filesystem($filesystemAdapter);
60
            $pool = new FilesystemCachePool($filesystem);
61
62
            //set up a callback to log token refresh
63
            $logger=$this->logger;
64
            $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

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

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

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