Issues (5)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/API/Workouts.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace SportTrackerConnector\Endomondo\API;
6
7
use GuzzleHttp\Client;
8
use SportTrackerConnector\Core\Workout\Extension\HR;
9
use SportTrackerConnector\Core\Workout\Track;
10
use SportTrackerConnector\Core\Workout\TrackPoint;
11
use SportTrackerConnector\Endomondo\API\Exception\BadResponseException;
12
13
/**
14
 * Class for working with Endomondo API.
15
 */
16
class Workouts
17
{
18
    private const URL_BASE = 'https://api.mobile.endomondo.com/mobile';
19
    private const URL_WORKOUTS = 'https://api.mobile.endomondo.com/mobile/api/workouts';
20
    private const URL_WORKOUT_GET = 'https://api.mobile.endomondo.com/mobile/api/workout/get';
21
    private const URL_WORKOUT_POST = 'https://api.mobile.endomondo.com/mobile/api/workout/post';
22
    private const URL_TRACK = 'https://api.mobile.endomondo.com/mobile/track';
23
    private const URL_FRIENDS = 'https://api.mobile.endomondo.com/mobile/friends';
24
25
    private const INSTRUCTION_PAUSE = 0;
26
    private const INSTRUCTION_RESUME = 1;
27
    private const INSTRUCTION_START = 2;
28
    private const INSTRUCTION_STOP = 3;
29
    private const INSTRUCTION_NONE = 4;
30
    private const INSTRUCTION_GPS_OFF = 5;
31
    private const INSTRUCTION_LAP = 6;
32
33
    /**
34
     * Endomondo authentication token.
35
     *
36
     * @var string
37
     */
38
    protected $authentication;
39
40
    /**
41
     * @var Client
42
     */
43
    protected $client;
44
45
    /**
46
     * @param Authentication $authentication
47
     * @param Client $client
48
     */
49
    public function __construct(Authentication $authentication, Client $client)
50
    {
51
        $this->authentication = $authentication;
0 ignored issues
show
Documentation Bug introduced by
It seems like $authentication of type object<SportTrackerConne...ndo\API\Authentication> is incompatible with the declared type string of property $authentication.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
52
        $this->client = $client;
53
    }
54
55
    /**
56
     * Get the details of a workout.
57
     *
58
     * Possible fields when getting the workout:
59
     *  device,simple,basic,motivation,interval,hr_zones,weather,polyline_encoded_small,points,lcp_count,tagged_users,pictures,feed
60
     *
61
     * @param string $idWorkout The ID of the workout.
62
     * @return array
63
     * @throws \RuntimeException
64
     */
65
    public function getWorkout($idWorkout): array
66
    {
67
        $response = $this
68
            ->client
69
            ->get(
70
                self::URL_WORKOUT_GET,
71
                array(
72
                    'query' => array(
73
                        'authToken' => $this->authentication->token(),
0 ignored issues
show
The method token cannot be called on $this->authentication (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
74
                        'fields' => 'device,simple,basic,motivation,interval,weather,polyline_encoded_small,points,lcp_count,tagged_users,pictures',
75
                        'workoutId' => $idWorkout
76
                    )
77
                )
78
            );
79
80
81
        $json = \GuzzleHttp\json_decode($response->getBody(), true);
82
        if (isset($json['error'])) {
83
            throw new BadResponseException('Endomondo returned an unexpected error :"' . json_encode($json['error']));
84
        }
85
86
        return $json;
87
    }
88
89
    /**
90
     * Get a list of workouts in a date interval.
91
     *
92
     * @param \DateTimeImmutable $startDate The start date for the workouts.
93
     * @param \DateTimeImmutable $endDate The end date for the workouts.
94
     * @return array
95
     */
96
    public function listWorkouts(\DateTimeImmutable $startDate, \DateTimeImmutable $endDate): array
97
    {
98
        $response = $this
99
            ->client
100
            ->get(
101
                self::URL_WORKOUTS,
102
                array(
103
                    'query' => array(
104
                        'authToken' => $this->authentication->token(),
0 ignored issues
show
The method token cannot be called on $this->authentication (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
105
                        'fields' => 'simple',
106
                        'maxResults' => 100000, // Be lazy and fetch everything in one request.
107
                        'after' => $startDate->format('Y-m-d H:i:s \U\T\C'),
108
                        'before' => $endDate->format('Y-m-d H:i:s \U\T\C')
109
                    )
110
                )
111
            );
112
113
        $json = \GuzzleHttp\json_decode($response->getBody(), true);
114
115
        return $json['data'];
116
    }
117
118
    /**
119
     * Post one workout track to Endomondo.
120
     *
121
     * @param Track $track
122
     * @param string $sport
123
     * @return null|string
124
     */
125
    public function postTrack(Track $track, string $sport)
126
    {
127
        $deviceWorkoutId = $this->generateDeviceWorkoutId();
128
        $duration = $track->duration()->totalSeconds();
129
130
        $workoutId = null;
0 ignored issues
show
$workoutId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
131
        $previousPoint = null;
132
        $distance = 0;
133
        $speed = 0;
134
        // Split in chunks of 100 points like the mobile app.
135
        foreach (array_chunk($track->trackPoints(), 100) as $trackPoints) {
136
            $data = array();
137
            /** @var TrackPoint[] $trackPoints */
138
            foreach ($trackPoints as $trackPoint) {
139
                if ($previousPoint !== null) {
140
                    $distance += $trackPoint->distanceFromPoint($previousPoint);
141
                    $speed = $trackPoint->speed($previousPoint);
142
                }
143
144
                $data[] = $this->flattenTrackPoint($trackPoint, $distance, $speed);
145
146
                $previousPoint = $trackPoint;
147
            }
148
149
            $this->postWorkoutData($deviceWorkoutId, $sport, $duration, $data);
150
        }
151
152
        // End of workout data.
153
        $data = $this->flattenEndWorkoutTrackPoint($track, $speed);
154
        $workoutId = $this->postWorkoutData($deviceWorkoutId, $sport, $duration, array($data));
155
156
        return $workoutId;
157
    }
158
159
    /**
160
     * Post the workout end data.
161
     *
162
     * @param Track $track The track.
163
     * @param float $speed The speed for the last point.
164
     * @return string The workout ID.
165
     */
166
    private function flattenEndWorkoutTrackPoint(Track $track, $speed)
167
    {
168
        $endDateTime = clone $track->endDateTime();
169
        $endDateTime->setTimezone(new \DateTimeZone('UTC'));
170
        $distance = $track->length();
171
        $lastTrackPoint = $track->lastTrackPoint();
172
173
        $totalAscent = $lastTrackPoint->elevation(); // TODO Compute it from the track, this is not correct.
174
175
        return $this->formatEndomondoTrackPoint(
176
            $endDateTime,
177
            self::INSTRUCTION_STOP,
178
            $lastTrackPoint->latitude(),
179
            $lastTrackPoint->longitude(),
180
            $distance,
181
            $speed,
182
            $totalAscent,
183
            $lastTrackPoint->hasExtension(HR::ID()) ? $lastTrackPoint->extension(HR::ID())->value() : ''
184
        );
185
    }
186
187
    /**
188
     * Post workout data chunk.
189
     *
190
     * @param string $deviceWorkoutId The workout ID in progress of the device.
191
     * @param string $sport The sport.
192
     * @param integer $duration The duration in seconds.
193
     * @param array $data The data points to post.
194
     * @return string The workout ID.
195
     * @throws \RuntimeException
196
     */
197
    private function postWorkoutData($deviceWorkoutId, $sport, $duration, array $data): string
198
    {
199
        $body = \GuzzleHttp\Psr7\stream_for(gzencode(implode("\n", $data)));
200
201
        $response = $this
202
            ->client
203
            ->post(
204
                self::URL_TRACK,
205
                array(
206
                    'query' => array(
207
                        'authToken' => $this->authentication->token(),
0 ignored issues
show
The method token cannot be called on $this->authentication (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
208
                        'gzip' => 'true',
209
                        'workoutId' => $deviceWorkoutId,
210
                        'sport' => $sport,
211
                        'duration' => $duration,
212
                        'audioMessage' => 'false',
213
                        'goalType' => 'BASIC',
214
                        'extendedResponse' => 'true'
215
                    ),
216
                    'headers' => array(
217
                        'Content-Type' => 'application/octet-stream'
218
                    ),
219
                    'body' => $body
220
                )
221
            );
222
223
        $responseBody = $response->getBody()->getContents();
224
        $response = parse_ini_string($responseBody);
225
226
        if (array_key_exists('workout.id', $response)) {
227
            return $response['workout.id'];
228
        }
229
230
        throw new \RuntimeException('Unexpected response from Endomondo. Data may be partially uploaded. Response was: ' . $responseBody);
231
    }
232
233
    /**
234
     * Flatten a track point to be posted on Endomondo.
235
     *
236
     * @param TrackPoint $trackPoint The track point to flatten.
237
     * @param float $distance The total distance the point in meters.
238
     * @param float $speed The speed the point in km/h from the previous point.
239
     * @return string
240
     */
241
    private function flattenTrackPoint(TrackPoint $trackPoint, $distance, $speed): string
242
    {
243
        $dateTime = clone $trackPoint->dateTime();
244
        $dateTime->setTimezone(new \DateTimeZone('UTC'));
245
246
        return $this->formatEndomondoTrackPoint(
247
            $dateTime,
248
            self::INSTRUCTION_START,
249
            $trackPoint->latitude(),
250
            $trackPoint->longitude(),
251
            $distance,
252
            $speed,
253
            $trackPoint->elevation(),
254
            $trackPoint->hasExtension(HR::ID()) ? $trackPoint->extension(HR::ID())->value() : ''
255
        );
256
    }
257
258
    /**
259
     * Format a point to send to Endomondo when posting a new workout.
260
     *
261
     * Type:
262
     *  0 - pause
263
     *  1 - running ?
264
     *  2 - running
265
     *  3 - stop
266
     *
267
     * @param \DateTimeImmutable $dateTime
268
     * @param integer $type The post type (0-6). Don't know what they mean.
269
     * @param string $lat The latitude of the point.
270
     * @param string $lon The longitude of the point.
271
     * @param string $distance The distance in meters.
272
     * @param string $speed The speed in km/h.
273
     * @param string $elevation The elevation
274
     * @param string $heartRate The heart rate.
275
     * @param string $cadence The cadence (in rpm).
276
     * @return string
277
     */
278
    private function formatEndomondoTrackPoint(
279
        \DateTimeImmutable $dateTime,
280
        $type,
281
        $lat = null,
282
        $lon = null,
283
        $distance = null,
284
        $speed = null,
285
        $elevation = null,
286
        $heartRate = null,
287
        $cadence = null
288
    ): string {
289
        $dateTime = clone $dateTime;
290
        $dateTime->setTimezone(new \DateTimeZone('UTC'));
291
292
        return sprintf(
293
            '%s;%s;%s;%s;%s;%s;%s;%s;%s;',
294
            $dateTime->format('Y-m-d H:i:s \U\T\C'),
295
            $type,
296
            $lat,
297
            $lon,
298
            $distance / 1000,
299
            $speed,
300
            $elevation,
301
            $heartRate,
302
            $cadence
303
        );
304
    }
305
306
    /**
307
     * Generate a big number of specified length.
308
     *
309
     * @return string
310
     */
311
    private function generateDeviceWorkoutId()
312
    {
313
        $randNumber = '-';
314
315
        for ($i = 0; $i < 19; $i++) {
316
            $randNumber .= random_int(0, 9);
317
        }
318
319
        return $randNumber;
320
    }
321
}
322