From 30506818a5750c9bbfeffa838fabc4251e3836c9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 4 Jun 2018 16:11:03 +0200 Subject: [PATCH] auth working, getting user data a problem --- rowers/models.py | 6 + rowers/polarstuff.py | 168 +++++++++++++++++++++ rowers/templates/email.html | 2 +- rowers/templates/imports.html | 12 +- rowers/urls.py | 1 + rowers/views.py | 68 ++++++++- rowsandall_app/settings.py | 5 + rowsandall_app/urls.py | 1 + static/img/Polar_API_logo_3-horizontal.png | Bin 0 -> 18963 bytes static/img/Polar_connectwith_btn_white.png | Bin 0 -> 18773 bytes 10 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 rowers/polarstuff.py create mode 100644 static/img/Polar_API_logo_3-horizontal.png create mode 100644 static/img/Polar_connectwith_btn_white.png diff --git a/rowers/models.py b/rowers/models.py index 945bbd39..28abaf6f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -655,6 +655,12 @@ class Rower(models.Model): tprefreshtoken = models.CharField(default='',max_length=1000, blank=True,null=True) + polartoken = models.CharField(default='',max_length=1000,blank=True,null=True) + polartokenexpirydate = models.DateTimeField(blank=True,null=True) + polarrefreshtoken = models.CharField(default='',max_length=1000, + blank=True,null=True) + polaruserid = models.IntegerField(default=0) + stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) stravaexportas = models.CharField(default="Rowing", max_length=30, diff --git a/rowers/polarstuff.py b/rowers/polarstuff.py new file mode 100644 index 00000000..65ac068d --- /dev/null +++ b/rowers/polarstuff.py @@ -0,0 +1,168 @@ +# All the functionality needed to connect to Strava + +# 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 +import gzip +import base64 + +# 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 rowers.models import checkworkoutuser +import dataprep +from dataprep import columndict + +import stravalib +from stravalib.exc import ActivityUploadFailed,TimeoutExceeded + +from rowsandall_app.settings import ( + POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, + ) + +# 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 + +# Custom error class - to raise a NoTokenError +class PolarNoTokenError(Exception): + def __init__(self,value): + self.value=value + + def __str__(self): + return repr(self.value) + + +# Exchange access code for long-lived access token +def get_token(code): + + post_data = {"grant_type": "authorization_code", + "code": code, + "redirect_uri": POLAR_REDIRECT_URI, + } + + auth_string = '{id}:{secret}'.format( + id= POLAR_CLIENT_ID, + secret=POLAR_CLIENT_SECRET + ) + + headers = { 'Authorization': 'Basic %s' % base64.b64encode(auth_string) } + + response = requests.post("https://polarremote.com/v2/oauth2/token", + data=post_data, + headers=headers) + + + try: + token_json = response.json() + thetoken = token_json['access_token'] + expires_in = token_json['expires_in'] + user_id = token_json['x_user_id'] + except KeyError: + thetoken = 0 + expires_in = 0 + + return [thetoken,expires_in,user_id] + +# 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": POLAR_CLIENT_ID, + "response_type": "code", + "redirect_uri": POLAR_REDIRECT_URI, + "scope":"write"} + import urllib + url = "https://flow.polar.com/oauth2/authorization" +urllib.urlencode(params) + + return HttpResponseRedirect(url) + +def get_polar_workout_list(user): + r = Rower.objects.get(user=user) + if (r.polartoken == '') or (r.polartoken is None): + s = "Token doesn't exist. Need to authorize" + return custom_exception_handler(401,s) + elif (timezone.now()>r.polartokenexpirydate): + s = "Token expired. Needs to refresh" + return custom_exception_handler(401,s) + else: + authorizationstring = str('Bearer ' + r.polartoken) + headers = {'Authorization':authorizationstring, + 'Accept': 'application/json'} + + url = 'https://polaraccesslink.com/v3/users/{userid}/exercise-transactions'.format( + userid = r.polaruserid + ) + + response = requests.post(url, headers=headers) + + return response + +def get_polar_user_info(user): + r = Rower.objects.get(user=user) + if (r.polartoken == '') or (r.polartoken is None): + s = "Token doesn't exist. Need to authorize" + return custom_exception_handler(401,s) + elif (timezone.now()>r.polartokenexpirydate): + s = "Token expired. Needs to refresh" + return custom_exception_handler(401,s) + else: + authorizationstring = str('Bearer ' + r.polartoken) + headers = { + 'Authorization':authorizationstring, + 'Accept': 'application/json' + } + + params = { + 'user-id': r.polaruserid + } + + url = 'https://polaraccesslink.com/v3/users/{userid}'.format( + userid = r.polaruserid + ) + + + print url + response = requests.get(url, headers=headers) + + return response + diff --git a/rowers/templates/email.html b/rowers/templates/email.html index 02f23929..562f8615 100644 --- a/rowers/templates/email.html +++ b/rowers/templates/email.html @@ -92,7 +92,7 @@ When the site is down, this is the appropriate channel to look for apologies, up 602 00 Brno
Czech Republic
IČ: 070 48 572
- DIČ: CZ 070 48 572
+ DIČ: CZ 070 48 572 (Nejsme plátce DPH)
Datová schránka: 7897syr
Email: info@rowsandall.com
The company is registered in the business register at the diff --git a/rowers/templates/imports.html b/rowers/templates/imports.html index d6e5a6af..17f27503 100644 --- a/rowers/templates/imports.html +++ b/rowers/templates/imports.html @@ -1,9 +1,9 @@ {% extends "base.html" %} - {% block title %}Contact Us{% endblock title %} + {% block title %}Import Workouts{% endblock title %} {% block content %}
-

Import Workouts

+

Import Workouts

@@ -56,7 +56,7 @@
-

Connect

+

Connect

Click one of the below logos to connect to the service of your choice. @@ -81,9 +81,13 @@

connect with RunKeeper

-
+

connect with Under Armour

+
+

connect with Polar

+
diff --git a/rowers/urls.py b/rowers/urls.py index de5d498d..5e29e8b5 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -379,6 +379,7 @@ urlpatterns = [ url(r'^rower/edit/(?P\d+)$',views.rower_edit_view), url(r'^me/edit/(.+.*)/$',views.rower_edit_view), url(r'^me/c2authorize/$',views.rower_c2_authorize), + url(r'^me/polarauthorize/$',views.rower_polar_authorize), url(r'^me/revokeapp/(?P\d+)$',views.rower_revokeapp_view), url(r'^me/stravaauthorize/$',views.rower_strava_authorize), url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize), diff --git a/rowers/views.py b/rowers/views.py index 78f6c367..af6e4264 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -21,6 +21,7 @@ from django.views.decorators.csrf import csrf_exempt from matplotlib.backends.backend_agg import FigureCanvas import gc from pyparsing import ParseException +from uuid import uuid4 from django.shortcuts import render from django.http import ( @@ -93,6 +94,8 @@ from sporttracksstuff import SportTracksNoTokenError,sporttracks_open from tpstuff import TPNoTokenError,tp_open from iso8601 import ParseError import stravastuff +import polarstuff +from polarstuff import PolarNoTokenError from stravastuff import StravaNoTokenError import sporttracksstuff import underarmourstuff @@ -104,6 +107,7 @@ from ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, + POLAR_CLIENT_ID, POLAR_REDIRECT_URI, POLAR_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI, SPORTTRACKS_CLIENT_SECRET, UNDERARMOUR_CLIENT_ID, UNDERARMOUR_REDIRECT_URI, @@ -2358,7 +2362,7 @@ def workout_sporttracks_upload_view(request,id=0): def rower_c2_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()) scope = "user:read,results:write" params = {"client_id": C2_CLIENT_ID, @@ -2373,7 +2377,7 @@ def rower_c2_authorize(request): def rower_strava_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": STRAVA_CLIENT_ID, @@ -2385,12 +2389,30 @@ def rower_strava_authorize(request): return HttpResponseRedirect(url) +# Polar Authorization +@login_required() +def rower_polar_authorize(request): + + state = str(uuid4()) + + params = {"client_id": POLAR_CLIENT_ID, + "response_type": "code", + "redirect_uri": POLAR_REDIRECT_URI, + "state": state, +# "scope":"accesslink.read_all" + } + url = "https://flow.polar.com/oauth2/authorization?" +urllib.urlencode(params) + + return HttpResponseRedirect(url) + + + # Runkeeper authorization @login_required() def rower_runkeeper_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": RUNKEEPER_CLIENT_ID, @@ -2408,7 +2430,7 @@ def rower_runkeeper_authorize(request): def rower_sporttracks_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": SPORTTRACKS_CLIENT_ID, @@ -2426,7 +2448,7 @@ def rower_sporttracks_authorize(request): 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 @@ -2443,7 +2465,7 @@ def rower_underarmour_authorize(request): 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", @@ -2608,6 +2630,40 @@ def test_reverse_view(request): def rower_process_twittercallback(request): return "dummy" +# Process Polar Callback +@login_required() +def rower_process_polarcallback(request): + try: + code = request.GET['code'] + except MultiValueDictKeyError: + try: + message = request.GET['error'] + except MultiValueDictKeyError: + message = "access error" + + messages.error(request,message) + return imports_view(request) + + res = polarstuff.get_token(code) + + access_token = res[0] + expires_in = res[1] + user_id = res[2] + + expirydatetime = timezone.now()+datetime.timedelta(seconds=expires_in) + + r = getrower(request.user) + r.polartoken = access_token + r.polartokenexpirydate = expirydatetime + r.polaruserid = user_id + + r.save() + + successmessage = "Tokens stored. Good to go" + messages.info(request,successmessage) + return imports_view(request) + + # Process Strava Callback @login_required() def rower_process_stravacallback(request): diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index 9bfc9d1e..661ae2ad 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -254,6 +254,11 @@ RUNKEEPER_CLIENT_ID = CFG['runkeeper_client_id'] RUNKEEPER_CLIENT_SECRET = CFG['runkeeper_client_secret'] RUNKEEPER_REDIRECT_URI = CFG['runkeeper_callback'] +# Polar Flow + +POLAR_CLIENT_ID = CFG['polarflow_client_id'] +POLAR_CLIENT_SECRET = CFG['polarflow_client_secret'] +POLAR_REDIRECT_URI = CFG['polarflow_callback'] # Under Armour diff --git a/rowsandall_app/urls.py b/rowsandall_app/urls.py index 64e2a3dc..5fdc8511 100644 --- a/rowsandall_app/urls.py +++ b/rowsandall_app/urls.py @@ -71,6 +71,7 @@ urlpatterns += [ 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'^polarflowcallback',rowersviews.rower_process_polarcallback), url(r'^runkeeper\_callback',rowersviews.rower_process_runkeepercallback), url(r'^tp\_callback',rowersviews.rower_process_tpcallback), url(r'^twitter\_callback',rowersviews.rower_process_twittercallback), diff --git a/static/img/Polar_API_logo_3-horizontal.png b/static/img/Polar_API_logo_3-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..7f6dc054a09823bce4e9da2ec41158d1bb6d80b3 GIT binary patch literal 18963 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