From 7d97ae2e92e0647acd53287aae827e83d37f59a8 Mon Sep 17 00:00:00 2001 From: KMaina Date: Wed, 26 Sep 2018 10:54:23 +0300 Subject: [PATCH 1/5] [Feature #160784176] Restructure the code --- app/__init__.py | 4 ++- app/api/v1/{views.py => views_orders.py} | 0 app/api/v1/views_users.py | 0 .../{test_views.py => test_views_orders.py} | 0 app/tests/v1/test_views_users.py | 36 +++++++++++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) rename app/api/v1/{views.py => views_orders.py} (100%) create mode 100644 app/api/v1/views_users.py rename app/tests/v1/{test_views.py => test_views_orders.py} (100%) create mode 100644 app/tests/v1/test_views_users.py diff --git a/app/__init__.py b/app/__init__.py index 148093e..46cf7dd 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,7 +7,9 @@ from flask import Flask from flask_restful import Api -from app.api.v1.views import Orders, OrderSpecific +from app.api.v1.views_orders import Orders, OrderSpecific + + from instance.config import app_config diff --git a/app/api/v1/views.py b/app/api/v1/views_orders.py similarity index 100% rename from app/api/v1/views.py rename to app/api/v1/views_orders.py diff --git a/app/api/v1/views_users.py b/app/api/v1/views_users.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/v1/test_views.py b/app/tests/v1/test_views_orders.py similarity index 100% rename from app/tests/v1/test_views.py rename to app/tests/v1/test_views_orders.py diff --git a/app/tests/v1/test_views_users.py b/app/tests/v1/test_views_users.py new file mode 100644 index 0000000..bf65531 --- /dev/null +++ b/app/tests/v1/test_views_users.py @@ -0,0 +1,36 @@ +import unittest +import json +from app import create_app + +class TestOrders(unittest.TestCase): + def setUp(self): + self.app = create_app('testing') + self.app.config['TESTING'] = True + self.client = self.app.test_client + self.order = { + "name": "Pizza", + "quantity": 2, + "description": "Perfect as a snack.", + "status": 0 + } + self.changed_order = { + "name": "Pizza", + "quantity": 2, + "description": "Perfect as a snack.", + "status": 1 + } + + def test_add_a_user(self): + """Test for adding a user""" + pass + + + def test_login_user(self): + """Test for logging in a user""" + pass + + def test_logout_user(self): + """Test for fetching a specific order""" + pass + + \ No newline at end of file From ddd4afa8e73125b3ea7af6a2b85c15b3dd239b39 Mon Sep 17 00:00:00 2001 From: KMaina Date: Wed, 26 Sep 2018 15:51:13 +0300 Subject: [PATCH 2/5] [Feature #160784176] Add a models.py file with a Users class --- app/__init__.py | 4 +- app/api/v1/models.py | 63 ++++++++++++++++++++++++++++++++ app/api/v1/views_users.py | 45 +++++++++++++++++++++++ app/tests/v1/test_views_users.py | 30 +++++++-------- 4 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 app/api/v1/models.py diff --git a/app/__init__.py b/app/__init__.py index 46cf7dd..910afd8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,7 +9,7 @@ from app.api.v1.views_orders import Orders, OrderSpecific - +from app.api.v1.views_users import UserRegistration, UserLogin from instance.config import app_config @@ -20,5 +20,7 @@ def create_app(config_name): api_endpoint = Api(app) api_endpoint.add_resource(Orders, '/api/v1/orders') api_endpoint.add_resource(OrderSpecific, '/api/v1/order/') + api_endpoint.add_resource(UserRegistration, '/api/v1/users') + api_endpoint.add_resource(UserLogin, '/api/v1/users/login') return app \ No newline at end of file diff --git a/app/api/v1/models.py b/app/api/v1/models.py new file mode 100644 index 0000000..4c240a0 --- /dev/null +++ b/app/api/v1/models.py @@ -0,0 +1,63 @@ +from flask import jsonify, request + +USERS_LIST = [] + +USERS_DICT = [] + +class Users(): + + user_id = 0 + + def register_user(self, name, password, username, email, address, telephone, admin): + """Returns a message confirm if registration was succesful or not""" + global USERS_DICT + global USERS_LIST + + USERS_DICT = { + 'name' : name, + 'password' : password, + 'username' : username, + 'email' : email, + 'address' : address, + 'telephone' : telephone, + 'id' : int(Users.user_id + 1), + 'admin' : admin + } + + Users.user_id += 1 + USERS_LIST.append(USERS_DICT) + + response = jsonify({'msg':'User added sucessfully'}) + response.status_code = 201 + + return response + + def get_all_users(self): + global USERS_LIST + if len(USERS_LIST) == 0: + response = jsonify({'msg':'No users yet'}) + response.status_code = 404 + return response + else: + response = jsonify({'users': USERS_LIST}) + response.status_code == 200 + return response + + def login_user(self, username, password): + """Logs in a user given the password and username are correct and valid""" + username = request.json.get('username', None) + password = request.json.get('password', None) + + if not username and password: + return jsonify({'msg':'Username or passowrd is invalid or missing'}), 401 + else: + global USERS_LIST + user = [user for user in USERS_LIST if user['username'] == username and user['password'] == password] + if user: + response = jsonify({'msg' : 'Successfully logged in'}) + response.status_code = 200 + return response + else: + response = jsonify({'msg' : 'Sorry, problem logging you in'}) + response.status_code = 400 + return response diff --git a/app/api/v1/views_users.py b/app/api/v1/views_users.py index e69de29..9403d33 100644 --- a/app/api/v1/views_users.py +++ b/app/api/v1/views_users.py @@ -0,0 +1,45 @@ +from flask_restful import Resource, reqparse +from flask import json, request, Flask +# from flask_jwt_extended import jwt_required +from app.api.v1.models import Users + +class UserRegistration(Resource): + + def post(self): + """Register a new user""" + parser = reqparse.RequestParser() + + parser.add_argument('name', required=True, help='Name must be supplied') + parser.add_argument('password', required=True, help='Password must be supplied') + parser.add_argument('username', required=True, help='Username has to be supplied') + parser.add_argument('email', required=True, help='Email has to be supplied') + parser.add_argument('address', required=True, help='Address has to be supplied') + parser.add_argument('telephone', required=True, help='Telephone number has to be supplied') + parser.add_argument('admin', required=True, help='Admin status is to be supplied as a bool') + + # Parse the arguments into an object + args = parser.parse_args() + + return Users().register_user( + name = request.json['name'], + password = request.json['password'], + username = request.json['username'], + email = request.json['email'], + address = request.json['address'], + telephone = request.json['telephone'], + admin = request.json['admin'] + ) + + def get(self): + """Return all users""" + return Users().get_all_users() + +class UserLogin(Resource): + + def post(self): + """Log in a users""" + return Users().login_user( + username = request.json['username'], + password = request.json['password'] + ) + \ No newline at end of file diff --git a/app/tests/v1/test_views_users.py b/app/tests/v1/test_views_users.py index bf65531..639f0b0 100644 --- a/app/tests/v1/test_views_users.py +++ b/app/tests/v1/test_views_users.py @@ -7,28 +7,28 @@ def setUp(self): self.app = create_app('testing') self.app.config['TESTING'] = True self.client = self.app.test_client - self.order = { - "name": "Pizza", - "quantity": 2, - "description": "Perfect as a snack.", - "status": 0 - } - self.changed_order = { - "name": "Pizza", - "quantity": 2, - "description": "Perfect as a snack.", - "status": 1 + self.user = { + 'name' : "Ken Maina", + 'password' : "1234", + 'username' : "itsme", + 'email' : "ken@me.com", + 'address' : "Roysambu, Nairobi", + 'telephone' : "+712249175", + 'id' : 1, + 'admin' : True } + def test_add_a_user(self): """Test for adding a user""" - pass - + response = self.client().post('/api/v1/users', data=json.dumps(self.user), content_type='application/json') + self.assertEqual(response.status_code, 201) def test_login_user(self): """Test for logging in a user""" - pass - + response = self.client().post('/api/v1/users/login', data = json.dumps(self.user), content_type = 'application/json') + self.assertEqual(response.status_code, 200) + def test_logout_user(self): """Test for fetching a specific order""" pass From c62b876a296725a12f71dad2824762628ab99b9b Mon Sep 17 00:00:00 2001 From: KMaina Date: Wed, 26 Sep 2018 23:19:53 +0300 Subject: [PATCH 3/5] [Feature #160784176] Implement JWT functionality in the app --- app/__init__.py | 9 ++++++++ app/api/v1/models.py | 34 +++++++++++++++++++--------- app/api/v1/views_users.py | 14 ++++++++++-- app/tests/v1/test_views_users.py | 38 +++++++++++++++++++++++++++----- instance/config.py | 11 +++++---- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 910afd8..7c8b975 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,9 +3,11 @@ included to make app a package """ +import os from flask import Flask from flask_restful import Api +from flask_jwt_extended import JWTManager from app.api.v1.views_orders import Orders, OrderSpecific @@ -17,10 +19,17 @@ def create_app(config_name): app = Flask(__name__, instance_relative_config=True) app.config.from_object(app_config[config_name]) app.config.from_pyfile('config.py') + # app.secret_key = os.environ["JWT_SECRET_KEY"] + # os.environ["JWT_SECRET_KEY"] + # app.config.get("SECRET_KEY") + app.secret_key = os.getenv('SECRET_KEY') + # os.getenv("SECRET_KEY") api_endpoint = Api(app) api_endpoint.add_resource(Orders, '/api/v1/orders') api_endpoint.add_resource(OrderSpecific, '/api/v1/order/') api_endpoint.add_resource(UserRegistration, '/api/v1/users') api_endpoint.add_resource(UserLogin, '/api/v1/users/login') + jwt = JWTManager(app) + return app \ No newline at end of file diff --git a/app/api/v1/models.py b/app/api/v1/models.py index 4c240a0..c445235 100644 --- a/app/api/v1/models.py +++ b/app/api/v1/models.py @@ -1,4 +1,5 @@ -from flask import jsonify, request +from flask import jsonify, request, make_response +from flask_jwt_extended import create_access_token, get_jwt_identity, get_raw_jwt USERS_LIST = [] @@ -33,14 +34,21 @@ def register_user(self, name, password, username, email, address, telephone, adm return response def get_all_users(self): - global USERS_LIST - if len(USERS_LIST) == 0: - response = jsonify({'msg':'No users yet'}) - response.status_code = 404 - return response - else: - response = jsonify({'users': USERS_LIST}) - response.status_code == 200 + """Returns all users who have registered""" + current_user = get_jwt_identity() + if current_user: + global USERS_LIST + if len(USERS_LIST) == 0: + response = jsonify({'msg':'No users yet'}) + response.status_code = 404 + return response + else: + response = jsonify({'users': USERS_LIST}) + response.status_code == 200 + return response + if not current_user: + response = jsonify({'msg' : 'Sorry, you cannot view this page'}) + response.status_code = 401 return response def login_user(self, username, password): @@ -54,10 +62,14 @@ def login_user(self, username, password): global USERS_LIST user = [user for user in USERS_LIST if user['username'] == username and user['password'] == password] if user: - response = jsonify({'msg' : 'Successfully logged in'}) + access_token = create_access_token(identity=username) + access_token = access_token + response = make_response(jsonify({'access-token' : access_token, 'msg' : 'Successfully logged in'})) response.status_code = 200 + response.headers['Authorization'] = access_token + print(response) return response else: response = jsonify({'msg' : 'Sorry, problem logging you in'}) response.status_code = 400 - return response + return response \ No newline at end of file diff --git a/app/api/v1/views_users.py b/app/api/v1/views_users.py index 9403d33..6a55299 100644 --- a/app/api/v1/views_users.py +++ b/app/api/v1/views_users.py @@ -1,8 +1,10 @@ from flask_restful import Resource, reqparse from flask import json, request, Flask -# from flask_jwt_extended import jwt_required +from flask_jwt_extended import jwt_required + from app.api.v1.models import Users + class UserRegistration(Resource): def post(self): @@ -17,7 +19,6 @@ def post(self): parser.add_argument('telephone', required=True, help='Telephone number has to be supplied') parser.add_argument('admin', required=True, help='Admin status is to be supplied as a bool') - # Parse the arguments into an object args = parser.parse_args() return Users().register_user( @@ -30,6 +31,7 @@ def post(self): admin = request.json['admin'] ) + @jwt_required def get(self): """Return all users""" return Users().get_all_users() @@ -38,6 +40,14 @@ class UserLogin(Resource): def post(self): """Log in a users""" + + parser = reqparse.RequestParser() + + parser.add_argument('password', required=True, help='Password must be supplied') + parser.add_argument('username', required=True, help='Username has to be supplied') + + args = parser.parse_args() + return Users().login_user( username = request.json['username'], password = request.json['password'] diff --git a/app/tests/v1/test_views_users.py b/app/tests/v1/test_views_users.py index 639f0b0..56c6280 100644 --- a/app/tests/v1/test_views_users.py +++ b/app/tests/v1/test_views_users.py @@ -1,10 +1,14 @@ import unittest import json +import os + from app import create_app +from flask_jwt_extended import JWTManager, create_access_token class TestOrders(unittest.TestCase): def setUp(self): self.app = create_app('testing') + jwt = JWTManager(self.app) self.app.config['TESTING'] = True self.client = self.app.test_client self.user = { @@ -17,6 +21,24 @@ def setUp(self): 'id' : 1, 'admin' : True } + self.login = { + 'username' : 'kem1', + 'password' : '1234' + } + + USERS_LIST = [ + { + 'name' : "Ken Maina", + 'password' : "1234", + 'username' : "itsme", + 'email' : "ken@me.com", + 'address' : "Roysambu, Nairobi", + 'telephone' : "+712249175", + 'id' : 1, + 'admin' : True + } + ] + def test_add_a_user(self): @@ -24,13 +46,19 @@ def test_add_a_user(self): response = self.client().post('/api/v1/users', data=json.dumps(self.user), content_type='application/json') self.assertEqual(response.status_code, 201) + def test_get_all_users(self): + # response = self.client().get('/api/v1/users', content_type='application/json') + # result = json.loads(response.data.decode()) + # print(result, file=sys.stderr) + # self.assertEqual(result['name'], 'Ken Maina') + pass + def test_login_user(self): """Test for logging in a user""" - response = self.client().post('/api/v1/users/login', data = json.dumps(self.user), content_type = 'application/json') - self.assertEqual(response.status_code, 200) - - def test_logout_user(self): - """Test for fetching a specific order""" + response = self.client().post('/api/v1/users/login', data = json.dumps(self.login), content_type='application/json') + # result = json.loads(response.data.decode()) + # self.assertEqual(response.status_code, 200) pass + \ No newline at end of file diff --git a/instance/config.py b/instance/config.py index c1c9d92..47b774d 100644 --- a/instance/config.py +++ b/instance/config.py @@ -2,30 +2,29 @@ class Config(object): """Parent configuration class.""" - DEBUG = False - CSRF_ENABLED = True - SECRET = os.getenv('SECRET') + # SECRET_KEY = os.getenv('SECRET') class DevelopmentConfig(Config): """Configurations for Development.""" DEBUG = True - + CSRF_ENABLED = True class TestingConfig(Config): """Configurations for Testing""" TESTING = True DEBUG = True + CSRF_ENABLED = True class StagingConfig(Config): """Configurations for Staging.""" DEBUG = True - + CSRF_ENABLED = True class ProductionConfig(Config): """Configurations for Production.""" DEBUG = False TESTING = False - + CSRF_ENABLED = True app_config = { 'development': DevelopmentConfig, From 00ceed6da7a7d16aab1b44bbda9c29c5ccf3cf4a Mon Sep 17 00:00:00 2001 From: KMaina Date: Thu, 27 Sep 2018 01:24:32 +0300 Subject: [PATCH 4/5] [Feature #160784176] Remove unecessary comments --- app/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 7c8b975..099655f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,11 +19,7 @@ def create_app(config_name): app = Flask(__name__, instance_relative_config=True) app.config.from_object(app_config[config_name]) app.config.from_pyfile('config.py') - # app.secret_key = os.environ["JWT_SECRET_KEY"] - # os.environ["JWT_SECRET_KEY"] - # app.config.get("SECRET_KEY") app.secret_key = os.getenv('SECRET_KEY') - # os.getenv("SECRET_KEY") api_endpoint = Api(app) api_endpoint.add_resource(Orders, '/api/v1/orders') api_endpoint.add_resource(OrderSpecific, '/api/v1/order/') From defe3448698c5841e059ef3c21756c2e6c2963c2 Mon Sep 17 00:00:00 2001 From: KMaina Date: Thu, 27 Sep 2018 01:39:52 +0300 Subject: [PATCH 5/5] [Feature #160784176] Updated the requirements.txt file --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 6cc2731..c80424e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ coveralls==1.5.0 decorator==4.3.0 docopt==0.6.2 Flask==1.0.2 +Flask-JWT-Extended==3.13.0 Flask-RESTful==0.3.6 gunicorn==19.9.0 idna==2.7 @@ -30,6 +31,7 @@ pluggy==0.7.1 prompt-toolkit==1.0.15 py==1.6.0 Pygments==2.2.0 +PyJWT==1.6.4 pylint==2.1.1 pytest==3.8.0 python-dotenv==0.9.1