Skip to content

Commit d7b3979

Browse files
committed
Update the app to Python 3
1 parent ef89cf9 commit d7b3979

18 files changed

+270
-193
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var/
3333
.installed.cfg
3434
*.egg
3535

36+
3637
# PyInstaller
3738
# Usually these files are written by a python script from a template
3839
# before PyInstaller builds the exe, so as to inject date/other infos into it.
@@ -91,6 +92,8 @@ celerybeat-schedule
9192
.venv/
9293
venv/
9394
ENV/
95+
bin/
96+
pyvenv.cfg
9497

9598
# Spyder project settings
9699
.spyderproject

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# For further information, please contact Curity AB.
1010
#
1111

12-
FROM python:2.7
12+
FROM python:3.9
1313
MAINTAINER Curity AB
1414

1515
ADD requirements.txt /usr/src/

Pipfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ name = "pypi"
66
[dev-packages]
77

88
[packages]
9-
pyjwkest = "==1.3.1"
10-
Flask = "==0.12.3"
9+
pyjwkest = "1.4.2"
10+
Flask = "1.1.2"
1111

1212
[requires]
13-
python_version = "2.7"
13+
python_version = "3.9"

Pipfile.lock

Lines changed: 135 additions & 72 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Browse to https://localhost:5443 to see the app.
1616

1717
## Dependencies
1818

19-
**python 2.x** (tested with python 2.7.10)
19+
**python 3.x** (tested with python 3.9.1)
2020

2121
**OpenSSL 1.0** to be able to do modern TLS versions. Python together with 0.9.x has a bug that makes it impossible to select protocol in the handshake, so it cannot connect to servers that have disabled SSLv2.
2222

@@ -53,7 +53,7 @@ Name | Type | Description
5353
To run the example in a Docker container, build an image and run a container like this.:
5454

5555
```bash
56-
$ docker build -t curityio/openid-python-example
56+
$ docker build -t curityio/openid-python-example .
5757
$ docker run -ti curityio/openid-python-example
5858

5959
```

app.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
##########################################################################
1818

1919
import sys
20-
import urllib2
21-
from urlparse import urlparse
20+
from urllib.request import Request, urlopen
21+
from urllib.error import URLError, HTTPError
2222

2323
from flask import redirect, request, render_template, session, abort, Flask
2424
from jwkest import BadSignature
@@ -63,7 +63,7 @@ def index():
6363
if 'redirect_uri' not in _config:
6464
_config['redirect_uri'] = _config['base_url'].rstrip('/') + '/callback'
6565

66-
if isinstance(user, (str, unicode)):
66+
if isinstance(user, (bytes, str)):
6767
# User is a string! Probably a bunch of HTML from a previous error. Just bail and hope for the best.
6868
return user
6969

@@ -131,7 +131,7 @@ def logout():
131131
del _session_store[session['session_id']]
132132
session.clear()
133133

134-
print "Logging out at ", _config['end_session_endpoint']
134+
print("Logging out at ", _config['end_session_endpoint'])
135135
logout_request = _config['end_session_endpoint'] + '?client_id=' + _config['client_id'] + '&post_logout_redirect_uri=' + _config['base_url']
136136
return redirect(logout_request)
137137

@@ -196,7 +196,7 @@ def revoke():
196196

197197
try:
198198
_client.revoke(token, token_type_hint)
199-
except urllib2.URLError as e:
199+
except URLError as e:
200200
return create_error(error_message, e)
201201

202202
return redirect_with_baseurl('/')
@@ -247,12 +247,12 @@ def call_api():
247247
access_token = user.access_token
248248
else:
249249
user.api_response = None
250-
print 'No access token in session'
250+
print('No access token in session')
251251

252252
return redirect_with_baseurl("/")
253253

254254
try:
255-
req = urllib2.Request(_config['api_endpoint'])
255+
req = Request(_config['api_endpoint'])
256256
req.add_header('User-Agent', 'CurityExample/1.0')
257257
req.add_header("Authorization", "Bearer %s" % access_token)
258258
req.add_header("Accept", 'application/json')
@@ -261,16 +261,16 @@ def call_api():
261261
req.add_header('Ocp-Apim-Subscription-Key', _config['subscription_key'])
262262
req.add_header('Ocp-Apim-Trace', 'true')
263263

264-
response = urllib2.urlopen(req, context=tools.get_ssl_context(_config))
264+
response = urlopen(req, context=tools.get_ssl_context(_config))
265265
user.api_response = {'code': response.code, 'data': response.read()}
266-
except urllib2.HTTPError as e:
266+
except HTTPError as e:
267267
user.api_response = {'code': e.code, 'data': e.read()}
268268
except Exception as e:
269269
message = e.message if len(e.message) > 0 else "unknown error"
270270
user.api_response = {"code": "unknown error", "data": message}
271271
else:
272272
user.api_response = None
273-
print 'No API endpoint configured'
273+
print('No API endpoint configured')
274274

275275
return redirect_with_baseurl('/')
276276

@@ -311,7 +311,7 @@ def oauth_callback():
311311
# This is the callback for a hybrid or implicit flow
312312
return render_template('index.html')
313313

314-
if 'state' not in session or session['state'] != request.args['state']:
314+
if 'state' not in session or session['state'].decode() != request.args['state']:
315315
return create_error('Missing or invalid state')
316316

317317
if "code_verifier" not in session:
@@ -332,7 +332,7 @@ def callback(params):
332332
session.pop('state', None)
333333

334334
try:
335-
token_data = _client.get_token(params['code'], session["code_verifier"])
335+
token_data = _client.get_token(params['code'], session["code_verifier"].decode())
336336
except Exception as e:
337337
return create_error('Could not fetch token(s)', e)
338338

@@ -377,8 +377,8 @@ def create_error(message, exception=None):
377377
:param message:
378378
:return: redirects to index.html with the error message
379379
"""
380-
print 'Caught error!'
381-
print message, exception
380+
print('Caught error!')
381+
print(message, exception)
382382
if _app:
383383
user = UserSession()
384384
if 'session_id' in session:
@@ -396,7 +396,7 @@ def load_config():
396396
:return:
397397
"""
398398
if len(sys.argv) > 1:
399-
print "Using an alternative config file: %s" % sys.argv[1]
399+
print("Using an alternative config file: %s" % sys.argv[1])
400400
filename = sys.argv[1]
401401
else:
402402
filename = 'settings.json'
@@ -419,7 +419,7 @@ def redirect_with_baseurl(path):
419419
if 'jwks_uri' in _config:
420420
_jwt_validator = JwtValidator(_config)
421421
else:
422-
print 'Found no url to JWK set, will not be able to validate JWT signature.'
422+
print('Found no url to JWK set, will not be able to validate JWT signature.')
423423
_jwt_validator = None
424424

425425
# create a session store
@@ -442,7 +442,7 @@ def redirect_with_baseurl(path):
442442
debug = _config['debug'] = 'debug' in _config and _config['debug']
443443

444444
if debug:
445-
print 'Running conf:'
445+
print('Running conf:')
446446
print_json(_config)
447447

448448
if _disable_https:

client.py

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import json
1818
import os
1919
import time
20-
import urllib
21-
import urllib2
20+
from urllib.parse import urlencode
21+
from urllib.request import urlopen
22+
from urllib.error import URLError
23+
from urllib.request import Request
2224

2325
from jwkest.jwk import KEYS
2426
from jwkest.jws import JWS
@@ -42,7 +44,7 @@ class Client:
4244
def __init__(self, config):
4345
self.config = config
4446

45-
print 'Getting ssl context for oauth server'
47+
print('Getting ssl context for oauth server')
4648
self.ctx = tools.get_ssl_context(self.config)
4749
self.__init_config()
4850
self.client_data = None
@@ -51,14 +53,14 @@ def __init_config(self):
5153

5254
if 'issuer' in self.config:
5355
meta_data_url = self.config['issuer'] + '/.well-known/openid-configuration'
54-
print 'Fetching config from: %s' % meta_data_url
55-
meta_data = urllib2.urlopen(meta_data_url, context=self.ctx)
56+
print('Fetching config from: %s' % meta_data_url)
57+
meta_data = urlopen(meta_data_url, context=self.ctx)
5658
if meta_data:
5759
self.config.update(json.load(meta_data))
5860
else:
59-
print 'Unexpected response on discovery document: %s' % meta_data
61+
print('Unexpected response on discovery document: %s' % meta_data)
6062
else:
61-
print 'Found no issuer in config, can not perform discovery. All endpoint config needs to be set manually'
63+
print('Found no issuer in config, can not perform discovery. All endpoint config needs to be set manually')
6264

6365
# Mandatory settings
6466
if 'authorization_endpoint' not in self.config:
@@ -68,20 +70,20 @@ def __init_config(self):
6870

6971
self.read_credentials_from_file()
7072
if 'client_id' not in self.config:
71-
print 'Client is not registered.'
73+
print('Client is not registered.')
7274

7375
if 'scope' not in self.config:
7476
self.config['scope'] = 'openid'
7577

7678
def read_credentials_from_file(self):
7779
if not os.path.isfile(REGISTERED_CLIENT_FILENAME):
78-
print 'Client is not dynamically registered'
80+
print('Client is not dynamically registered')
7981
return
8082

8183
try:
8284
registered_client = json.loads(open(REGISTERED_CLIENT_FILENAME).read())
8385
except Exception as e:
84-
print 'Could not read credentials from file', e
86+
print('Could not read credentials from file', e)
8587
return
8688
self.config['client_id'] = registered_client['client_id']
8789
self.config['client_secret'] = registered_client['client_secret']
@@ -94,8 +96,8 @@ def register(self):
9496
:raises: raises error when http call fails
9597
"""
9698
if 'registration_endpoint' not in self.config:
97-
print 'Authorization server does not support Dynamic Client Registration. Please configure client ' \
98-
'credentials manually '
99+
print('Authorization server does not support Dynamic Client Registration. Please configure client ' \
100+
'credentials manually ')
99101
return
100102

101103
if 'client_id' in self.config:
@@ -108,7 +110,7 @@ def register(self):
108110
dcr_access_token = self.get_registration_token()
109111

110112
if 'template_client' in self.config:
111-
print 'Registering client using template_client: %s' % self.config['template_client']
113+
print('Registering client using template_client: %s' % self.config['template_client'])
112114
data = {
113115
'software_id': self.config['template_client']
114116
}
@@ -120,7 +122,7 @@ def register(self):
120122
}
121123

122124
if self.config['debug']:
123-
print 'Registering client with data:\n %s' % json.dumps(data)
125+
print('Registering client with data:\n %s' % json.dumps(data))
124126

125127
register_response = self.__urlopen(self.config['registration_endpoint'], data=json.dumps(data),
126128
context=self.ctx, token=dcr_access_token)
@@ -153,7 +155,7 @@ def revoke(self, token, token_type_hint="access_token"):
153155
:raises: raises error when http call fails
154156
"""
155157
if 'revocation_endpoint' not in self.config:
156-
print 'No revocation endpoint set'
158+
print('No revocation endpoint set')
157159
return
158160

159161
data = {
@@ -163,7 +165,7 @@ def revoke(self, token, token_type_hint="access_token"):
163165
'client_secret': self.config['client_secret']
164166
}
165167

166-
self.__urlopen(self.config['revocation_endpoint'], urllib.urlencode(data), context=self.ctx)
168+
self.__urlopen(self.config['revocation_endpoint'], urlencode(data), context=self.ctx)
167169

168170
def refresh(self, refresh_token):
169171
"""
@@ -177,7 +179,7 @@ def refresh(self, refresh_token):
177179
'client_id': self.config['client_id'],
178180
'client_secret': self.config['client_secret']
179181
}
180-
token_response = self.__urlopen(self.config['token_endpoint'], urllib.urlencode(data), context=self.ctx)
182+
token_response = self.__urlopen(self.config['token_endpoint'], urlencode(data), context=self.ctx)
181183
return json.loads(token_response.read())
182184

183185
def get_authn_req_url(self, session, acr, forceAuthN, scope, forceConsent, allowConsentOptionDeselection,
@@ -247,15 +249,16 @@ def get_authn_req_url(self, session, acr, forceAuthN, scope, forceConsent, allow
247249
elif send_parameters_via == "request_uri":
248250
request_args = None # TODO: Implement request URI support
249251

250-
login_url = "%s%s%s" % (self.config['authorization_endpoint'], delimiter, urllib.urlencode(request_args))
252+
login_url = "%s%s%s" % (self.config['authorization_endpoint'], delimiter, urlencode(request_args))
251253

252-
print "Redirect to %s" % login_url
254+
print("Redirect to %s" % login_url)
253255

254256
return login_url
255257

256258
def get_token(self, code, code_verifier):
257259
"""
258260
:param code: The authorization code to use when getting tokens
261+
:param code_verifier: The original code verifier sent with the authorization request
259262
:return the json response containing the tokens
260263
"""
261264
data = {'client_id': self.config['client_id'], "client_secret": self.config['client_secret'],
@@ -266,9 +269,9 @@ def get_token(self, code, code_verifier):
266269

267270
# Exchange code for tokens
268271
try:
269-
token_response = self.__urlopen(self.config['token_endpoint'], urllib.urlencode(data), context=self.ctx)
270-
except urllib2.URLError as te:
271-
print "Could not exchange code for tokens"
272+
token_response = self.__urlopen(self.config['token_endpoint'], urlencode(data), context=self.ctx)
273+
except URLError as te:
274+
print("Could not exchange code for tokens")
272275
raise te
273276
return json.loads(token_response.read())
274277

@@ -294,14 +297,14 @@ def get_registration_token(self):
294297
}
295298

296299
try:
297-
token_response = self.__urlopen(self.config['token_endpoint'], urllib.urlencode(data), context=self.ctx)
298-
except urllib2.URLError as te:
299-
print "Could not get DCR access token"
300+
token_response = self.__urlopen(self.config['token_endpoint'], urlencode(data), context=self.ctx)
301+
except URLError as te:
302+
print("Could not get DCR access token")
300303
raise te
301304

302305
json_response = json.loads(token_response.read())
303306
if self.config['debug']:
304-
print 'Got DCR token response: %s ' % json_response
307+
print('Got DCR token response: %s ' % json_response)
305308

306309
return json_response['access_token']
307310

@@ -322,14 +325,17 @@ def __urlopen(self, url, data=None, context=None, token=None):
322325
if token:
323326
headers['Authorization'] = 'Bearer %s' % token
324327

325-
request = urllib2.Request(url, data, headers)
328+
if data is not None:
329+
data = data.encode('utf-8')
330+
331+
request = Request(url, data, headers)
326332

327333
if self.config['debug']:
328-
print 'Request url: ' + url
329-
print 'Request headers:\n' + json.dumps(headers)
330-
print 'Request data:\n' + json.dumps(data)
334+
print('Request url: ' + url)
335+
print('Request headers:\n' + json.dumps(headers))
336+
print('Request data:\n' + json.dumps(data.decode() if data is not None else None))
331337

332-
return urllib2.urlopen(request, context=context)
338+
return urlopen(request, context=context)
333339

334340
def __authn_req_args(self, state, scope, code_challenge, code_challenge_method="plain"):
335341
"""

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def load_config(self):
5454
return self.store
5555

5656
def _load_from_file(self, filename):
57-
print 'Loading settings from %s' % filename
57+
print('Loading settings from %s' % filename)
5858
self.store = json.loads(open(filename).read())
5959

6060
def _update_config_from_environment(self):

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '2'
22
services:
33
oidc-example:
4-
image: curity/oidc-python-demo:0.1
4+
image: curity/oidc-python-demo:latest
55
ports:
66
- 5443:5443
77
environment:

0 commit comments

Comments
 (0)