Private
Public Access
1
0

successfully authorizes, obtained and refreshes token

This commit is contained in:
Sander Roosendaal
2017-04-18 18:07:58 +02:00
parent ee162437b0
commit 256d0af28c
6 changed files with 417 additions and 9 deletions

View File

@@ -199,10 +199,17 @@ class Rower(models.Model):
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
underarmourrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
tptoken = models.CharField(default='',max_length=200,blank=True,null=True)
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
tprefreshtoken = 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,
blank=True,null=True)
# runkeepertokenexpirydate = models.DateTimeField(blank=True,null=True)
# runkeeperrefreshtoken = models.CharField(default='',max_length=200,
# blank=True,null=True)
@@ -365,6 +372,7 @@ class Workout(models.Model):
uploadedtostrava = models.IntegerField(default=0)
uploadedtosporttracks = models.IntegerField(default=0)
uploadedtounderarmour = models.IntegerField(default=0)
uploadedtotp = models.IntegerField(default=0)
uploadedtorunkeeper = models.IntegerField(default=0)
# empower stuff

250
rowers/tpstuff.py Normal file
View File

@@ -0,0 +1,250 @@
# 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
import gzip
from math import sin,cos,atan2,sqrt
import os,sys
import urllib
import base64
from io import BytesIO
# 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,
TP_CLIENT_ID, TP_CLIENT_SECRET,
TP_REDIRECT_URI,TP_CLIENT_KEY,
)
tpapilocation = "https://api.sandbox.trainingpeaks.com"
# Custom error class - to raise a NoTokenError
class TPNoTokenError(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,access_token):
client_auth = requests.auth.HTTPBasicAuth(TP_CLIENT_KEY, TP_CLIENT_SECRET)
post_data = {"grant_type": "refresh_token",
"client_secret": TP_CLIENT_SECRET,
"client_id":TP_CLIENT_KEY,
"refresh_token": refreshtoken,
}
headers = {'user-agent': 'sanderroosendaal',
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'authorization': 'Bearer %s' % access_token}
url = "https://oauth.sandbox.trainingpeaks.com/oauth/token"
response = requests.post(url,
data=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(TP_CLIENT_KEY, TP_CLIENT_SECRET)
post_data = {
"client_id":TP_CLIENT_KEY,
"grant_type": "authorization_code",
"code": code,
"redirect_uri":TP_REDIRECT_URI,
"client_secret": TP_CLIENT_SECRET,
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
response = requests.post("https://oauth.sandbox.trainingpeaks.com/oauth/token",
data=post_data)
print "Reason"
print response.reason
print response.content
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": TP_CLIENT_KEY,
"response_type": "code",
"redirect_uri": TP_REDIRECT_URI,
"scope": "write",
}
url = "https://oauth.sandbox.trainingpeaks.com/oauth/authorize?" +urllib.urlencode(params)
return HttpResponseRedirect(url)
def getidfromresponse(response):
t = json.loads(response.text)
links = t["_links"]
id = links["self"][0]["id"]
return int(id)
def createtpworkoutdata(w):
filename = w.csvfilename
try:
row = rowingdata(filename)
tcxfilename = filename[:-4]+'.tcx'
row.exporttotcx(tcxfilename,notes=w.notes)
# with file(tcxfilename,'rb') as inF:
# s = inF.read()
# with gzip.GzipFile(tcxfilename+'.gz','wb') as outF:
# outF.write(s)
return tcxfilename
except:
tcxfilename = 0
return tcxfilename
def tp_check(access_token):
headers = {
"Content-Type": "application/json",
'Accept': 'application/json',
'authorization': 'Bearer %s' % access_token
}
resp = requests.post(tpapilocation+"/v1/info/version",
headers=headers)
return resp
def uploadactivity(access_token,filename,description=''):
data_gz = BytesIO()
with file(filename,'rb') as inF:
s = inF.read()
with gzip.GzipFile(fileobj=data_gz,mode="w") as gzf:
gzf.write(s)
headers = {
"Content-Type": "application/json",
'Accept': 'application/json',
'authorization': 'Bearer %s' % access_token
}
data = {
"UploadClient": "rowsandall",
"Filename": filename,
"SetWorkoutPublic": True,
"Comment": description,
"Data": base64.b64encode(data_gz.getvalue()).decode("ascii")
}
resp = requests.post(tpapilocation+"/v1/file",
data = json.dumps(data),
headers=headers)
if resp.status_code != 200:
print resp.status_code
print resp.reason
print ""
print headers
print ""
return 0
else:
return resp.json()[0]["Id"]
return 0

View File

@@ -238,6 +238,7 @@ urlpatterns = [
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
url(r'^workout/(\d+)/runkeeperuploadw/$',views.workout_runkeeper_upload_view),
url(r'^workout/(\d+)/underarmouruploadw/$',views.workout_underarmour_upload_view),
url(r'^workout/(\d+)/tpuploadw/$',views.workout_tp_upload_view),
url(r'^multi-compare$',views.multi_compare_view),
url(r'^me/teams/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
url(r'^me/teams/s/(?P<successmessage>\w+.*)$',views.rower_teams_view),
@@ -273,9 +274,11 @@ urlpatterns = [
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/tpauthorize/$',views.rower_tp_authorize),
url(r'^me/runkeeperauthorize/$',views.rower_runkeeper_authorize),
url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh),
url(r'^me/underarmourrefresh/$',views.rower_underarmour_token_refresh),
url(r'^me/tprefresh/$',views.rower_tp_token_refresh),
url(r'^me/c2refresh/$',views.rower_c2_token_refresh),
url(r'^me/favoritecharts/$',views.rower_favoritecharts_view),
url(r'^email/send/$', views.sendmail),

View File

@@ -3,6 +3,7 @@ import timestring
import zipfile
import operator
import warnings
import urllib
from numbers import Number
from django.views.generic.base import TemplateView
from django.db.models import Q
@@ -53,10 +54,12 @@ import c2stuff
from c2stuff import C2NoTokenError
from runkeeperstuff import RunKeeperNoTokenError
from sporttracksstuff import SportTracksNoTokenError
from tpstuff import TPNoTokenError
from iso8601 import ParseError
import stravastuff
import sporttracksstuff
import underarmourstuff
import tpstuff
import runkeeperstuff
import ownapistuff
from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI
@@ -68,6 +71,7 @@ from rowsandall_app.settings import (
UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI,
UNDERARMOUR_CLIENT_SECRET,UNDERARMOUR_CLIENT_KEY,
RUNKEEPER_CLIENT_ID,RUNKEEPER_REDIRECT_URI,RUNKEEPER_CLIENT_SECRET,
TP_CLIENT_ID,TP_REDIRECT_URI,TP_CLIENT_KEY,TP_CLIENT_SECRET,
)
import requests
@@ -1075,6 +1079,20 @@ def underarmour_open(user):
return thetoken
# Checks if user has UnderArmour token, renews them if they are expired
def tp_open(user):
r = Rower.objects.get(user=user)
if (r.tptoken == '') or (r.tptoken is None):
s = "Token doesn't exist. Need to authorize"
raise TPNoTokenError("User has no token")
else:
if (timezone.now()>r.tptokenexpirydate):
thetoken = tpstuff.rower_tp_token_refresh(user)
else:
thetoken = r.tptoken
return thetoken
# Checks if user has SportTracks token, renews them if they are expired
def runkeeper_open(user):
r = Rower.objects.get(user=user)
@@ -1190,6 +1208,73 @@ def workout_csvemail_view(request,id=0):
return response
# Send workout to TP
@login_required()
def workout_tp_upload_view(request,id=0):
message = ""
r = Rower.objects.get(user=request.user)
res = -1
try:
thetoken = tp_open(r.user)
except TPNoTokenError:
return HttpResponseRedirect("/rowers/me/tpauthorize/")
# ready to upload. Hurray
try:
w = Workout.objects.get(id=id)
r = w.user
except Workout.DoesNotExist:
raise Http404("Workout doesn't exist")
if (checkworkoutuser(request.user,w)):
tcxfile = tpstuff.createtpworkoutdata(w)
if tcxfile:
res = tpstuff.uploadactivity(r.tptoken,tcxfile,
description=w.notes)
if res == 0:
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
w.save()
try:
os.remove(tcxfile)
except WindowsError:
pass
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
else: # res != 0
w.uploadedtotp = res
w.save()
os.remove(tcxfile)
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'successmessage':'Uploaded to TP',
})
else: # no tcxfile
message = "Upload to TrainingPeaks failed"
w.uploadedtotp = -1
w.save()
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
else: # not allowed to upload
message = "You are not allowed to export this workout to TP"
url = reverse(workout_export_view,
kwargs = {
'id':str(w.id),
'message':message,
})
return HttpResponseRedirect(url)
# Send workout to Strava
# abundance of error logging here because there were/are some bugs
@login_required()
@@ -1319,7 +1404,6 @@ def workout_c2_upload_view(request,id=0):
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
try:
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
response = requests.post(url,headers=headers,data=json.dumps(data))
@@ -1404,7 +1488,6 @@ def workout_runkeeper_upload_view(request,id=0):
'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json',
'Content-Length':'nnn'}
import urllib
url = "https://api.runkeeper.com/fitnessActivities"
response = requests.post(url,headers=headers,data=json.dumps(data))
@@ -1470,7 +1553,6 @@ def workout_underarmour_upload_view(request,id=0):
'Content-Type': 'application/json',
}
import urllib
url = "https://api.ua.com/v7.1/workout/"
response = requests.post(url,headers=headers,data=json.dumps(data))
@@ -1533,7 +1615,6 @@ def workout_sporttracks_upload_view(request,id=0):
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json"
response = requests.post(url,headers=headers,data=json.dumps(data))
@@ -1575,7 +1656,6 @@ def rower_c2_authorize(request):
params = {"client_id": C2_CLIENT_ID,
"response_type": "code",
"redirect_uri": C2_REDIRECT_URI}
import urllib
url = "http://log.concept2.com/oauth/authorize?"+ urllib.urlencode(params)
url += "&scope="+scope
return HttpResponseRedirect(url)
@@ -1593,7 +1673,6 @@ def rower_strava_authorize(request):
"redirect_uri": STRAVA_REDIRECT_URI,
"scope": "write"}
import urllib
url = "https://www.strava.com/oauth/authorize?"+ urllib.urlencode(params)
return HttpResponseRedirect(url)
@@ -1611,7 +1690,6 @@ def rower_runkeeper_authorize(request):
"state": state,
"redirect_uri": RUNKEEPER_REDIRECT_URI}
import urllib
url = "https://runkeeper.com/apps/authorize?"+ urllib.urlencode(params)
@@ -1630,7 +1708,6 @@ def rower_sporttracks_authorize(request):
"state": state,
"redirect_uri": SPORTTRACKS_REDIRECT_URI}
import urllib
url = "https://api.sporttracks.mobi/oauth2/authorize?"+ urllib.urlencode(params)
@@ -1653,6 +1730,23 @@ def rower_underarmour_authorize(request):
return HttpResponseRedirect(url)
# Underarmour Authorization
@login_required()
def rower_tp_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())
params = {"client_id": TP_CLIENT_KEY,
"response_type": "code",
"redirect_uri": TP_REDIRECT_URI,
"scope": "write",
}
url = "https://oauth.sandbox.trainingpeaks.com/oauth/authorize/?" +urllib.urlencode(params)
return HttpResponseRedirect(url)
# Concept2 token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_c2_token_refresh(request):
@@ -1702,6 +1796,30 @@ def rower_underarmour_token_refresh(request):
return imports_view(request,successmessage=successmessage)
# TrainingPeaks token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_tp_token_refresh(request):
r = Rower.objects.get(user=request.user)
res = tpstuff.do_refresh_token(
r.tprefreshtoken,
r.tptoken
)
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.tptoken = access_token
r.tptokenexpirydate = expirydatetime
r.tprefreshtoken = refresh_token
r.save()
successmessage = "Tokens refreshed. Good to go"
return imports_view(request,successmessage=successmessage)
# SportTracks token refresh. URL for manual refresh. Not visible to users
@login_required()
def rower_sporttracks_token_refresh(request):
@@ -1863,6 +1981,28 @@ def rower_process_underarmourcallback(request):
successmessage = "Tokens stored. Good to go"
return imports_view(request,successmessage=successmessage)
# Process TrainingPeaks callback
@login_required()
def rower_process_tpcallback(request):
code = request.GET['code']
res = tpstuff.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.tptoken = access_token
r.tptokenexpirydate = expirydatetime
r.tprefreshtoken = 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):
@@ -5720,7 +5860,7 @@ def workout_upload_view(request,message="",
headers = {'Authorization': authorizationstring,
'user-agent': 'sanderroosendaal',
'Content-Type': 'application/json'}
import urllib
url = "https://log.concept2.com/api/users/%s/results" % (c2userid)
response = requests.post(url,headers=headers,data=json.dumps(data))