From a1d859515016a5e4e6fa41545f8cb310d40341d4 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Sun, 4 Mar 2018 16:47:19 -0500 Subject: [PATCH 01/16] #20 started working on google oauth. --- .dockerignore | 1 + .gitignore | 3 +- app.py | 7 ++- coder_directory_api/api.py | 1 + coder_directory_api/resources/__init__.py | 4 +- coder_directory_api/resources/google.py | 54 +++++++++++++++++++++++ coder_directory_api/settings.py | 7 +++ mock_data/auth.json | 2 +- 8 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 coder_directory_api/resources/google.py diff --git a/.dockerignore b/.dockerignore index db00cf6..e8ffa4a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ *.json .gitignore .travis.yml +/google_secrets.json diff --git a/.gitignore b/.gitignore index 8797299..576b473 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ ENV/ .idea #secrets -coder_directory_api/prod_settings.json \ No newline at end of file +coder_directory_api/prod_settings.json +/google_secrets.json diff --git a/app.py b/app.py index 1a3ef65..8a61f3e 100644 --- a/app.py +++ b/app.py @@ -20,6 +20,7 @@ def create_app() -> Flask: """ api = Flask('__name__') + api.secret_key = SECRET_KEY CORS(api) api.wsgi_app = ProxyFix(api.wsgi_app) api.url_map.strict_slashes = False @@ -49,7 +50,9 @@ def register_resources(api, bp, route=None): if __name__ == '__main__': app = create_app() - app.run(host=HOST, + app.run( + host=HOST, port=PORT, debug=DEBUG, - threaded=MULTITHREADING) + threaded=MULTITHREADING, + ) diff --git a/coder_directory_api/api.py b/coder_directory_api/api.py index edf05b0..fa129c8 100644 --- a/coder_directory_api/api.py +++ b/coder_directory_api/api.py @@ -20,6 +20,7 @@ def create_app() -> Flask: """ api = Flask('__name__') + api.secret_key = SECRET_KEY CORS(api) api.wsgi_app = ProxyFix(api.wsgi_app) api.url_map.strict_slashes = False diff --git a/coder_directory_api/resources/__init__.py b/coder_directory_api/resources/__init__.py index 4f1e804..da34c74 100644 --- a/coder_directory_api/resources/__init__.py +++ b/coder_directory_api/resources/__init__.py @@ -13,6 +13,7 @@ from .home import api as home_api from .login import api as login_api from .register import api as register_api +from .google import api as google_api # Create a list of resource objects to register in api api_resources = [ @@ -20,5 +21,6 @@ {'bp': users_api, 'route': 'users'}, {'bp': home_api, 'route': None}, {'bp': login_api, 'route': 'login'}, - {'bp': register_api, 'route': 'register'} + {'bp': register_api, 'route': 'register'}, + {'bp': google_api, 'route': 'google'} ] diff --git a/coder_directory_api/resources/google.py b/coder_directory_api/resources/google.py new file mode 100644 index 0000000..fd581ce --- /dev/null +++ b/coder_directory_api/resources/google.py @@ -0,0 +1,54 @@ +from flask import Blueprint, jsonify, redirect, request +from coder_directory_api.settings import GOOGLE_SECRETS +from coder_directory_api.engines.auth_engine import AuthEngine +import coder_directory_api.auth as auth + +import google_auth_oauthlib.flow +import googleapiclient.discovery + +api = Blueprint('google', __name__) +auth_engine = AuthEngine() + +# setup a flow object to manage OAuth exchange. +flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + GOOGLE_SECRETS, + scopes=[ + 'https://www.googleapis.com/auth/plus.login', + 'https://www.googleapis.com/auth/plus.me', + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + ]) +flow.redirect_uri = 'http://localhost:3000/api/google/callback' +authorization_url, state = flow.authorization_url( + access_type='offline', + include_granted_scopes='true' +) + + +@api.route('/', methods=['GET']) +def google_login() -> redirect: + """ + Google OAuth login resource for initiating OAuth Protocol. + Returns: + redirect to google login page. + """ + return redirect(authorization_url) + + +@api.route('/callback') +def callback_url() -> None: + """ + Callback uri for handling the second piece of google OAuth after user has + consented. + Returns: + Nothing. + """ + authorization_response = request.url + flow.fetch_token(authorization_response=authorization_response) + credentials = flow.credentials + d = googleapiclient.discovery.build('oauth2', 'v2', credentials=credentials) + data = d.userinfo().v2().me().get().execute() + auth_doc = {'user': data['email'], 'googleId': data['id']} + auth_engine.add_one(auth_doc) + payload = auth.make_token(auth_doc['user']) + return jsonify(payload) diff --git a/coder_directory_api/settings.py b/coder_directory_api/settings.py index d26f395..71c2a34 100644 --- a/coder_directory_api/settings.py +++ b/coder_directory_api/settings.py @@ -15,6 +15,7 @@ ENV = os.environ.get('API_ENV', 'DEV') BASE_URL = os.environ.get('API_BASE_URL', '/dev') + with open(SECRET, 'r') as s: creds = json.load(s) @@ -26,9 +27,15 @@ SECRET_KEY = creds['secretKey'] +GOOGLE_SECRETS = os.environ.get('GOOGLE', None) +with open(GOOGLE_SECRETS, 'r') as g: + google_secrets = json.load(g) + + if ENV == 'DEV': DEBUG = True MULTITHREADING = False + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' elif ENV == 'PROD': DEBUG = False diff --git a/mock_data/auth.json b/mock_data/auth.json index 05be759..4e08a6f 100644 --- a/mock_data/auth.json +++ b/mock_data/auth.json @@ -11,7 +11,7 @@ }, "refresh_token": { "iss": "coder directory", - "sub": "seekheart", + "sub": "test credentials", "created": "12/06/2017 01:54:11", "jti": "928f8e06-453c-436d-8b11-6fe21a2614d2", "iat": "1512543251000" From 3c85abb4ae633bb112da783d749795d5a881beb9 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Sun, 4 Mar 2018 17:32:33 -0500 Subject: [PATCH 02/16] #20 added validation of google tokens endpoint and tests. Updated home to show new google resource. --- coder_directory_api/resources/google.py | 75 ++++++++++++++++++++++--- coder_directory_api/resources/home.py | 3 +- tests/api/test_api_google.py | 28 +++++++++ 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 tests/api/test_api_google.py diff --git a/coder_directory_api/resources/google.py b/coder_directory_api/resources/google.py index fd581ce..2f003f3 100644 --- a/coder_directory_api/resources/google.py +++ b/coder_directory_api/resources/google.py @@ -1,3 +1,10 @@ +""" +Google resource for Coder Directory + +Copyright (c) 2017 by Mike Tung. +MIT License, see LICENSE for details. +""" + from flask import Blueprint, jsonify, redirect, request from coder_directory_api.settings import GOOGLE_SECRETS from coder_directory_api.engines.auth_engine import AuthEngine @@ -5,6 +12,8 @@ import google_auth_oauthlib.flow import googleapiclient.discovery +from google.oauth2 import id_token +from google.auth.transport import requests api = Blueprint('google', __name__) auth_engine = AuthEngine() @@ -13,26 +22,47 @@ flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( GOOGLE_SECRETS, scopes=[ - 'https://www.googleapis.com/auth/plus.login', - 'https://www.googleapis.com/auth/plus.me', - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/userinfo.profile', - ]) + 'https://www.googleapis.com/auth/plus.login', + 'https://www.googleapis.com/auth/plus.me', + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + ]) flow.redirect_uri = 'http://localhost:3000/api/google/callback' authorization_url, state = flow.authorization_url( - access_type='offline', - include_granted_scopes='true' + access_type='offline', + include_granted_scopes='true' ) +# setup white list for valid issuers +white_list = ['accounts.google.com', 'https://accounts.google.com'] -@api.route('/', methods=['GET']) + +@api.route('/', methods=['GET', 'POST']) def google_login() -> redirect: """ Google OAuth login resource for initiating OAuth Protocol. Returns: redirect to google login page. """ - return redirect(authorization_url) + if request.method == 'GET': + return redirect(authorization_url) + elif request.method == 'POST': + data = request.get_json() + token = data['token'] + client_id = data['clientId'] + + is_valid, data = _validate_google_token( + token=token, + client_id=client_id + ) + + if is_valid: + user = auth_engine.find_one(data) + if not user: + return jsonify({'message': 'User not registered!'}), 404 + return jsonify(user) + else: + return jsonify({'message': data}) @api.route('/callback') @@ -52,3 +82,30 @@ def callback_url() -> None: auth_engine.add_one(auth_doc) payload = auth.make_token(auth_doc['user']) return jsonify(payload) + + +def _validate_google_token(token, client_id) -> tuple: + """ + Helper function to validate a google oauth token + + Args: + token: google token + client_id: application id which was used to get token. + + Returns: + indicator as to whether google token is valid or not and + message/username. + """ + + try: + id_info = id_token.verify_oauth2_token( + token, + requests.Request(), + client_id) + if id_info['iss'] not in white_list: + raise ValueError('Wrong Issuer!') + user_name = id_info['email'] + except ValueError: + return False, 'Invalid Google token!' + + return True, user_name diff --git a/coder_directory_api/resources/home.py b/coder_directory_api/resources/home.py index bc7706f..b14e4c3 100644 --- a/coder_directory_api/resources/home.py +++ b/coder_directory_api/resources/home.py @@ -23,7 +23,8 @@ def home() -> tuple: 'languages': 'Programming languages resource.', 'users': 'Users resource.', 'login': 'Login to api for access.', - 'register': 'Registration route for access to api.' + 'register': 'Registration route for access to api.', + 'google': 'OAuth2 Google sign in for access' } ] diff --git a/tests/api/test_api_google.py b/tests/api/test_api_google.py new file mode 100644 index 0000000..8318eae --- /dev/null +++ b/tests/api/test_api_google.py @@ -0,0 +1,28 @@ +""" +Tests for Google Resource + +Copyright (c) 2017 by Mike Tung. +MIT License, see LICENSE for details. +""" + +from .common_test_setup import CommonApiTest +import json +from coder_directory_api.engines import AuthEngine + +class GoogleResourceTest(CommonApiTest): + def setUp(self): + """Setup Google Tests""" + super(GoogleResourceTest, self).setUp() + self.endpoint = '{}/google'.format(self.base_url) + self.engine = AuthEngine() + + def tearDown(self): + """Teardown method for Google Tests""" + super(GoogleResourceTest, self).tearDown() + + def test_redirect(self): + """Test google oauth sign in redirect""" + result = self.app.get(self.endpoint) + self.assertEqual(result.status_code, + 302, + msg='Expected 301 redirect to Google') \ No newline at end of file From 0161dcbb71254d24c184c55e3e31a551c9270faf Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Sun, 4 Mar 2018 17:36:49 -0500 Subject: [PATCH 03/16] #20 updated readme, dependencies, and docker-compose to support google OAuth --- README.md | 4 +++- docker-compose.yml | 2 ++ requirements.txt | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f643f3..dfe7d55 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@ coders and programming languages. | /register | Registers a user/app to use api | | /login | Login user to obtain token | | /login/token | Send your tokens here to refresh your access before it expires | +| /google | Sign in to google and get access token | | /users | Access users resource for GET/POST | | /users/{id} | Access users resource for GET/PATCH/DELETE for 1 user | | /languages | Access language resource for GET/POST | | /languages/{id} | Access language resource for GET/PATCH/DELETE of 1 language | -With the exception of the `register` and `login` endpoints all resources + +With the exception of the `register`, `google`, and `login` endpoints all resources require a jwt to be sent in the `Authorization` header with `Bearer` scheme. ## Development diff --git a/docker-compose.yml b/docker-compose.yml index f76be61..b59f032 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,10 @@ services: - HOST=0.0.0.0 - API_ENV=PROD - API_SECRET=/app/coder_directory_api/prod_settings.json + - GOOGLE=/app/google_secrets.json volumes: - ./coder_directory_api/prod_settings.json:/app/coder_directory_api/prod_settings.json + - ./google_secrets.json:/app/google_secrets.json links: - mongodb mongodb: diff --git a/requirements.txt b/requirements.txt index 7f393b1..dc4a920 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,18 @@ aniso8601==1.3.0 -certifi==2017.11.5 +asn1crypto==0.24.0 +cachetools==2.0.1 +certifi==2018.1.18 +cffi==1.11.5 chardet==3.0.4 click==6.7 +cryptography==2.1.4 Flask==0.12.2 Flask-Cors==3.0.3 +google-api-python-client==1.6.5 +google-auth==1.4.1 +google-auth-httplib2==0.0.3 +google-auth-oauthlib==0.2.0 +httplib2==0.10.3 idna==2.6 itsdangerous==0.24 Jinja2==2.10 @@ -12,7 +21,11 @@ lazy==1.3 MarkupSafe==1.0 mistune==0.8.1 nose==1.3.7 +oauth2client==4.1.2 oauthlib==2.0.6 +pyasn1==0.4.2 +pyasn1-modules==0.2.1 +pycparser==2.18 PyJWT==1.5.3 pymongo==3.5.1 python-dateutil==2.6.1 @@ -20,7 +33,9 @@ pytz==2017.3 PyYAML==3.12 requests==2.18.4 requests-oauthlib==0.8.0 +rsa==3.4.2 six==1.11.0 +uritemplate==3.0.0 urllib3==1.22 URLObject==2.4.3 Werkzeug==0.12.2 From 5fc75053de714c0ce7130758a0202aa58385bd89 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Sun, 4 Mar 2018 17:51:26 -0500 Subject: [PATCH 04/16] #20 added encrypted secrets for travis CI --- .travis.yml | 3 +++ google_secrets.json.enc | Bin 0 -> 512 bytes 2 files changed, 3 insertions(+) create mode 100644 google_secrets.json.enc diff --git a/.travis.yml b/.travis.yml index b62fc6f..19ddd3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,9 @@ install: services: - mongodb +before_install: + - openssl aes-256-cbc -K $encrypted_fedd13c2de32_key -iv $encrypted_fedd13c2de32_iv -in google_secrets.json.enc -out google_secrets.json -d + # Seed the mongodb with some test data before_script: - bash mock_data/db_init.sh diff --git a/google_secrets.json.enc b/google_secrets.json.enc new file mode 100644 index 0000000000000000000000000000000000000000..cf2c877dad5eb62a0e23307d77929cd8dc6d70c3 GIT binary patch literal 512 zcmV+b0{{I)&&SKI0|mvnxA7)pCA1kN@8Pc&Q!YIwesRT8t4)Tjm`-XFEAlNhiVyQ1 zV!)|$7O6lE3$o3lCQ*%|=}adw3aLE9Vf{oax7+=+M*6-^fkRwhhQD;dvG(B!$)knk z(QOvXOUo}8e)J_tCrBU9&OqXdgK632Qg($ZcJ)CPzPPObdtNEL`VVY8fd}!ckjV;6 zb}U3@0!_Iz*Qss<;D1NuAkr6{^k-12O{ZN;id#akcwBZ?!{tAFXgd@YgKcamg0(5) zAiDYvN(wqb3F*J5rQV)R25<&kn1d&DAip03*V`S#eNs5;ZeeX|lpB%cI(K5Zj(mxX zh0ddd!uue}rLm~WgC_MmJtlIU9v_LVVl@r#-3X2JPqg=0ny;kF?900mj Date: Tue, 6 Mar 2018 00:22:35 -0500 Subject: [PATCH 05/16] #20 refactored jwt api to handle distributing access and refresh tokens separately --- coder_directory_api/auth/__init__.py | 2 +- coder_directory_api/auth/jwt_authorization.py | 151 +++++++++++++----- 2 files changed, 114 insertions(+), 39 deletions(-) diff --git a/coder_directory_api/auth/__init__.py b/coder_directory_api/auth/__init__.py index 6865af5..648d5f6 100644 --- a/coder_directory_api/auth/__init__.py +++ b/coder_directory_api/auth/__init__.py @@ -5,5 +5,5 @@ MIT License, see LICENSE for details """ -__all__ = ['check_token', 'token_required', 'make_token'] +__all__ = ['check_token', 'token_required', 'make_token', 'refresh_token'] from .jwt_authorization import * diff --git a/coder_directory_api/auth/jwt_authorization.py b/coder_directory_api/auth/jwt_authorization.py index 4a39f0b..3fa7218 100644 --- a/coder_directory_api/auth/jwt_authorization.py +++ b/coder_directory_api/auth/jwt_authorization.py @@ -16,6 +16,7 @@ # set some global helpers auth_engine = engines.AuthEngine() secret = settings.SECRET_KEY +expire_time = datetime.timedelta(minutes=5) def refresh_token(token) -> dict or None: @@ -28,33 +29,48 @@ def refresh_token(token) -> dict or None: Returns: refreshed jwt token payload. """ + user = token['user'] + user_refresh_token = token['refresh_token'] + ref_token = None try: - user = token['user'] - token['access_token'] = jwt.decode(token['access_token'], secret) - token['refresh_token'] = jwt.decode(token['refresh_token'], secret) - except (jwt.ExpiredSignatureError, jwt.DecodeError, jwt.InvalidTokenError): + jwt.decode(token['access_token'], secret) + jwt.decode(token['refresh_token'], secret) + except jwt.ExpiredSignatureError as e: + pass + except (jwt.DecodeError, jwt.InvalidTokenError) as e: return None - ref_token = auth_engine.find_one(user=user) + if user: + ref_token = auth_engine.find_one(user=user) + ref_token = ref_token['refresh_token'] - if ref_token == token['refresh_token']: - token['access_token']['exp'] = datetime.datetime.utcnow() + \ - datetime.timedelta(minutes=5) - result = auth_engine.edit_one(user=user, doc=token) + try: + ref_token = ref_token.decode('utf-8') + except AttributeError: + pass + try: + user_refresh_token = user_refresh_token.decode('utf-8') + except AttributeError: + pass + + if ref_token == user_refresh_token: + new_access_token = make_access_token(user) + result = auth_engine.edit_one( + user=user, + doc={'access_token': new_access_token} + ) else: return None - token['access_token'] = jwt.encode( - token['access_token'], secret - ).decode('utf-8') - token['refresh_token'] = jwt.encode( - token['refresh_token'], secret - ).decode('utf-8') - if result: - return token + user_doc = { + 'user': user, + 'access_token': new_access_token, + 'refresh_token': token['refresh_token'] + } + return make_payload(user_doc=user_doc) else: return None @@ -136,21 +152,8 @@ def make_token(user: str) -> dict: for unauthenticated clients. """ - renew_token = { - 'iss': 'coder directory', - 'sub': user, - 'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'), - 'jti': str(uuid.uuid4()), - 'iat': make_timestamp(), - } - - access_token = { - 'iss': 'coder directory', - 'user': user, - 'jti': str(uuid.uuid4()), - 'iat': make_timestamp(), - 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) - } + renew_token = make_refresh_token(user) + access_token = make_access_token(user) tokens = { 'access_token': access_token, @@ -158,14 +161,12 @@ def make_token(user: str) -> dict: } auth_engine.edit_one(user=user, doc=tokens) - - payload = { + user_doc = { 'user': user, - 'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'), - 'access_token': jwt.encode(access_token, secret).decode('utf-8'), - 'refresh_token': jwt.encode(renew_token, secret).decode('utf-8') + 'access_token': tokens['access_token'], + 'refresh_token': tokens['refresh_token'] } - + payload = make_payload(user_doc=user_doc) return payload @@ -178,3 +179,77 @@ def make_timestamp() -> int: """ date = int(datetime.datetime.utcnow().strftime('%s')) * 1000 return date + + +def make_payload(user_doc: dict) -> dict: + """ + Helper function to make payload for jwt tokens. + Args: + user_doc: dictionary containing user, access_token, refresh_token + + Returns: + api payload for jwt token. + """ + + access_token = None + renew_token = None + try: + access_token = user_doc['access_token'].decode('utf-8') + except AttributeError: + access_token = user_doc['access_token'] + + try: + renew_token = user_doc['refresh_token'].decode('utf-8') + except AttributeError: + renew_token = user_doc['refresh_token'] + return { + 'user': user_doc['user'], + 'created': datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S'), + 'expires_in': expire_time.seconds, + 'access_token': access_token, + 'refresh_token': renew_token + } + + +def make_access_token(user_name: str) -> dict: + """ + Helper function to make the access token. + + Args: + user_name: username to make token for. + + Returns: + encrypted jwt access token + """ + return jwt.encode( + { + 'iss': 'coder directory', + 'user': user_name, + 'jti': str(uuid.uuid4()), + 'iat': make_timestamp(), + 'exp': datetime.datetime.now() + expire_time + }, + secret + ) + + +def make_refresh_token(user_name: str) -> dict: + """ + Helper function to make refresh token. + + Args: + user_name: username to make token for. + + Returns: + encrypted jwt refresh token. + """ + + return jwt.encode( + { + 'iss': 'coder directory', + 'sub': user_name, + 'created': datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S'), + 'jti': str(uuid.uuid4()), + 'iat': make_timestamp(), + }, + secret) From 7bb0a0dc1492cbd2e4591abebc79a3cf3ccf6222 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 00:23:04 -0500 Subject: [PATCH 06/16] #20 refactored login resource and tests to make use of new jwt refactoring changes --- coder_directory_api/resources/login.py | 6 +++--- tests/api/test_api_login.py | 17 +++-------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/coder_directory_api/resources/login.py b/coder_directory_api/resources/login.py index 9b74079..2a3045a 100644 --- a/coder_directory_api/resources/login.py +++ b/coder_directory_api/resources/login.py @@ -40,8 +40,8 @@ def login() -> tuple: @api.route('/token', methods=['GET', 'POST']) def refresh() -> tuple: """ - Refresh token resource allows users to renew their access token before - expiration. + Refresh token resource allows users to renew their access token with their + refresh token. Returns: Tuple with json containing refreshed token or message with http status @@ -55,7 +55,7 @@ def refresh() -> tuple: data = request.json payload = auth.refresh_token(data) if payload is None: - message = {'message': 'Access token has expired please re-login'} + message = {'message': 'Bad token'} return jsonify(message), 400 return jsonify(payload), 200 diff --git a/tests/api/test_api_login.py b/tests/api/test_api_login.py index 11347fc..994a251 100644 --- a/tests/api/test_api_login.py +++ b/tests/api/test_api_login.py @@ -7,11 +7,8 @@ from .common_test_setup import CommonApiTest from coder_directory_api.engines import AuthEngine -from coder_directory_api.settings import SECRET_KEY from coder_directory_api.auth import make_token import json -import jwt -import datetime class LoginResourceTest(CommonApiTest): @@ -99,8 +96,8 @@ def test_refresh_token(self): ) self.assertEqual( - result.status_code, 200, + result.status_code, msg='Expected status code to be 200' ) @@ -111,18 +108,10 @@ def test_refresh_token(self): msg='Expected payload of 4 keys to return' ) - def test_refresh_expired_token(self): + def test_bad_token(self): """Test if POST request will bounce bad tokens""" dummy = json.loads(self.dummy) - - dummy['access_token'] = { - 'iss': 'coder directory', - 'user': dummy['user'], - 'exp': datetime.datetime.utcnow() - datetime.timedelta(minutes=5) - } - dummy['access_token'] = jwt.encode( - dummy['access_token'], SECRET_KEY - ).decode('utf-8') + dummy['refresh_token'] = None dummy = json.dumps(dummy) result = self.app.post( From 4151fb5263376e686df4b21734ac5354482ac4e5 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 00:24:10 -0500 Subject: [PATCH 07/16] #20 updated google resource to refresh tokens for logged in google user, and on GET route auto refresh access. --- coder_directory_api/resources/google.py | 26 +++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/coder_directory_api/resources/google.py b/coder_directory_api/resources/google.py index 2f003f3..530e683 100644 --- a/coder_directory_api/resources/google.py +++ b/coder_directory_api/resources/google.py @@ -6,9 +6,10 @@ """ from flask import Blueprint, jsonify, redirect, request -from coder_directory_api.settings import GOOGLE_SECRETS +from coder_directory_api.settings import GOOGLE_SECRETS, PORT from coder_directory_api.engines.auth_engine import AuthEngine import coder_directory_api.auth as auth +from coder_directory_api.auth import refresh_token import google_auth_oauthlib.flow import googleapiclient.discovery @@ -27,7 +28,7 @@ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', ]) -flow.redirect_uri = 'http://localhost:3000/api/google/callback' +flow.redirect_uri = 'http://localhost:{}/api/google/callback'.format(PORT) authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true' @@ -45,9 +46,18 @@ def google_login() -> redirect: redirect to google login page. """ if request.method == 'GET': - return redirect(authorization_url) + try: + auth_token = request.headers['Authorization'].split(' ')[1] + except KeyError: + return redirect(authorization_url) + return refresh_token(auth_token) + elif request.method == 'POST': data = request.get_json() + + if not data: + return jsonify({'message': 'Missing Google OAuth Token!'}), 400 + token = data['token'] client_id = data['clientId'] @@ -60,7 +70,15 @@ def google_login() -> redirect: user = auth_engine.find_one(data) if not user: return jsonify({'message': 'User not registered!'}), 404 - return jsonify(user) + + user_tokens = { + 'user': user['user'], + 'access_token': user['access_token'], + 'refresh_token': user['refresh_token'] + } + new_tokens = refresh_token(user_tokens) + + return jsonify(new_tokens) else: return jsonify({'message': data}) From 4b76f6468582bde339c9c5d01fa3dd095f39c141 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 00:24:28 -0500 Subject: [PATCH 08/16] #20 updated db init script to truely drop db. --- mock_data/db_init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mock_data/db_init.sh b/mock_data/db_init.sh index 17d8817..e1afb79 100755 --- a/mock_data/db_init.sh +++ b/mock_data/db_init.sh @@ -2,7 +2,7 @@ set -e echo Dropping database... -mongo --eval "db.dropDatabase()" +mongo coder --eval "db.dropDatabase()" echo Database dropped! echo Seeding user data... From 44de7b9c9b98744653b37fa95e3e4812f44d6985 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 21:11:28 -0500 Subject: [PATCH 09/16] #22 updated google resource to only need GET, added tests for google resource and updated documentation. --- README.md | 9 ++++-- coder_directory_api/resources/google.py | 42 +++++++------------------ tests/api/test_api_google.py | 15 +++++++-- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index dfe7d55..ff511da 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ The following variables need to be set. * HOST - host address to run app on, defaults to localhost or 0.0.0.0 * PORT - port number to run on, defaults to 3000 * SECRET - path to your secret credentials json file. +* GOOGLE - path to your google credentials json file. In addition for `APP_ENV` this variable will determine whether the app outputs debug messages if not in `PROD` and whether or not `MULTITHREADING` for @@ -91,6 +92,10 @@ signatures for JWT. Included in the project is an example setup file: `dev_settings.json` +### GOOGLE credentials +In order to use google oauth you will need to register a service account with +[google]. + ## Author * **Mike Tung** - *Main Developer* - [Github] @@ -98,5 +103,5 @@ Included in the project is an example setup file: `dev_settings.json` [Github]: https://github.com/seekheart [travis]: https://travis-ci.org/seekheart/coder_directory_api.svg?branch=master [license]: https://img.shields.io/badge/license-MIT%20License-blue.svg -[version]: https://img.shields.io/badge/Version-1.0.0-brightgreen.svg - +[version]: https://img.shields.io/badge/Version-1.1.0-brightgreen.svg +[google]: https://console.developers.google.com diff --git a/coder_directory_api/resources/google.py b/coder_directory_api/resources/google.py index 530e683..c3b2e1e 100644 --- a/coder_directory_api/resources/google.py +++ b/coder_directory_api/resources/google.py @@ -6,7 +6,7 @@ """ from flask import Blueprint, jsonify, redirect, request -from coder_directory_api.settings import GOOGLE_SECRETS, PORT +from coder_directory_api.settings import GOOGLE_SECRETS, PORT, google_secrets from coder_directory_api.engines.auth_engine import AuthEngine import coder_directory_api.auth as auth from coder_directory_api.auth import refresh_token @@ -38,49 +38,36 @@ white_list = ['accounts.google.com', 'https://accounts.google.com'] -@api.route('/', methods=['GET', 'POST']) -def google_login() -> redirect: +@api.route('/', methods=['GET']) +def google_login() -> redirect or tuple: """ Google OAuth login resource for initiating OAuth Protocol. Returns: - redirect to google login page. + redirect to google login page if no auth token provided, otherwise, + api will return api access and refresh tokens if google token is valid + along with 200 status code, or 400 status code with error message if + google token. """ if request.method == 'GET': try: auth_token = request.headers['Authorization'].split(' ')[1] + is_valid, data = _validate_google_token(auth_token) except KeyError: return redirect(authorization_url) - return refresh_token(auth_token) - - elif request.method == 'POST': - data = request.get_json() - - if not data: - return jsonify({'message': 'Missing Google OAuth Token!'}), 400 - - token = data['token'] - client_id = data['clientId'] - - is_valid, data = _validate_google_token( - token=token, - client_id=client_id - ) if is_valid: user = auth_engine.find_one(data) if not user: return jsonify({'message': 'User not registered!'}), 404 - user_tokens = { 'user': user['user'], 'access_token': user['access_token'], 'refresh_token': user['refresh_token'] } new_tokens = refresh_token(user_tokens) - return jsonify(new_tokens) else: - return jsonify({'message': data}) + return jsonify({'message': data}), 400 @api.route('/callback') @@ -89,7 +76,7 @@ def callback_url() -> None: Callback uri for handling the second piece of google OAuth after user has consented. Returns: - Nothing. + api access and refresh tokens """ authorization_response = request.url flow.fetch_token(authorization_response=authorization_response) @@ -102,13 +89,12 @@ def callback_url() -> None: return jsonify(payload) -def _validate_google_token(token, client_id) -> tuple: +def _validate_google_token(token) -> tuple: """ Helper function to validate a google oauth token Args: token: google token - client_id: application id which was used to get token. Returns: indicator as to whether google token is valid or not and @@ -116,14 +102,10 @@ def _validate_google_token(token, client_id) -> tuple: """ try: - id_info = id_token.verify_oauth2_token( - token, - requests.Request(), - client_id) + id_info = id_token.verify_oauth2_token(token, requests.Request()) if id_info['iss'] not in white_list: raise ValueError('Wrong Issuer!') user_name = id_info['email'] except ValueError: return False, 'Invalid Google token!' - return True, user_name diff --git a/tests/api/test_api_google.py b/tests/api/test_api_google.py index 8318eae..a038370 100644 --- a/tests/api/test_api_google.py +++ b/tests/api/test_api_google.py @@ -9,12 +9,12 @@ import json from coder_directory_api.engines import AuthEngine + class GoogleResourceTest(CommonApiTest): def setUp(self): """Setup Google Tests""" super(GoogleResourceTest, self).setUp() self.endpoint = '{}/google'.format(self.base_url) - self.engine = AuthEngine() def tearDown(self): """Teardown method for Google Tests""" @@ -25,4 +25,15 @@ def test_redirect(self): result = self.app.get(self.endpoint) self.assertEqual(result.status_code, 302, - msg='Expected 301 redirect to Google') \ No newline at end of file + msg='Expected 301 redirect to Google') + + def test_token(self): + """Test google oauth with bad OAuth token""" + result = self.app.get(self.endpoint, + headers={'Authorization': 'Bearer asdf'}) + + self.assertEquals( + 400, + result.status_code, + msg='Expected 400 status code' + ) From e02a4be7a2b55f8200bd1c10e806b56adf68e261 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 21:30:40 -0500 Subject: [PATCH 10/16] #22 fixed bug where token was always expired, had to move exp keys to utcnow time. Changed auth json to reflect encrypted credentials --- coder_directory_api/auth/jwt_authorization.py | 8 +++----- mock_data/auth.json | 18 ++++-------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/coder_directory_api/auth/jwt_authorization.py b/coder_directory_api/auth/jwt_authorization.py index 3fa7218..70327fc 100644 --- a/coder_directory_api/auth/jwt_authorization.py +++ b/coder_directory_api/auth/jwt_authorization.py @@ -94,7 +94,7 @@ def check_token(token) -> bool: jwt.DecodeError, jwt.InvalidTokenError, KeyError - ): + ) as e: result = False else: user = auth_engine.find_one(decoded_token['user']) @@ -191,8 +191,6 @@ def make_payload(user_doc: dict) -> dict: api payload for jwt token. """ - access_token = None - renew_token = None try: access_token = user_doc['access_token'].decode('utf-8') except AttributeError: @@ -227,7 +225,7 @@ def make_access_token(user_name: str) -> dict: 'user': user_name, 'jti': str(uuid.uuid4()), 'iat': make_timestamp(), - 'exp': datetime.datetime.now() + expire_time + 'exp': datetime.datetime.utcnow() + expire_time }, secret ) @@ -248,7 +246,7 @@ def make_refresh_token(user_name: str) -> dict: { 'iss': 'coder directory', 'sub': user_name, - 'created': datetime.datetime.now().strftime('%m/%d/%Y %H:%M:%S'), + 'created': datetime.datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S'), 'jti': str(uuid.uuid4()), 'iat': make_timestamp(), }, diff --git a/mock_data/auth.json b/mock_data/auth.json index 4e08a6f..b0b6da4 100644 --- a/mock_data/auth.json +++ b/mock_data/auth.json @@ -2,19 +2,9 @@ { "user": "test1", "password": "test", - "access_token": { - "iss": "coder directory", - "user": "test", - "jti": "46cb6d55-7885-4ca7-abaf-2d3694c59af4", - "iat": 1512543251000, - "exp": "2017-12-06T01:59:11.319Z" - }, - "refresh_token": { - "iss": "coder directory", - "sub": "test credentials", - "created": "12/06/2017 01:54:11", - "jti": "928f8e06-453c-436d-8b11-6fe21a2614d2", - "iat": "1512543251000" - } + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJ1c2VyIjoidGVzdDEiLCJqdGkiOiI5YTNmYWM2My1hZWUyLTRiZWMtYWY0My0wODJjM2JhZDI3ZGEiLCJpYXQiOjE1MjA0MDY5MDEwMDAsImV4cCI6MTUyMDM3MTIwMX0.uIMh-rrJnusCV5mub1PV8OW0pg9ZN509EnhA-7nWKtE", + "created": "03/06/2018 21:15:01", + "expires_in": 300, + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJzdWIiOiJ0ZXN0MSIsImNyZWF0ZWQiOiIwMy8wNi8yMDE4IDIxOjE1OjAxIiwianRpIjoiZGVhMTYzMmUtNjQxMS00NjFhLTg1MzktNDYyOTJkYzdlMjNhIiwiaWF0IjoxNTIwNDA2OTAxMDAwfQ.-tvqRuXUuvOnsMy5DLW4RWHDZ-vL-FWYfi2QVyuUDGo" } ] \ No newline at end of file From 64fdd2105e290c649b3be8adb48895f5f9424ef3 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 21:37:20 -0500 Subject: [PATCH 11/16] #22 updated auth doc --- mock_data/auth.json | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/mock_data/auth.json b/mock_data/auth.json index b0b6da4..b4515be 100644 --- a/mock_data/auth.json +++ b/mock_data/auth.json @@ -1,10 +1,8 @@ -[ - { - "user": "test1", - "password": "test", - "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJ1c2VyIjoidGVzdDEiLCJqdGkiOiI5YTNmYWM2My1hZWUyLTRiZWMtYWY0My0wODJjM2JhZDI3ZGEiLCJpYXQiOjE1MjA0MDY5MDEwMDAsImV4cCI6MTUyMDM3MTIwMX0.uIMh-rrJnusCV5mub1PV8OW0pg9ZN509EnhA-7nWKtE", - "created": "03/06/2018 21:15:01", - "expires_in": 300, - "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJzdWIiOiJ0ZXN0MSIsImNyZWF0ZWQiOiIwMy8wNi8yMDE4IDIxOjE1OjAxIiwianRpIjoiZGVhMTYzMmUtNjQxMS00NjFhLTg1MzktNDYyOTJkYzdlMjNhIiwiaWF0IjoxNTIwNDA2OTAxMDAwfQ.-tvqRuXUuvOnsMy5DLW4RWHDZ-vL-FWYfi2QVyuUDGo" - } -] \ No newline at end of file +{ + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJ1c2VyIjoidGVzdDEiLCJqdGkiOiIwODc3OTU3Ni02YmRmLTQxMjAtOTQ5Yy02NmQ5NTc4MjM4ZTAiLCJpYXQiOjE1MjA0MDc5MDkwMDAsImV4cCI6MTUyMDM5MDIwOX0.GYUrnHvZUBiGHnSdolj-cfXN8bSm8wLAY23tkJ9LFNk", + "created": "03/06/2018 21:31:49", + "expires_in": 300, + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJzdWIiOiJ0ZXN0MSIsImNyZWF0ZWQiOiIwMy8wNy8yMDE4IDAyOjMxOjQ5IiwianRpIjoiZWM4OTI4MGYtODExYS00ZjQzLWJlYzItZDI1MGRhZDBmOTRjIiwiaWF0IjoxNTIwNDA3OTA5MDAwfQ.OYOv40XCnLhM8dD3lTlnjkXWbAP_-3mlx4g1J0vkMH0", + "user": "test1", + "password": "test" +} \ No newline at end of file From a42a871ab9b8d9a7bf832ee5108488fc35feff17 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 22:06:55 -0500 Subject: [PATCH 12/16] #22 fixed auth json --- mock_data/auth.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mock_data/auth.json b/mock_data/auth.json index b4515be..dfd0c52 100644 --- a/mock_data/auth.json +++ b/mock_data/auth.json @@ -1,8 +1,10 @@ -{ - "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJ1c2VyIjoidGVzdDEiLCJqdGkiOiIwODc3OTU3Ni02YmRmLTQxMjAtOTQ5Yy02NmQ5NTc4MjM4ZTAiLCJpYXQiOjE1MjA0MDc5MDkwMDAsImV4cCI6MTUyMDM5MDIwOX0.GYUrnHvZUBiGHnSdolj-cfXN8bSm8wLAY23tkJ9LFNk", - "created": "03/06/2018 21:31:49", - "expires_in": 300, - "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJzdWIiOiJ0ZXN0MSIsImNyZWF0ZWQiOiIwMy8wNy8yMDE4IDAyOjMxOjQ5IiwianRpIjoiZWM4OTI4MGYtODExYS00ZjQzLWJlYzItZDI1MGRhZDBmOTRjIiwiaWF0IjoxNTIwNDA3OTA5MDAwfQ.OYOv40XCnLhM8dD3lTlnjkXWbAP_-3mlx4g1J0vkMH0", - "user": "test1", - "password": "test" -} \ No newline at end of file +[ + { + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJ1c2VyIjoidGVzdDEiLCJqdGkiOiIwODc3OTU3Ni02YmRmLTQxMjAtOTQ5Yy02NmQ5NTc4MjM4ZTAiLCJpYXQiOjE1MjA0MDc5MDkwMDAsImV4cCI6MTUyMDM5MDIwOX0.GYUrnHvZUBiGHnSdolj-cfXN8bSm8wLAY23tkJ9LFNk", + "created": "03/06/2018 21:31:49", + "expires_in": 300, + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjb2RlciBkaXJlY3RvcnkiLCJzdWIiOiJ0ZXN0MSIsImNyZWF0ZWQiOiIwMy8wNy8yMDE4IDAyOjMxOjQ5IiwianRpIjoiZWM4OTI4MGYtODExYS00ZjQzLWJlYzItZDI1MGRhZDBmOTRjIiwiaWF0IjoxNTIwNDA3OTA5MDAwfQ.OYOv40XCnLhM8dD3lTlnjkXWbAP_-3mlx4g1J0vkMH0", + "user": "test1", + "password": "test" + } +] \ No newline at end of file From 9ba02f764bdde1454d097d428486ab1f18e0ca0b Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Tue, 6 Mar 2018 23:37:03 -0500 Subject: [PATCH 13/16] added code climate added badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff511da..4ed53c1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status][travis]](https://travis-ci.org/seekheart/coder_directory_api) [![License][license]](https://img.shields.io/badge/license-MIT%20License-blue.svg) [![Version][version]](https://img.shields.io/badge/Version-1.0.0-brightgreen.svg) - +[![Maintainability][maintain]](https://codeclimate.com/github/seekheart/coder_directory_api/maintainability) The Coder Directory Api is a RESTful api developed to provide management of coders and programming languages. @@ -105,3 +105,4 @@ In order to use google oauth you will need to register a service account with [license]: https://img.shields.io/badge/license-MIT%20License-blue.svg [version]: https://img.shields.io/badge/Version-1.1.0-brightgreen.svg [google]: https://console.developers.google.com +[maintain]: https://api.codeclimate.com/v1/badges/47c92b40567f27394cec/maintainability From 1a40694ce346e4f7024626178b0deea0ec6818a5 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Thu, 8 Mar 2018 20:49:50 -0500 Subject: [PATCH 14/16] applied travis changes to use coveralls --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 19ddd3e..4111c07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ python: install: - "pip install -U setuptools" - "pip install -r requirements.txt" + - "pip install coveralls" # we use mongodb as our database services: @@ -24,7 +25,10 @@ before_script: # Run the test suite script: - - nosetests + - coverage run -m nose tests + +after_success: + - coveralls # notify if there is a change in build status notification: From acce5fb72909faa66740c614659e268a304ef89d Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Thu, 8 Mar 2018 21:01:20 -0500 Subject: [PATCH 15/16] applied travis changes to use coverage reporter, added badge to readme --- .travis.yml | 9 ++++++++- README.md | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4111c07..a542c76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +env: + global: + - CC_TEST_REPORTER_ID=5fcffa3ecf8aa57eefeb35fceff9cd300db4af96a4287dc29e1b1a12ad46eb29 # These are the versions of our backend to be supported language: python python: @@ -19,9 +22,12 @@ services: before_install: - openssl aes-256-cbc -K $encrypted_fedd13c2de32_key -iv $encrypted_fedd13c2de32_iv -in google_secrets.json.enc -out google_secrets.json -d -# Seed the mongodb with some test data +# Seed the mongodb with some test data, and setup test coverage reporters before_script: - bash mock_data/db_init.sh + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build # Run the test suite script: @@ -29,6 +35,7 @@ script: after_success: - coveralls + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT # notify if there is a change in build status notification: diff --git a/README.md b/README.md index 4ed53c1..35b7036 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![License][license]](https://img.shields.io/badge/license-MIT%20License-blue.svg) [![Version][version]](https://img.shields.io/badge/Version-1.0.0-brightgreen.svg) [![Maintainability][maintain]](https://codeclimate.com/github/seekheart/coder_directory_api/maintainability) +[![Coverage Status][coverage]](https://coveralls.io/github/seekheart/coder_directory_api?branch=master) The Coder Directory Api is a RESTful api developed to provide management of coders and programming languages. @@ -106,3 +107,4 @@ In order to use google oauth you will need to register a service account with [version]: https://img.shields.io/badge/Version-1.1.0-brightgreen.svg [google]: https://console.developers.google.com [maintain]: https://api.codeclimate.com/v1/badges/47c92b40567f27394cec/maintainability +[coverage]: https://coveralls.io/repos/github/seekheart/coder_directory_api/badge.svg?branch=master \ No newline at end of file From 12eda65c74d68cb56cdc55d54c3d27a01c0a20f5 Mon Sep 17 00:00:00 2001 From: Mike Tung Date: Thu, 8 Mar 2018 21:18:06 -0500 Subject: [PATCH 16/16] refactored refresh token logic, by removing unused logic. --- coder_directory_api/auth/jwt_authorization.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/coder_directory_api/auth/jwt_authorization.py b/coder_directory_api/auth/jwt_authorization.py index 70327fc..d387989 100644 --- a/coder_directory_api/auth/jwt_authorization.py +++ b/coder_directory_api/auth/jwt_authorization.py @@ -31,29 +31,23 @@ def refresh_token(token) -> dict or None: """ user = token['user'] user_refresh_token = token['refresh_token'] - ref_token = None try: - jwt.decode(token['access_token'], secret) jwt.decode(token['refresh_token'], secret) - except jwt.ExpiredSignatureError as e: - pass except (jwt.DecodeError, jwt.InvalidTokenError) as e: return None if user: ref_token = auth_engine.find_one(user=user) + else: + return None ref_token = ref_token['refresh_token'] try: ref_token = ref_token.decode('utf-8') except AttributeError: - pass - try: - user_refresh_token = user_refresh_token.decode('utf-8') - except AttributeError: - pass + return None if ref_token == user_refresh_token: new_access_token = make_access_token(user)