|
2 | 2 | import requests |
3 | 3 | import json |
4 | 4 | import datetime |
| 5 | +import base64 |
5 | 6 |
|
6 | 7 | try: |
7 | 8 | from urllib.parse import urlencode |
8 | 9 | except ImportError: |
9 | 10 | # Python 2.x |
10 | 11 | from urllib import urlencode |
11 | 12 |
|
12 | | -from requests_oauthlib import OAuth1, OAuth1Session |
| 13 | +from requests_oauthlib import OAuth1, OAuth1Session, OAuth2, OAuth2Session |
13 | 14 |
|
14 | 15 | from fitbit.exceptions import (BadResponse, DeleteError, HTTPBadRequest, |
15 | 16 | HTTPUnauthorized, HTTPForbidden, |
@@ -142,6 +143,133 @@ def fetch_access_token(self, verifier, token=None): |
142 | 143 | return response |
143 | 144 |
|
144 | 145 |
|
| 146 | +class FitbitOauth2Client(object): |
| 147 | + API_ENDPOINT = "https://api.fitbit.com" |
| 148 | + AUTHORIZE_ENDPOINT = "https://www.fitbit.com" |
| 149 | + API_VERSION = 1 |
| 150 | + |
| 151 | + request_token_url = "%s/oauth2/token" % API_ENDPOINT |
| 152 | + authorization_url = "%s/oauth2/authorize" % AUTHORIZE_ENDPOINT |
| 153 | + access_token_url = request_token_url |
| 154 | + refresh_token_url = request_token_url |
| 155 | + |
| 156 | + def __init__(self, client_id , client_secret, |
| 157 | + access_token=None, refresh_token=None, |
| 158 | + resource_owner_key=None, resource_owner_secret=None, user_id=None, |
| 159 | + *args, **kwargs): |
| 160 | + """ |
| 161 | + Create a FitbitOauth2Client object. Specify the first 7 parameters if |
| 162 | + you have them to access user data. Specify just the first 2 parameters |
| 163 | + to start the setup for user authorization (as an example see gather_key_oauth2.py) |
| 164 | + - client_id, client_secret are in the app configuration page |
| 165 | + https://dev.fitbit.com/apps |
| 166 | + - access_token, refresh_token are obtained after the user grants permission |
| 167 | + - resource_owner_key, resource_owner_secret, user_id are user parameters |
| 168 | + """ |
| 169 | + |
| 170 | + self.session = requests.Session() |
| 171 | + self.client_id = client_id |
| 172 | + self.client_secret = client_secret |
| 173 | + self.resource_owner_key = resource_owner_key |
| 174 | + self.resource_owner_secret = resource_owner_secret |
| 175 | + self.header = {'Authorization': 'Basic ' + base64.b64encode(client_id +':' + client_secret)} |
| 176 | + if user_id: |
| 177 | + self.user_id = user_id |
| 178 | + |
| 179 | + #params = {'client_secret': client_secret} |
| 180 | + #if self.resource_owner_key and self.resource_owner_secret: |
| 181 | + #params['resource_owner_key'] = self.resource_owner_key |
| 182 | + #params['resource_owner_secret'] = self.resource_owner_secret |
| 183 | + #self.oauth = OAuth2Session(client_id, **params) |
| 184 | + self.oauth = OAuth2Session(client_id) |
| 185 | + |
| 186 | + def _request(self, method, url, **kwargs): |
| 187 | + """ |
| 188 | + A simple wrapper around requests. |
| 189 | + """ |
| 190 | + return self.session.request(method, url, **kwargs) |
| 191 | + |
| 192 | + def make_request(self, url, data={}, method=None, **kwargs): |
| 193 | + """ |
| 194 | + Builds and makes the OAuth Request, catches errors |
| 195 | +
|
| 196 | + https://wiki.fitbit.com/display/API/API+Response+Format+And+Errors |
| 197 | + """ |
| 198 | + if not method: |
| 199 | + method = 'POST' if data else 'GET' |
| 200 | + auth = OAuth2( |
| 201 | + self.client_id, self.client_secret, self.resource_owner_key, |
| 202 | + self.resource_owner_secret, signature_type='auth_header') |
| 203 | + response = self._request(method, url, data=data, auth=auth, **kwargs) |
| 204 | + |
| 205 | + if response.status_code == 401: |
| 206 | + raise HTTPUnauthorized(response) |
| 207 | + elif response.status_code == 403: |
| 208 | + raise HTTPForbidden(response) |
| 209 | + elif response.status_code == 404: |
| 210 | + raise HTTPNotFound(response) |
| 211 | + elif response.status_code == 409: |
| 212 | + raise HTTPConflict(response) |
| 213 | + elif response.status_code == 429: |
| 214 | + exc = HTTPTooManyRequests(response) |
| 215 | + exc.retry_after_secs = int(response.headers['Retry-After']) |
| 216 | + raise exc |
| 217 | + |
| 218 | + elif response.status_code >= 500: |
| 219 | + raise HTTPServerError(response) |
| 220 | + elif response.status_code >= 400: |
| 221 | + raise HTTPBadRequest(response) |
| 222 | + return response |
| 223 | + |
| 224 | + def authorize_token_url(self, scope=None, redirect_uri=None, **kwargs): |
| 225 | + """Step 1: Return the URL the user needs to go to in order to grant us |
| 226 | + authorization to look at their data. Then redirect the user to that |
| 227 | + URL, open their browser to it, or tell them to copy the URL into their |
| 228 | + browser. |
| 229 | + - scope: pemissions that that are being requested [default ask all] |
| 230 | + - redirect_uri: url to which the reponse will posted |
| 231 | + required only if your app does not have one |
| 232 | + TODO: check if you can give any url and grab code from it |
| 233 | + for more info see https://wiki.fitbit.com/display/API/OAuth+2.0 |
| 234 | + """ |
| 235 | + |
| 236 | + if scope: |
| 237 | + self.oauth.scope = scope |
| 238 | + else: |
| 239 | + #self.oauth.scope = {"heartrate", "location"} |
| 240 | + self.oauth.scope = "activity nutrition heartrate location nutrition profile settings sleep social weight" |
| 241 | + |
| 242 | + if redirect_uri: |
| 243 | + self.oauth.redirect_uri = redirect_uri |
| 244 | + |
| 245 | + return self.oauth.authorization_url(self.authorization_url, **kwargs) |
| 246 | + |
| 247 | + def fetch_access_token(self, verifier, token=None): |
| 248 | + """Step 3: Given the verifier from fitbit, and optionally a token from |
| 249 | + step 1 (not necessary if using the same FitbitOAuthClient object) calls |
| 250 | + fitbit again and returns an access token object. Extract the needed |
| 251 | + information from that and save it to use in future API calls. |
| 252 | + """ |
| 253 | + if token: |
| 254 | + self.resource_owner_key = token.get('oauth_token') |
| 255 | + self.resource_owner_secret = token.get('oauth_token_secret') |
| 256 | + |
| 257 | + self.oauth = OAuth2Session( |
| 258 | + self.client_key, |
| 259 | + client_secret=self.client_secret, |
| 260 | + resource_owner_key=self.resource_owner_key, |
| 261 | + resource_owner_secret=self.resource_owner_secret, |
| 262 | + verifier=verifier) |
| 263 | + response = self.oauth.fetch_access_token(self.access_token_url) |
| 264 | + |
| 265 | + self.user_id = response.get('encoded_user_id') |
| 266 | + self.resource_owner_key = response.get('oauth_token') |
| 267 | + self.resource_owner_secret = response.get('oauth_token_secret') |
| 268 | + return response |
| 269 | + |
| 270 | + |
| 271 | + |
| 272 | + |
145 | 273 | class Fitbit(object): |
146 | 274 | US = 'en_US' |
147 | 275 | METRIC = 'en_UK' |
|
0 commit comments