def is_activity(filename): ''' check to see if file exists and that the extension is a valid activity file accepted by GC. ''' if not os.path.isfile(filename): logger.warning("File '{}' does not exist. Skipping...".format( filename)) # noqa return False # Get file extension from name extension = os.path.splitext(filename)[1].lower() logger.debug("File '{}' has extension '{}'".format( filename, extension)) # noqa # Valid file extensions are .tcx, .fit, and .gpx if extension in VALID_GARMIN_FILE_EXTENSIONS: logger.debug("File '{}' extension '{}' is valid.".format( filename, extension)) # noqa return True else: logger.warning( "File '{}' extension '{}' is not valid. Skipping file...". format(filename, extension)) # noqa return False
def load_activity_types(self): """ Fetch valid activity types from Garmin Connect """ # Only fetch once if self.activity_types: return self.activity_types logger.debug('Fetching activity types') resp = requests.get(URL_ACTIVITY_TYPES) if not resp.ok: raise GarminAPIException('Failed to retrieve activity types') # Store as a clean dict, mapping keys and lower case common name types = resp.json() self.activity_types = {t['typeKey']: t for t in types} logger.debug('Fetched {} activity types'.format(len(self.activity_types))) # noqa return self.activity_types
def load_activity_types(self): """ Fetch valid activity types from Garmin Connect """ # Only fetch once if self.activity_types: return self.activity_types logger.debug('Fecthing activity types') resp = requests.get(URL_ACTIVITY_TYPES) if not resp.ok: raise GarminAPIException('Failed to retrieve activity types') # Store as a clean dict, mapping keys and lower case common name types = resp.json()["dictionary"] out = [(t['key'], t['key']) for t in types] out += [(t['display'].lower(), t['key']) for t in types] out = dict(out) self.activity_types = out logger.debug('Fetched {} activity types'.format( len(self.activity_types))) # noqa return self.activity_types
def authenticate(self): """ Authenticate on Garmin API """ logger.info('Try to login on GarminConnect...') logger.debug('Username: {}'.format(self.username)) logger.debug('Password: {}'.format('*'*len(self.password))) api = GarminAPI() try: self.session = api.authenticate(self.username, self.password) logger.debug('Login Successful.') except Exception as e: logger.critical('Login Failure: {}'.format(e)) return False return True
def __init__(self, username=None, password=None): """ ---- GC login credential order of precedence ---- 1) Credentials given on command line 2) Credentials given in config file in current working directory 3) Credentials given in config file in user's home directory Command line overrides all, config in cwd overrides config in home dir """ # Authenticated API session self.session = None configCurrentDir = os.path.abspath( os.path.normpath('./' + CONFIG_FILE) ) configHomeDir = os.path.expanduser( os.path.normpath('~/' + CONFIG_FILE) ) if username and password: logger.debug('Using credentials from command line.') self.username = username self.password = password elif os.path.isfile(configCurrentDir): logger.debug('Using credentials from \'%s\'.' % configCurrentDir) config = ConfigParser() config.read(configCurrentDir) self.username = config.get('Credentials', 'username') self.password = config.get('Credentials', 'password') elif os.path.isfile(configHomeDir): logger.debug('Using credentials from \'%s\'.' % configHomeDir) config = ConfigParser() config.read(configHomeDir) self.username = config.get('Credentials', 'username') self.password = config.get('Credentials', 'password') else: cwd = os.path.abspath(os.path.normpath('./')) homepath = os.path.expanduser(os.path.normpath('~/')) raise Exception("'{}' file does not exist in current directory {}" "or home directory {}. Use login options.".format( CONFIG_FILE, cwd, homepath))
def authenticate(self, username, password): """ That's where the magic happens ! Try to mimick a browser behavior trying to login on Garmin Connect as closely as possible Outputs a Requests session, loaded with precious cookies """ # Use a valid Browser user agent # TODO: use several UA picked randomly session = requests.Session() session.headers.update({ 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/50.0', # noqa }) # Request sso hostname sso_hostname = None resp = session.get(URL_HOSTNAME) if not resp.ok: raise Exception('Invalid SSO first request status code {}'.format( resp.status_code)) # noqa sso_hostname = resp.json().get('host') # Load login page to get login ticket # Full parameters from Firebug, we have to maintain # F**k this shit. # Who needs mandatory urls in a request parameters ! params = { 'clientId': 'GarminConnect', 'connectLegalTerms': 'true', 'consumeServiceTicket': 'false', 'createAccountShown': 'true', 'cssUrl': 'https://connect.garmin.com/gauth-custom-v1.2-min.css', 'displayNameShown': 'false', 'embedWidget': 'false', 'gauthHost': 'https://sso.garmin.com/sso', 'generateExtraServiceTicket': 'true', 'generateNoServiceTicket': 'false', 'generateTwoExtraServiceTickets': 'true', 'globalOptInChecked': 'false', 'globalOptInShown': 'true', 'id': 'gauth-widget', 'initialFocus': 'true', 'locale': 'fr_FR', 'locationPromptShown': 'true', 'mfaRequired': 'false', 'mobile': 'false', 'openCreateAccount': 'false', 'privacyStatementUrl': 'https://www.garmin.com/fr-FR/privacy/connect/', # noqa 'redirectAfterAccountCreationUrl': 'https://connect.garmin.com/modern/', # noqa 'redirectAfterAccountLoginUrl': 'https://connect.garmin.com/modern/', # noqa 'rememberMeChecked': 'false', 'rememberMeShown': 'true', 'rememberMyBrowserChecked': 'false', 'rememberMyBrowserShown': 'false', 'service': 'https://connect.garmin.com/modern/', 'showConnectLegalAge': 'false', 'showPassword': '******', 'showPrivacyPolicy': 'false', 'showTermsOfUse': 'false', 'source': 'https://connect.garmin.com/signin/', 'useCustomHeader': 'false', 'webhost': sso_hostname, } res = session.get(URL_LOGIN, params=params) if res.status_code != 200: raise Exception('No login form') # Lookup for CSRF token csrf = re.search(r'<input type="hidden" name="_csrf" value="(\w+)" />', res.content.decode('utf-8')) # noqa if csrf is None: raise Exception('No CSRF token') csrf_token = csrf.group(1) logger.debug('Found CSRF token {}'.format(csrf_token)) # Login/Password with login ticket data = { 'embed': 'false', 'username': username, 'password': password, '_csrf': csrf_token, } headers = { 'Host': URL_HOST_SSO, 'Referer': URL_SSO_SIGNIN, } res = session.post(URL_LOGIN, params=params, data=data, headers=headers) if not res.ok: raise Exception('Authentification failed.') # Check we have sso guid in cookies if 'GARMIN-SSO-GUID' not in session.cookies: raise Exception('Missing Garmin auth cookie') # Try to find the full post login url in response regex = 'var response_url(\s+)= (\"|\').*?ticket=(?P<ticket>[\w\-]+)(\"|\')' # noqa params = {} matches = re.search(regex, res.text) if not matches: raise Exception('Missing service ticket') params['ticket'] = matches.group('ticket') logger.debug('Found service ticket {}'.format(params['ticket'])) # Second auth step # Needs a service ticket from previous response headers = { 'Host': URL_HOST_CONNECT, } res = session.get(URL_POST_LOGIN, params=params, headers=headers) if res.status_code != 200 and not res.history: raise Exception('Second auth step failed.') # Check login res = session.get(URL_PROFILE) if not res.ok: raise Exception("Login check failed.") garmin_user = res.json() logger.info('Logged in as {}'.format(garmin_user['username'])) return session
def authenticate(self, username, password): """ That's where the magic happens ! Try to mimick a browser behavior trying to login on Garmin Connect as closely as possible Outputs a Requests session, loaded with precious cookies """ # Use a valid Browser user agent # TODO: use several UA picked randomly session = requests.Session() session.headers.update({ 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:48.0) Gecko/20100101 Firefox/50.0', # noqa }) # Request sso hostname sso_hostname = None resp = session.get(URL_HOSTNAME) if not resp.ok: raise Exception('Invalid SSO first request status code {}'.format( resp.status_code)) # noqa sso_hostname = resp.json().get('host') # Load login page to get login ticket # Full parameters from Firebug, we have to maintain # F**k this shit. # Who needs mandatory urls in a request parameters ! params = { 'clientId': 'GarminConnect', 'connectLegalTerms': 'true', 'consumeServiceTicket': 'false', 'createAccountShown': 'true', 'cssUrl': 'https://static.garmincdn.com/com.garmin.connect/ui/css/gauth-custom-v1.2-min.css', # noqa 'displayNameShown': 'false', 'embedWidget': 'false', 'gauthHost': 'https://sso.garmin.com/sso', 'generateExtraServiceTicket': 'false', 'globalOptInChecked': 'false', 'globalOptInShown': 'false', 'id': 'gauth-widget', 'initialFocus': 'true', 'locale': 'fr', 'mobile': 'false', 'openCreateAccount': 'false', 'privacyStatementUrl': '//connect.garmin.com/fr-FR/privacy/', 'redirectAfterAccountCreationUrl': 'https://connect.garmin.com/modern/', # noqa 'redirectAfterAccountLoginUrl': 'https://connect.garmin.com/modern/', # noqa 'rememberMeChecked': 'false', 'rememberMeShown': 'true', 'service': 'https://connect.garmin.com/modern/', 'source': 'https://connect.garmin.com/fr-FR/signin', 'usernameShown': 'false', 'webhost': sso_hostname } res = session.get(URL_LOGIN, params=params) if res.status_code != 200: raise Exception('No login form') # Login/Password with login ticket data = { 'embed': 'false', 'username': username, 'password': password, } headers = { 'Host': URL_HOST_SSO, } res = session.post(URL_LOGIN, params=params, data=data, headers=headers) if res.status_code != 200: raise Exception('Authentification failed.') # Check we have sso guid in cookies if 'GARMIN-SSO-GUID' not in session.cookies: raise Exception('Missing Garmin auth cookie') # Try to find the full post login url in response regex = 'var response_url(\s+)= (\"|\').*?ticket=(?P<ticket>[\w\-]+)(\"|\')' # noqa params = {} matches = re.search(regex, res.text) if not matches: raise Exception('Missing service ticket') params['ticket'] = matches.group('ticket') logger.debug('Found service ticket {}'.format(params['ticket'])) # Second auth step # Needs a service ticket from previous response headers = { 'Host': URL_HOST_CONNECT, } res = session.get(URL_POST_LOGIN, params=params, headers=headers) if res.status_code != 200 and not res.history: raise Exception('Second auth step failed.') # Get Jsessionid res = session.get(URL_SESSION) res.raise_for_status() if 'JSESSIONID' not in session.cookies: raise Exception('Missing jsession auth cookie') # Check login res = session.get(URL_CHECK_LOGIN) garmin_user = res.json() username = garmin_user.get('username') if not res.ok or not username: raise Exception("Login check failed.") logger.info('Logged in as {}'.format(username)) return session