From 7ee5f3175680887fb846caf6b95455fc14f950f4 Mon Sep 17 00:00:00 2001 From: gear4dave Date: Wed, 18 Jan 2017 10:47:50 +0000 Subject: [PATCH 1/3] Clarified any authentication issues, and fixed the locked account check to ensure that the array of matches isset() first --- .gitignore | 10 ---------- composer.lock | 20 ++++++++++++++++++++ src/dawguk/GarminConnect.php | 12 ++++++++---- 3 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index 9d749e5..ef87a42 100755 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,3 @@ -<<<<<<< HEAD .idea/ vendor/ -<<<<<<< HEAD -composer.lock -======= ->>>>>>> 09d8383ef35d34b295a950b43bec75d3891eb6a6 -======= composer.phar -vendor/ -.idea/ -composer.lock ->>>>>>> 4baef86989c5dff3fc2e290230885a72a6142535 diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a2c2b46 --- /dev/null +++ b/composer.lock @@ -0,0 +1,20 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "4ee2561439655435ae23ecd5c83e75a2", + "content-hash": "7b9028a9a319613e1282d67736ab053c", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0" + }, + "platform-dev": [] +} diff --git a/src/dawguk/GarminConnect.php b/src/dawguk/GarminConnect.php index a76ebd0..9b81891 100755 --- a/src/dawguk/GarminConnect.php +++ b/src/dawguk/GarminConnect.php @@ -134,12 +134,16 @@ private function authorize($strUsername, $strPassword) { if (!isset($arrMatches[1])) { - $strMessage = "Looks like the authentication failed"; + $strMessage = "Authentication failed - please check your credentials"; - preg_match("/locked/", $strResponse, $arrLocked); - if ($arrLocked[0]) { - $strMessage = "Looks like your account has been locked. Please access https://connect.garmin.com"; + print_r($strResponse); + + preg_match("/locked/", $strResponse, $arrLocked); + + if (isset($arrLocked[0])) { + $strMessage = "Authentication failed, and it looks like your account has been locked. Please access https://connect.garmin.com to unlock"; } + $this->objConnector->cleanupSession(); throw new AuthenticationException($strMessage); } From 3fc7a1d5d062b97aa84901d729c0dc479e2068d4 Mon Sep 17 00:00:00 2001 From: gear4dave Date: Wed, 18 Jan 2017 10:49:09 +0000 Subject: [PATCH 2/3] Remove debug! (oops) --- src/dawguk/GarminConnect.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/dawguk/GarminConnect.php b/src/dawguk/GarminConnect.php index 9b81891..70d8f53 100755 --- a/src/dawguk/GarminConnect.php +++ b/src/dawguk/GarminConnect.php @@ -134,9 +134,7 @@ private function authorize($strUsername, $strPassword) { if (!isset($arrMatches[1])) { - $strMessage = "Authentication failed - please check your credentials"; - - print_r($strResponse); + $strMessage = "Authentication failed - please check your credentials"; preg_match("/locked/", $strResponse, $arrLocked); From 941c5aedda8b83631513d2a3b3ffc55977e0e7b6 Mon Sep 17 00:00:00 2001 From: gear4dave Date: Wed, 18 Jan 2017 10:50:12 +0000 Subject: [PATCH 3/3] PSR2 reformat on GarminConnect.php --- src/dawguk/GarminConnect.php | 550 ++++++++++++++++++----------------- 1 file changed, 282 insertions(+), 268 deletions(-) diff --git a/src/dawguk/GarminConnect.php b/src/dawguk/GarminConnect.php index 70d8f53..dbbb118 100755 --- a/src/dawguk/GarminConnect.php +++ b/src/dawguk/GarminConnect.php @@ -21,274 +21,288 @@ use dawguk\GarminConnect\exceptions\AuthenticationException; use dawguk\GarminConnect\exceptions\UnexpectedResponseCodeException; -class GarminConnect { - - const DATA_TYPE_TCX = 'tcx'; - const DATA_TYPE_GPX = 'gpx'; - const DATA_TYPE_GOOGLE_EARTH = 'kml'; - - /** - * @var string - */ - private $strUsername = ''; - - /** - * @var string - */ - private $strPassword = ''; - - /** - * @var GarminConnect\Connector|null - */ - private $objConnector = NULL; - - /** - * Performs some essential setup - * - * @param array $arrCredentials - * @throws \Exception - */ - public function __construct(array $arrCredentials = array()) { - - if (!isset($arrCredentials['username'])) { - throw new \Exception("Username credential missing"); - } - - $this->strUsername = $arrCredentials['username']; - unset($arrCredentials['username']); - - $intIdentifier = md5($this->strUsername); - - $this->objConnector = new Connector($intIdentifier); - - // If we can validate the cached auth, we don't need to do anything else - if ($this->checkCookieAuth()) { - return; - } - - if (!isset($arrCredentials['password'])){ - throw new \Exception("Password credential missing"); - } - - $this->strPassword = $arrCredentials['password']; - unset($arrCredentials['password']); - - $this->authorize($this->strUsername, $this->strPassword); - - } - - /** - * Try to read the username from the API - if successful, it means we have a valid cookie, and we don't need to auth - * - * @return bool - */ - private function checkCookieAuth() { - if (strlen(trim($this->getUsername())) == 0) { - $this->objConnector->cleanupSession(); +class GarminConnect +{ + + const DATA_TYPE_TCX = 'tcx'; + const DATA_TYPE_GPX = 'gpx'; + const DATA_TYPE_GOOGLE_EARTH = 'kml'; + + /** + * @var string + */ + private $strUsername = ''; + + /** + * @var string + */ + private $strPassword = ''; + + /** + * @var GarminConnect\Connector|null + */ + private $objConnector = null; + + /** + * Performs some essential setup + * + * @param array $arrCredentials + * @throws \Exception + */ + public function __construct(array $arrCredentials = array()) + { + + if (!isset($arrCredentials['username'])) { + throw new \Exception("Username credential missing"); + } + + $this->strUsername = $arrCredentials['username']; + unset($arrCredentials['username']); + + $intIdentifier = md5($this->strUsername); + + $this->objConnector = new Connector($intIdentifier); + + // If we can validate the cached auth, we don't need to do anything else + if ($this->checkCookieAuth()) { + return; + } + + if (!isset($arrCredentials['password'])) { + throw new \Exception("Password credential missing"); + } + + $this->strPassword = $arrCredentials['password']; + unset($arrCredentials['password']); + + $this->authorize($this->strUsername, $this->strPassword); + + } + + /** + * Try to read the username from the API - if successful, it means we have a valid cookie, and we don't need to auth + * + * @return bool + */ + private function checkCookieAuth() + { + if (strlen(trim($this->getUsername())) == 0) { + $this->objConnector->cleanupSession(); + $this->objConnector->refreshSession(); + return false; + } + return true; + } + + /** + * Because there doesn't appear to be a nice "API" way to authenticate with Garmin Connect, we have to effectively spoof + * a browser session using some pretty high-level scraping techniques. The connector object does all of the HTTP + * work, and is effectively a wrapper for CURL-based session handler (via CURLs in-built cookie storage). + * + * @param string $strUsername + * @param string $strPassword + * @throws AuthenticationException + * @throws UnexpectedResponseCodeException + */ + private function authorize($strUsername, $strPassword) + { + + $arrParams = array( + 'service' => 'https://connect.garmin.com/post-auth/login', + 'clientId' => 'GarminConnect', + 'gauthHost' => 'https://sso.garmin.com/sso', + 'consumeServiceTicket' => 'false' + ); + $strResponse = $this->objConnector->get("https://sso.garmin.com/sso/login", $arrParams); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new AuthenticationException(sprintf("SSO prestart error (code: %d, message: %s)", + $this->objConnector->getLastResponseCode(), $strResponse)); + } + + $arrData = array( + "username" => $strUsername, + "password" => $strPassword, + "_eventId" => "submit", + "embed" => "true", + "displayNameRequired" => "false" + ); + + preg_match("/name=\"lt\"\s+value=\"([^\"]+)\"/", $strResponse, $arrMatches); + if (!isset($arrMatches[1])) { + throw new AuthenticationException("\"lt\" value wasn't found in response"); + } + + $arrData['lt'] = $arrMatches[1]; + + $strResponse = $this->objConnector->post("https://sso.garmin.com/sso/login", $arrParams, $arrData, false); + preg_match("/ticket=([^']+)'/", $strResponse, $arrMatches); + + if (!isset($arrMatches[1])) { + + $strMessage = "Authentication failed - please check your credentials"; + + preg_match("/locked/", $strResponse, $arrLocked); + + if (isset($arrLocked[0])) { + $strMessage = "Authentication failed, and it looks like your account has been locked. Please access https://connect.garmin.com to unlock"; + } + + $this->objConnector->cleanupSession(); + throw new AuthenticationException($strMessage); + } + + $strTicket = $arrMatches[1]; + $arrParams = array( + 'ticket' => $strTicket + ); + + $this->objConnector->post('https://connect.garmin.com/post-auth/login', $arrParams, null, false); + if ($this->objConnector->getLastResponseCode() != 302) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + + // should only exist if the above response WAS a 302 ;) + $strRedirectUrl = $this->objConnector->getCurlInfo()['redirect_url']; + + $this->objConnector->get($strRedirectUrl, null, null, true); + if ($this->objConnector->getLastResponseCode() != 302) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + + // Fires up a fresh CuRL instance, because of our reliance on Cookies requiring "a new page load" as it were ... $this->objConnector->refreshSession(); - return false; - } - return true; - } - - /** - * Because there doesn't appear to be a nice "API" way to authenticate with Garmin Connect, we have to effectively spoof - * a browser session using some pretty high-level scraping techniques. The connector object does all of the HTTP - * work, and is effectively a wrapper for CURL-based session handler (via CURLs in-built cookie storage). - * - * @param string $strUsername - * @param string $strPassword - * @throws AuthenticationException - * @throws UnexpectedResponseCodeException - */ - private function authorize($strUsername, $strPassword) { - - $arrParams = array( - 'service' => 'https://connect.garmin.com/post-auth/login', - 'clientId' => 'GarminConnect', - 'gauthHost' => 'https://sso.garmin.com/sso', - 'consumeServiceTicket' => 'false' - ); - $strResponse = $this->objConnector->get("https://sso.garmin.com/sso/login", $arrParams); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new AuthenticationException(sprintf("SSO prestart error (code: %d, message: %s)", $this->objConnector->getLastResponseCode() , $strResponse)); - } - - $arrData = array( - "username" => $strUsername, - "password" => $strPassword, - "_eventId" => "submit", - "embed" => "true", - "displayNameRequired" => "false" - ); - - preg_match("/name=\"lt\"\s+value=\"([^\"]+)\"/", $strResponse, $arrMatches); - if (!isset($arrMatches[1])) { - throw new AuthenticationException("\"lt\" value wasn't found in response"); - } - - $arrData['lt'] = $arrMatches[1]; - - $strResponse = $this->objConnector->post("https://sso.garmin.com/sso/login", $arrParams, $arrData, FALSE); - preg_match("/ticket=([^']+)'/", $strResponse, $arrMatches); - - if (!isset($arrMatches[1])) { - - $strMessage = "Authentication failed - please check your credentials"; - - preg_match("/locked/", $strResponse, $arrLocked); - - if (isset($arrLocked[0])) { - $strMessage = "Authentication failed, and it looks like your account has been locked. Please access https://connect.garmin.com to unlock"; - } - - $this->objConnector->cleanupSession(); - throw new AuthenticationException($strMessage); - } - - $strTicket = $arrMatches[1]; - $arrParams = array( - 'ticket' => $strTicket - ); - - $this->objConnector->post('https://connect.garmin.com/post-auth/login', $arrParams, null, FALSE); - if ($this->objConnector->getLastResponseCode() != 302) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - - // should only exist if the above response WAS a 302 ;) - $strRedirectUrl = $this->objConnector->getCurlInfo()['redirect_url']; - - $this->objConnector->get($strRedirectUrl, null, null, TRUE); - if ($this->objConnector->getLastResponseCode() != 302) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - - // Fires up a fresh CuRL instance, because of our reliance on Cookies requiring "a new page load" as it were ... - $this->objConnector->refreshSession(); - - } - - /** - * @return mixed - * @throws UnexpectedResponseCodeException - */ - public function getActivityTypes() { - $strResponse = $this->objConnector->get('https://connect.garmin.com/proxy/activity-service-1.2/json/activity_types', null, null, FALSE); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - $objResponse = json_decode($strResponse); - return $objResponse; - } - - /** - * Gets a list of activities - * - * @param integer $intStart - * @param integer $intLimit - * @throws UnexpectedResponseCodeException - * @return mixed - */ - public function getActivityList($intStart = 0, $intLimit = 10) { - - $arrParams = array( - 'start' => $intStart, - 'limit' => $intLimit - ); - - $strResponse = $this->objConnector->get('https://connect.garmin.com/proxy/activity-search-service-1.0/json/activities', $arrParams, TRUE); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - $objResponse = json_decode($strResponse); - return $objResponse; - } - - /** - * Gets the summary information for the activity - * - * @param integer $intActivityID - * @return mixed - * @throws GarminConnect\exceptions\UnexpectedResponseCodeException - */ - public function getActivitySummary($intActivityID) { - $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.3/json/activity/" . $intActivityID); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - $objResponse = json_decode($strResponse); - return $objResponse; - } - - /** - * Gets the detailed information for the activity - * - * @param integer $intActivityID - * @return mixed - * @throws GarminConnect\exceptions\UnexpectedResponseCodeException - */ - public function getActivityDetails($intActivityID) { - $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.3/json/activityDetails/" . $intActivityID); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - $objResponse = json_decode($strResponse); - return $objResponse; - } - - /** - * Gets the extended details for the activity - * - * @param $intActivityID - * @return mixed - */ - public function getExtendedActivityDetails($intActivityID) { - $strResponse = $this->objConnector->get("https://connect.garmin.com/modern/proxy/activity-service/activity/" . $intActivityID . "/details?maxChartSize=1000&maxPolylineSize=1000"); - return json_decode($strResponse); - } - - /** - * Retrieves the data file for the activity - * - * @param string $strType - * @param $intActivityID - * @throws GarminConnect\exceptions\UnexpectedResponseCodeException - * @throws \Exception - * @return mixed - */ - public function getDataFile($strType, $intActivityID) { - - switch ($strType) { - - case self::DATA_TYPE_GPX: - case self::DATA_TYPE_TCX: - case self::DATA_TYPE_GOOGLE_EARTH: - break; - - default: - throw new \Exception("Unsupported data type"); - - } - - $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.2/" . $strType . "/activity/" . $intActivityID . "?full=true"); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - return $strResponse; - } - - /** - * @return mixed - * @throws UnexpectedResponseCodeException - */ - public function getUsername() { - $strResponse = $this->objConnector->get('https://connect.garmin.com/user/username'); - if ($this->objConnector->getLastResponseCode() != 200) { - throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); - } - $objResponse = json_decode($strResponse); - return $objResponse->username; - } + + } + + /** + * @return mixed + * @throws UnexpectedResponseCodeException + */ + public function getActivityTypes() + { + $strResponse = $this->objConnector->get('https://connect.garmin.com/proxy/activity-service-1.2/json/activity_types', + null, null, false); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + $objResponse = json_decode($strResponse); + return $objResponse; + } + + /** + * Gets a list of activities + * + * @param integer $intStart + * @param integer $intLimit + * @throws UnexpectedResponseCodeException + * @return mixed + */ + public function getActivityList($intStart = 0, $intLimit = 10) + { + + $arrParams = array( + 'start' => $intStart, + 'limit' => $intLimit + ); + + $strResponse = $this->objConnector->get('https://connect.garmin.com/proxy/activity-search-service-1.0/json/activities', + $arrParams, true); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + $objResponse = json_decode($strResponse); + return $objResponse; + } + + /** + * Gets the summary information for the activity + * + * @param integer $intActivityID + * @return mixed + * @throws GarminConnect\exceptions\UnexpectedResponseCodeException + */ + public function getActivitySummary($intActivityID) + { + $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.3/json/activity/" . $intActivityID); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + $objResponse = json_decode($strResponse); + return $objResponse; + } + + /** + * Gets the detailed information for the activity + * + * @param integer $intActivityID + * @return mixed + * @throws GarminConnect\exceptions\UnexpectedResponseCodeException + */ + public function getActivityDetails($intActivityID) + { + $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.3/json/activityDetails/" . $intActivityID); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + $objResponse = json_decode($strResponse); + return $objResponse; + } + + /** + * Gets the extended details for the activity + * + * @param $intActivityID + * @return mixed + */ + public function getExtendedActivityDetails($intActivityID) + { + $strResponse = $this->objConnector->get("https://connect.garmin.com/modern/proxy/activity-service/activity/" . $intActivityID . "/details?maxChartSize=1000&maxPolylineSize=1000"); + return json_decode($strResponse); + } + + /** + * Retrieves the data file for the activity + * + * @param string $strType + * @param $intActivityID + * @throws GarminConnect\exceptions\UnexpectedResponseCodeException + * @throws \Exception + * @return mixed + */ + public function getDataFile($strType, $intActivityID) + { + + switch ($strType) { + + case self::DATA_TYPE_GPX: + case self::DATA_TYPE_TCX: + case self::DATA_TYPE_GOOGLE_EARTH: + break; + + default: + throw new \Exception("Unsupported data type"); + + } + + $strResponse = $this->objConnector->get("https://connect.garmin.com/proxy/activity-service-1.2/" . $strType . "/activity/" . $intActivityID . "?full=true"); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + return $strResponse; + } + + /** + * @return mixed + * @throws UnexpectedResponseCodeException + */ + public function getUsername() + { + $strResponse = $this->objConnector->get('https://connect.garmin.com/user/username'); + if ($this->objConnector->getLastResponseCode() != 200) { + throw new UnexpectedResponseCodeException($this->objConnector->getLastResponseCode()); + } + $objResponse = json_decode($strResponse); + return $objResponse->username; + } }