Private
Public Access
1
0

token exchange works

This commit is contained in:
Sander Roosendaal
2017-04-13 17:40:39 +02:00
parent d1ac11bfa0
commit 9c5ed87241
6 changed files with 395 additions and 0 deletions

View File

@@ -195,6 +195,10 @@ class Rower(models.Model):
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
underarmourrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
runkeepertoken = models.CharField(default='',max_length=200,
@@ -360,6 +364,7 @@ class Workout(models.Model):
maxhr = models.IntegerField(blank=True,null=True)
uploadedtostrava = models.IntegerField(default=0)
uploadedtosporttracks = models.IntegerField(default=0)
uploadedtounderarmour = models.IntegerField(default=0)
uploadedtorunkeeper = models.IntegerField(default=0)
# empower stuff

323
rowers/underarmourstuff.py Normal file
View File

@@ -0,0 +1,323 @@
# All the functionality needed to connect to Runkeeper
# Python
import oauth2 as oauth
import cgi
import requests
import requests.auth
import json
from django.utils import timezone
from datetime import datetime
import numpy as np
from dateutil import parser
import time
import math
from math import sin,cos,atan2,sqrt
import os,sys
# Django
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect, HttpResponse,JsonResponse
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
# Project
# from .models import Profile
from rowingdata import rowingdata
import pandas as pd
from rowers.models import Rower,Workout
from rowsandall_app.settings import (
C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET,
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_CLIENT_SECRET,
UNDERARMOUR_REDIRECT_URI,UNDERARMOUR_CLIENT_KEY,
)
# Custom error class - to raise a NoTokenError
class UnderArmourNoTokenError(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
# Exponentially weighted moving average
# Used for data smoothing of the jagged data obtained by Strava
# See bitbucket issue 72
def ewmovingaverage(interval,window_size):
# Experimental code using Exponential Weighted moving average
try:
intervaldf = pd.DataFrame({'v':interval})
idf_ewma1 = intervaldf.ewm(span=window_size)
idf_ewma2 = intervaldf[::-1].ewm(span=window_size)
i_ewma1 = idf_ewma1.mean().ix[:,'v']
i_ewma2 = idf_ewma2.mean().ix[:,'v']
interval2 = np.vstack((i_ewma1,i_ewma2[::-1]))
interval2 = np.mean( interval2, axis=0) # average
except ValueError:
interval2 = interval
return interval2
from utils import geo_distance
# Custom exception handler, returns a 401 HTTP message
# with exception details in the json data
def custom_exception_handler(exc,message):
response = {
"errors": [
{
"code": str(exc),
"detail": message,
}
]
}
res = HttpResponse(message)
res.status_code = 401
res.json = json.dumps(response)
return res
# Refresh ST token using refresh token
def do_refresh_token(refreshtoken):
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
post_data = {"grant_type": "refresh_token",
"client_secret": UNDERARMOUR_CLIENT_SECRET,
"client_id":UNDERARMOUR_CLIENT_KEY,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
"Api-Key":UNDERARMOUR_CLIENT_KEY,
'Accept': 'application/json',
'Content-Type': 'application/json'}
url = "https://api.ua.com/v7.1/oauth2/access_token"
response = requests.post(url,
data=json.dumps(post_data),
headers=headers)
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
try:
refresh_token = token_json['refresh_token']
except KeyError:
refresh_token = refreshtoken
return [thetoken,expires_in,refresh_token]
# Exchange access code for long-lived access token
def get_token(code):
client_auth = requests.auth.HTTPBasicAuth(UNDERARMOUR_CLIENT_KEY, UNDERARMOUR_CLIENT_SECRET)
post_data = {
"grant_type": "authorization_code",
"code": code,
"client_secret": UNDERARMOUR_CLIENT_SECRET,
"client_id":UNDERARMOUR_CLIENT_KEY,
}
headers = {
'user-agent': 'sanderroosendaal',
"Api-Key":UNDERARMOUR_CLIENT_KEY,
}
response = requests.post("https://api.ua.com/v7.1/oauth2/access_token/",
data=post_data,
headers=headers)
try:
token_json = response.json()
thetoken = token_json['access_token']
expires_in = token_json['expires_in']
refresh_token = token_json['refresh_token']
except KeyError:
thetoken = 0
return thetoken,expires_in,refresh_token
# Make authorization URL including random string
def make_authorization_url(request):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
params = {"client_id": UNDERARMOUR_CLIENT_KEY,
"response_type": "code",
"redirect_uri": UNDERARMOUR_REDIRECT_URI,
}
import urllib
url = "https://www.mapmyfitness.com/v7.1/oauth2/uacf/authorize/" +urllib.urlencode(params)
return HttpResponseRedirect(url)
# Get list of workouts available on Underarmour
def get_underarmour_workout_list(user):
r = Rower.objects.get(user=user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
s = "Token doesn't exist. Need to authorize"
return custom_exception_handler(401,s)
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.underarmourtoken)
headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.underarmour.com/fitnessActivities"
s = requests.get(url,headers=headers)
return s
# Get workout summary data by Underarmour ID
def get_underarmour_workout(user,underarmourid):
r = Rower.objects.get(user=user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
return custom_exception_handler(401,s)
s = "Token doesn't exist. Need to authorize"
else:
# ready to fetch. Hurray
authorizationstring = str('Bearer ' + r.underarmourtoken)
headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
url = "https://api.underarmour.com/fitnessActivities/"+str(underarmourid)
s = requests.get(url,headers=headers)
return s
# Create Workout Data for upload to Underarmour
def createunderarmourworkoutdata(w):
filename = w.csvfilename
try:
row = rowingdata(filename)
except:
return 0
averagehr = int(row.df[' HRCur (bpm)'].mean())
maxhr = int(row.df[' HRCur (bpm)'].max())
duration = w.duration.hour*3600
duration += w.duration.minute*60
duration += w.duration.second
duration += +1.0e-6*w.duration.microsecond
# adding diff, trying to see if this is valid
#t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)']
t = row.df.ix[:,'TimeStamp (sec)'].values-row.df.ix[0,'TimeStamp (sec)']
t[0] = t[1]
d = row.df.ix[:,'cum_dist'].values
d[0] = d[1]
t = t.astype(int)
d = d.astype(int)
spm = row.df[' Cadence (stokes/min)'].astype(int)
spm[0] = spm[1]
hr = row.df[' HRCur (bpm)'].astype(int)
haslatlon=1
try:
lat = row.df[' latitude'].values
lon = row.df[' longitude'].values
if not lat.std() and not lon.std():
haslatlon = 0
except KeyError:
haslatlon = 0
# path data
if haslatlon:
locdata = []
for e in zip(t,lat,lon):
point = {'timestamp':e[0],
'latitude':e[1],
'longitude':e[2],
'altitude':0,
"type":"gps"}
locdata.append(point)
hrdata = []
for e in zip(t,hr):
point = {'timestamp':e[0],
'heart_rate':e[1]
}
hrdata.append(point)
distancedata = []
for e in zip(t,d):
point = {'timestamp':e[0],
'distance':e[1]
}
distancedata.append(point)
start_time = w.startdatetime.strftime("%a, %d %b %Y %H:%M:%S")
if haslatlon:
data = {
"type": "Rowing",
"start_time": start_time,
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"average_heart_rate": averagehr,
"path": locdata,
"distance": distancedata,
"heart_rate": hrdata,
"post_to_twitter":"false",
"post_to_facebook":"false",
}
else:
data = {
"type": "Rowing",
"start_time": start_time,
"total_distance": int(w.distance),
"duration": duration,
"notes": w.notes,
"avg_heartrate": averagehr,
"distance": distancedata,
"heart_rate": hrdata,
"post_to_twitter":"false",
"post_to_facebook":"false",
}
return data
# Obtain Underarmour Workout ID from the response returned on successful
# upload
def getidfromresponse(response):
uri = response.headers["Location"]
id = uri[len(uri)-9:]
return int(id)
# Get user id, having access token
# Handy for checking if the API access is working
def get_userid(access_token):
authorizationstring = str('Bearer ' + access_token)
headers = {'Authorization': authorizationstring,
'Api-Key': UNDERARMOUR_CLIENT_KEY,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://api.underarmour.com/user"
response = requests.get(url,headers=headers)
me_json = response.json()
try:
res = me_json['userID']
except KeyError:
res = 0
return res

View File

@@ -269,6 +269,7 @@ urlpatterns = [
url(r'^me/revokeapp/(\d+)$',views.rower_revokeapp_view),
url(r'^me/stravaauthorize/$',views.rower_strava_authorize),
url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize),
url(r'^me/underarmourauthorize/$',views.rower_underarmour_authorize),
url(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize),
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),

View File

@@ -56,6 +56,7 @@ from sporttracksstuff import SportTracksNoTokenError
from iso8601 import ParseError
import stravastuff
import sporttracksstuff
import underarmourstuff
import runkeeperstuff
import ownapistuff
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
@@ -64,6 +65,8 @@ from rowsandall_app.settings import (
STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET,
SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI,
SPORTTRACKS_CLIENT_SECRET,
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
)
@@ -878,6 +881,20 @@ def sporttracks_open(user):
return thetoken
# Checks if user has UnderArmour token, renews them if they are expired
def underarmour_open(user):
r = Rower.objects.get(user=user)
if (r.underarmourtoken == '') or (r.underarmourtoken is None):
s = "Token doesn't exist. Need to authorize"
raise UnderarmourNoTokenError("User has no token")
else:
if (timezone.now()>r.underarmourtokenexpirydate):
thetoken = underarmourstuff.rower_underarmour_token_refresh(user)
else:
thetoken = r.underarmourtoken
return thetoken
# Checks if user has SportTracks token, renews them if they are expired
def runkeeper_open(user):
r = Rower.objects.get(user=user)
@@ -1373,6 +1390,24 @@ def rower_sporttracks_authorize(request):
return HttpResponseRedirect(url)
# Underarmour Authorization
@login_required()
def rower_underarmour_authorize(request):
# Generate a random string for the state parameter
# Save it for use later to prevent xsrf attacks
from uuid import uuid4
state = str(uuid4())
redirect_uri = UNDERARMOUR_REDIRECT_URI
redirect_uri = 'http://localhost:8000/underarmour_callback'
url = 'https://api.mapmyfitness.com/v7.1/oauth2/authorize/?' \
'client_id={0}&response_type=code&redirect_uri={1}'.format(
UNDERARMOUR_CLIENT_KEY, redirect_uri
)
return HttpResponseRedirect(url)
# Concept2 token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_c2_token_refresh(request):
@@ -1535,6 +1570,28 @@ def rower_process_sporttrackscallback(request):
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# Process Underarmour callback
@login_required()
def rower_process_underarmourcallback(request):
code = request.GET['code']
res = underarmourstuff.get_token(code)
access_token = res[0]
expires_in = res[1]
refresh_token = res[2]
expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in)
r = Rower.objects.get(user=request.user)
r.underarmourtoken = access_token
r.underarmourtokenexpirydate = expirydatetime
r.underarmourrefreshtoken = refresh_token
r.save()
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# Process Own API callback - for API testing purposes
@login_required()
def rower_process_testcallback(request):

View File

@@ -234,6 +234,14 @@ RUNKEEPER_CLIENT_ID = CFG['runkeeper_client_id']
RUNKEEPER_CLIENT_SECRET = CFG['runkeeper_client_secret']
RUNKEEPER_REDIRECT_URI = "http://rowsandall.com/runkeeper_callback"
# Under Armour
UNDERARMOUR_CLIENT_ID = CFG['underarmour_client_name']
UNDERARMOUR_CLIENT_SECRET = CFG['underarmour_client_secret']
UNDERARMOUR_CLIENT_KEY = CFG['underarmour_client_key']
#UNDERARMOUR_REDIRECT_URI = "https://rowsandall.com/underarmour_callback"
UNDERARMOUR_REDIRECT_URI = "http://localhost:8000/underarmour_callback"
# RQ stuff
RQ_QUEUES = {

View File

@@ -55,6 +55,7 @@ urlpatterns += [
url(r'^call\_back',rowersviews.rower_process_callback),
url(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
url(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
url(r'^underarmour\_callback',rowersviews.rower_process_underarmourcallback),
url(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback),
url(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
url(r'^i18n/', include('django.conf.urls.i18n')),