-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjohn-deere-api.py
180 lines (144 loc) · 5.74 KB
/
john-deere-api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import base64
import datetime
import json
import uuid
import logging
from flask import Flask, render_template, request, redirect
import requests
import urllib.parse
app = Flask(__name__)
SERVER_URL='http://localhost:9090'
settings = {
'apiUrl': 'https://sandboxapi.deere.com/platform',
'clientId': '',
'clientSecret': '',
'wellKnown': 'https://signin.johndeere.com/oauth2/aus78tnlaysMraFhC1t7/.well-known/oauth-authorization-server',
'callbackUrl': f"{SERVER_URL}/callback",
'orgConnectionCompletedUrl': SERVER_URL,
'scopes': 'ag1 ag2 ag3 eq1 eq2 org1 org2 files offline_access',
'state': uuid.uuid1(),
'idToken': '',
'accessToken': '',
'refreshToken': '',
'apiResponse': '',
'accessTokenDetails': '',
'exp': ''
}
def populate(data):
settings['clientId'] = data['clientId']
settings['clientSecret'] = data['clientSecret']
settings['wellKnown'] = data['wellKnown']
settings['callbackUrl'] = data['callbackUrl']
settings['scopes'] = data['scopes']
settings['state'] = data['state']
def update_token_info(res):
json_response = res.json()
token = json_response['access_token']
settings['accessToken'] = token
settings['refreshToken'] = json_response['refresh_token']
settings['exp'] = datetime.datetime.now() + datetime.timedelta(seconds=json_response['expires_in'])
(header, payload, sig) = token.split('.')
payload += '=' * (-len(payload) % 4)
settings['accessTokenDetails'] = json.dumps(json.loads(base64.urlsafe_b64decode(payload).decode()), indent=4)
def get_location_from_metadata(endpoint):
response = requests.get(settings['wellKnown'])
return response.json()[endpoint]
def get_basic_auth_header():
return base64.b64encode(bytes(settings['clientId'] + ':' + settings['clientSecret'], 'utf-8'))
def api_get(access_token, resource_url):
headers = {
'authorization': 'Bearer ' + settings['accessToken'],
'Accept': 'application/vnd.deere.axiom.v3+json'
}
return requests.get(resource_url, headers=headers)
def render_error(message):
return render_template('error.html', title='John Deere API with Python', error=message)
def get_oidc_query_string():
query_params = {
"client_id": settings['clientId'],
"response_type": "code",
"scope": urllib.parse.quote(settings['scopes']),
"redirect_uri": settings['callbackUrl'],
"state": settings['state'],
}
params = [f"{key}={value}" for key, value in query_params.items()]
return "&".join(params)
@app.route("/", methods=['POST'])
def start_oidc():
populate(request.form)
redirect_url = f"{get_location_from_metadata('authorization_endpoint')}?{get_oidc_query_string()}"
return redirect(redirect_url, code=302)
def needs_organization_access():
"""Check if a another redirect is needed to finish the connection.
Check to see if the 'connections' rel is present for any organization.
If the rel is present it means the oauth application has not completed its
access to an organization and must redirect the user to the uri provided
in the link.
"""
api_response = api_get(settings['accessToken'], settings['apiUrl']+'/organizations').json()
for org in api_response['values']:
for link in org['links']:
if link['rel'] == 'connections':
connectionsUri = link['uri']
query = urllib.parse.urlencode({'redirect_uri': settings['orgConnectionCompletedUrl']})
return f"{connectionsUri}?{query}"
return None
@app.route("/callback")
def process_callback():
try:
code = request.args['code']
headers = {
'authorization': 'Basic ' + get_basic_auth_header().decode('utf-8'),
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
payload = {
'grant_type': 'authorization_code',
'redirect_uri': settings['callbackUrl'],
'code': code,
'scope': settings['scopes']
}
res = requests.post(get_location_from_metadata('token_endpoint'), data=payload, headers=headers)
update_token_info(res)
organization_access_url = needs_organization_access()
if organization_access_url is not None:
return redirect(organization_access_url, code=302)
return index()
except Exception as e:
logging.exception(e)
return render_error('Error getting token!')
@app.route("/call-api", methods=['POST'])
def call_the_api():
try:
url = request.form['url']
res = api_get(settings['accessToken'], url)
settings['apiResponse'] = json.dumps(res.json(), indent=4)
return index()
except Exception as e:
logging.exception(e)
return render_error('Error calling API!')
@app.route("/refresh-access-token")
def refresh_access_token():
try:
headers = {
'authorization': 'Basic ' + get_basic_auth_header().decode('utf-8'),
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
payload = {
'grant_type': 'refresh_token',
'redirect_uri': settings['callbackUrl'],
'refresh_token': settings['refreshToken'],
'scope': settings['scopes']
}
res = requests.post(get_location_from_metadata('token_endpoint'), data=payload, headers=headers)
update_token_info(res)
return index()
except Exception as e:
logging.exception(e)
return render_error('Error getting refresh token!')
@app.route("/")
def index():
return render_template('main.html', title='John Deere API with Python', settings=settings)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=9090)