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

Tiqr_Message_FCM::getGoogleAccessToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 10
c 2
b 0
f 0
dl 0
loc 13
ccs 0
cts 11
cp 0
rs 9.9332
cc 1
nc 1
nop 1
crap 2
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
 * @author peter
26
 */
27
class Tiqr_Message_FCM extends Tiqr_Message_Abstract
28
{
29
    /**
30
     * Send message.
31
     *
32
     * @throws Tiqr_Message_Exception_AuthFailure
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
42
        $translatedAddress = $this->getAddress();
43
        $alertText = $this->getText();
44
        $url = $this->getCustomProperty('challenge');
45
46
        $this->_sendFirebase($translatedAddress, $alertText, $url, $projectId, $credentialsFile);
47
    }
48
49
    /**
50
     * @throws \Google\Exception
51
     */
52
    private function getGoogleAccessToken($credentialsFile){
53
        $filesystemAdapter = new Local(__DIR__.'/');
54
        $filesystem        = new Filesystem($filesystemAdapter);
55
56
        $pool = new FilesystemCachePool($filesystem);
57
58
        $client = new \Google_Client();
59
        $client->setCache($pool);
60
        $client->setAuthConfig($credentialsFile);
61
        $client->addScope('https://www.googleapis.com/auth/firebase.messaging');
62
        $client->fetchAccessTokenWithAssertion();
63
        $token = $client->getAccessToken();
64
        return $token['access_token'];
65
    }
66
67
    /**
68
     * Send a message to a device using the firebase API key.
69
     *
70
     * @param $deviceToken string device ID
71
     * @param $alert string alert message
72
     * @param $challenge string tiqr challenge url
73
     * @param $projectId string the id of the firebase project
74
     * @param $credentialsFile string The location of the firebase secret json
75
     * @param $retry boolean is this a 2nd attempt
76
     * @throws Tiqr_Message_Exception_SendFailure
77
     * @throws \Google\Exception
78
     */
79
    private function _sendFirebase(string $deviceToken, string $alert, string $challenge, string $projectId, string $credentialsFile, bool $retry=false)
80
    {
81
        $apiurl = 'https://fcm.googleapis.com/v1/projects/'.$projectId.'/messages:send';
82
83
        $fields = [
84
            'message' => [
85
                'token' => $deviceToken,
86
                'data' => [
87
                    'challenge' => $challenge,
88
                    'text'      => $alert,
89
                ],
90
                "android" => [
91
                    "ttl" => "300s",
92
                ],
93
            ],
94
        ];
95
96
        $headers = array(
97
            'Authorization: Bearer ' . $this->getGoogleAccessToken($credentialsFile),
98
            'Content-Type: application/json',
99
        );
100
101
        $ch = curl_init();
102
        curl_setopt($ch, CURLOPT_URL, $apiurl);
103
        curl_setopt($ch, CURLOPT_POST, true);
104
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
105
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
106
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
107
        $result = curl_exec($ch);
108
        $errors = curl_error($ch);
109
        $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
110
        $remoteip = curl_getinfo($ch,CURLINFO_PRIMARY_IP);
111
        curl_close($ch);
112
113
        if ($result === false) {
114
            throw new Tiqr_Message_Exception_SendFailure("Server unavailable", true);
115
        }
116
117
        if (!empty($errors)) {
118
            throw new Tiqr_Message_Exception_SendFailure("Http error occurred: ". $errors, true);
119
        }
120
121
        // Wait and retry once in case of a 502 Bad Gateway error
122
        if ($statusCode === 502 && !($retry)) {
123
          sleep(2);
124
          $this->_sendFirebase($deviceToken, $alert, $challenge, $projectId, $credentialsFile, true);
125
          return;
126
        }
127
128
        if ($statusCode !== 200) {
129
            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

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

133
        $response = json_decode(/** @scrutinizer ignore-type */ $result, true);
Loading history...
134
        foreach ($response as $k => $v) {
135
            if ($k=="error") {
136
                throw new Tiqr_Message_Exception_SendFailure(sprintf("Error in FCM response: %s", $result), true);
137
            }
138
        }
139
    }
140
}
141