def test_authorize() -> None: """Test function.""" client_id = "fake_client_id" consumer_secret = "fake_consumer_secret" callback_uri = "http://127.0.0.1:8080" arrow.utcnow = MagicMock(return_value=arrow.get(100000000)) responses.add( method=responses.POST, url="https://account.withings.com/oauth2/token", json=_FETCH_TOKEN_RESPONSE_BODY, status=200, ) auth = WithingsAuth( client_id, consumer_secret, callback_uri=callback_uri, scope=(AuthScope.USER_METRICS, AuthScope.USER_ACTIVITY), ) url = auth.get_authorize_url() assert url.startswith( "https://account.withings.com/oauth2_user/authorize2") assert_url_query_equals( url, { "response_type": "code", "client_id": "fake_client_id", "redirect_uri": "http://127.0.0.1:8080", "scope": "user.metrics,user.activity", }, ) params = dict(parse.parse_qsl(parse.urlsplit(url).query)) assert "scope" in params assert len(params["scope"]) > 0 creds = auth.get_credentials("FAKE_CODE") assert creds == Credentials( access_token="my_access_token", token_expiry=100000011, token_type="Bearer", refresh_token="my_refresh_token", userid=_USERID, client_id=client_id, consumer_secret=consumer_secret, )
def main() -> None: """Run main function.""" parser = argparse.ArgumentParser(description="Process some integers.") parser.add_argument( "--client-id", dest="client_id", help="Client id provided by withings.", required=True, ) parser.add_argument( "--consumer-secret", dest="consumer_secret", help="Consumer secret provided by withings.", required=True, ) parser.add_argument( "--callback-uri", dest="callback_uri", help="Callback URI configured for withings application.", required=True, ) args = parser.parse_args() if path.isfile(CREDENTIALS_FILE): print("Using credentials saved in:", CREDENTIALS_FILE) with open(CREDENTIALS_FILE, "rb") as file_handle: credentials = pickle.load(file_handle) else: print("Attempting to get credentials...") auth = WithingsAuth( client_id=args.client_id, consumer_secret=args.consumer_secret, callback_uri=args.callback_uri, mode="demo", scope=( AuthScope.USER_ACTIVITY, AuthScope.USER_METRICS, AuthScope.USER_INFO, AuthScope.USER_SLEEP_EVENTS, ), ) authorize_url = auth.get_authorize_url() print("Goto this URL in your browser and authorize:", authorize_url) print("Once you are redirected, copy and paste the whole url" "(including code) here.") redirected_uri = input("Provide the entire redirect uri: ") redirected_uri_params = dict( parse.parse_qsl(parse.urlsplit(redirected_uri).query)) auth_code = redirected_uri_params["code"] print("Getting credentials with auth code", auth_code) credentials = auth.get_credentials(auth_code) with open(CREDENTIALS_FILE, "wb") as file_handle: pickle.dump(credentials, file_handle) refresh_cb = MagicMock() api = WithingsApi(credentials, refresh_cb=refresh_cb) print("Getting devices...") assert api.measure_get_meas() is not None print("Refreshing token...") refresh_cb.reset_mock() api.refresh_token() refresh_cb.assert_called_once() print("Getting measures...") assert (api.measure_get_meas(startdate=arrow.utcnow().shift(days=-21), enddate=arrow.utcnow()) is not None) print("Getting activity...") assert (api.measure_get_activity( startdateymd=arrow.utcnow().shift(days=-21), enddateymd=arrow.utcnow()) is not None) print("Getting sleep...") assert (api.sleep_get(startdate=arrow.utcnow().shift(days=-2), enddate=arrow.utcnow()) is not None) print("Getting sleep summary...") assert (api.sleep_get_summary(startdateymd=arrow.utcnow().shift(days=-2), enddateymd=arrow.utcnow()) is not None) print("Getting subscriptions...") assert api.notify_list() is not None print("Successfully finished.")
consumer_secret='1739c24a9971681ea82abc4b10a44882584eeaf6334eaebdbfd54df7396270fc', callback_uri='https://asia-southeast2-eric-han.cloudfunctions.net/nus-withings-bridge', scope=( AuthScope.USER_ACTIVITY, AuthScope.USER_METRICS, AuthScope.USER_INFO, AuthScope.USER_SLEEP_EVENTS, ) ) authorize_url = auth.get_authorize_url() # Have the user goto authorize_url and authorize the app. They will be redirected back to your redirect_uri. print(authorize_url) code = input("Enter Code: ") credentials = auth.get_credentials(code) # Now you are ready to make calls for data. api = WithingsApi(credentials) ll = api.notify_list() print(ll) api.notify_subscribe( callbackurl="https://asia-southeast2-eric-han.cloudfunctions.net/nus-withings-bridge", appli=NotifyAppli.WEIGHT, comment="Sync to NUS Temp Taking HTD", ) '''
class WebInterface(SmartPluginWebIf): def __init__(self, webif_dir, plugin): """ Initialization of instance of class WebInterface :param webif_dir: directory where the webinterface of the plugin resides :param plugin: instance of the plugin :type webif_dir: str :type plugin: object """ self.logger = plugin.logger self.webif_dir = webif_dir self.plugin = plugin self._creds = None self._auth = None self.tplenv = self.init_template_environment() @cherrypy.expose def index(self, reload=None, state=None, code=None, error=None): """ Build index.html for cherrypy Render the template and return the html file to be delivered to the browser :return: contents of the template after beeing rendered """ if self._auth is None: self._auth = WithingsAuth( client_id=self.plugin.get_client_id(), consumer_secret=self.plugin.get_consumer_secret(), callback_uri=self.plugin.get_callback_url(), scope=(AuthScope.USER_ACTIVITY, AuthScope.USER_METRICS, AuthScope.USER_INFO, AuthScope.USER_SLEEP_EVENTS,) ) if not reload and code: self.logger.debug("Got code as callback: {}".format(self.plugin.get_fullname(), code)) credentials = None try: credentials = self._auth.get_credentials(code) except Exception as e: self.logger.error( "An error occurred, perhaps code parameter is invalid or too old? Message: {}".format( str(e))) if credentials is not None: self._creds = credentials self.logger.debug( "New credentials are: access_token {}, token_expiry {}, token_type {}, refresh_token {}". format(self.plugin.get_fullname(), self._creds.access_token, self._creds.token_expiry, self._creds.token_type, self._creds.refresh_token)) self.plugin.get_item('access_token')(self._creds.access_token) self.plugin.get_item('token_expiry')(self._creds.token_expiry) self.plugin.get_item('token_type')(self._creds.token_type) self.plugin.get_item('refresh_token')(self._creds.refresh_token) self.plugin._client = None tmpl = self.tplenv.get_template('index.html') return tmpl.render(plugin_shortname=self.plugin.get_shortname(), plugin_version=self.plugin.get_version(), interface=None, item_count=len(self.plugin.get_items()), plugin_info=self.plugin.get_info(), tabcount=2, callback_url=self.plugin.get_callback_url(), tab1title="Withings Health Items (%s)" % len(self.plugin.get_items()), tab2title="OAuth2 Data", authorize_url=self._auth.get_authorize_url(), p=self.plugin, token_expiry=datetime.datetime.fromtimestamp(self.plugin.get_item( 'token_expiry')(), tz=self.plugin.shtime.tzinfo()), now=self.plugin.shtime.now(), code=code, state=state, reload=reload, language=self.plugin.get_sh().get_defaultlanguage())
class CommWithings: class NotAuthorized(Exception): pass def __init__(self): self.api = None self.scale = Settings.objects.first().default_scale self.auth = WithingsAuth( client_id=settings.WITHINGS_API_CLIENT_ID, consumer_secret=settings.WITHINGS_API_CONSUMER_SECRET, callback_uri=settings.WITHINGS_API_CALLBACK_URI, scope=( AuthScope.USER_ACTIVITY, AuthScope.USER_METRICS, AuthScope.USER_INFO, AuthScope.USER_SLEEP_EVENTS, ), ) def connect(self) -> bool: withings_settings = Settings.objects.first() if withings_settings.token: logger.info(f'Attempting to load credentials from database:') self.api = WithingsApi(self._load_credentials(), refresh_cb=self._save_credentials) try: self.api.user_get_device() orig_access_token = self.api.get_credentials().access_token logger.info('Refreshing token...') self.api.refresh_token() assert orig_access_token \ != self.api.get_credentials().access_token except (MissingTokenError, AuthFailedException): withings_settings.token = None withings_settings.save() self.api = None logger.info('Credentials in file are expired.') raise CommWithings.NotAuthorized else: logger.info('No credentials file found.') raise CommWithings.NotAuthorized return self.api is not None def authorize_request(self): logger.info('Attempting to get credentials...') authorize_url = self.get_authorize_url() subject = 'Withings API authorization request' html_message = ( f'<html><body><div>' f'The application needs to be authorized with Withings API.</div>' f'<div>Go to this <a href="{authorize_url}">link</a>' f' and authorize.</div>' f'</body></html>' ) plain_message = strip_tags(html_message) mail_admins(subject, plain_message, html_message=html_message) def get_authorize_url(self): return self.auth.get_authorize_url() def save_credentials(self, auth_code): self._save_credentials(self.auth.get_credentials(auth_code)) @classmethod def _save_credentials(cls, credentials: CredentialsType) -> None: """Save credentials to a file.""" logger.info(f'Saving credentials in database') cls._log_credentials(credentials) withings_settings = Settings.objects.first() withings_settings.token = pickle.dumps(credentials) withings_settings.save() @classmethod def _load_credentials(cls) -> CredentialsType: """Load credentials from a file.""" logger.info(f'Using credentials from database') credentials = cast(CredentialsType, pickle.loads(Settings.objects.first().token)) cls._log_credentials(credentials) @staticmethod def _log_credentials(credentials: CredentialsType): logger.info( f'Credential properties: ' f' Token: {credentials.access_token}' f' Refresh Token: {credentials.refresh_token}' f' Client ID: {credentials.client_id}' f' Expiry: {credentials.token_expiry}' ) def import_data(self, date_from, date_to): assert self.api is not None assert self.api.user_get_device() is not None meas_result = self.api.measure_get_meas( startdate=date_from, enddate=date_to, # lastupdate=1601478000, lastupdate=None, category=MeasureGetMeasGroupCategory.REAL) api_to_django = { MeasureType.WEIGHT: 'weight', MeasureType.FAT_RATIO: 'fat_pct', } dict_meas = [ { **{ api_to_django[measure.type]: float( measure.value * pow(10, measure.unit)) for measure in grp.measures }, 'measure_date': grp.date.datetime } for grp in query_measure_groups( meas_result, with_measure_type=cast(Tuple, api_to_django.keys()) ) ] logger.debug(f'Values from Withings API: {dict_meas}') db_values = list(Measurement.objects.filter( measure_date__range=[date_from.datetime, date_to.datetime] ).values('measure_date', *api_to_django.values())) logger.debug(f'Values from Django DB: {db_values}') results = {'add': 0, 'del': 0} for i in dict_meas + db_values: if i not in db_values: logger.info(f'Add to DB: {i}') Measurement.objects.create( scale=self.scale, **i ) results['add'] += 1 if i not in dict_meas: logger.info(f'Delete from DB: {i}') Measurement.objects.get(**i).delete() results['del'] += 1 logger.info(f'Import results: {results["add"]} addition,' f' {results["del"]} deletion')