Skip to content

Commit

Permalink
#33 Defined username and password for webservice components. Improve …
Browse files Browse the repository at this point in the history
…auth tests
  • Loading branch information
SrMouraSilva committed Mar 18, 2018
1 parent 675adb1 commit 06f6941
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Version 0.4.0 - released 05/dd/18

- Breaking change!
- See http://pedalpi.github.io/WebService/ for details
- Components that uses WebService can be use WSParameters.COMPONENT_USERNAME and WSParameters.COMPONENT_PASSWORD for auth

- Improve errors when is not passed all parameters

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def readme():

packages=[
'webservice',
'webservice/database',
'webservice/handler',
'webservice/search',
'webservice/util',
Expand Down
Empty file added webservice/database/__init__.py
Empty file.
43 changes: 43 additions & 0 deletions webservice/database/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from webservice.properties import WSProperties


class UsersDatabase(object):
"""
Management of username
"""

def __init__(self, controller):
"""
:param ComponentDataController controller:
"""
self._controller = controller

self._users = self._read_data(controller)

def _read_data(self, controller):
if WSProperties.USER not in controller[WSProperties.DATA_KEY]:
self._save_users({'pedal pi': 'pedal pi'})

return controller[WSProperties.DATA_KEY][WSProperties.USER]

def _save_users(self, users):
data = self._controller[WSProperties.DATA_KEY]
data[WSProperties.USER] = users
self._controller[WSProperties.DATA_KEY] = data

def auth(self, username, password):
return username in self._users \
and self._users[username] == password

def update(self, username, new_password):
"""
Updates the user password
:param string username:
:param string new_password:
"""
if username in self._users:
raise KeyError('Username "{}" not registered'.format(username))

self._users[username] = new_password
self._save_users(self._users)
17 changes: 12 additions & 5 deletions webservice/handler/auth_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,35 @@
import datetime

import jwt
from application.controller.component_data_controller import ComponentDataController
from webservice.database.database import UsersDatabase
from webservice.handler.abstract_request_handler import AbstractRequestHandler
from webservice.properties import WSProperties
from webservice.util.auth import JWTAuth
from webservice.util.auth import RequiresAuthMixing
from webservice.util.handler_utils import exception


class AuthHandler(RequiresAuthMixing, AbstractRequestHandler):
"""
Based on https://github.com/paulorodriguesxv/tornado-json-web-token-jwt
Handle to auth method.
This method aim to provide a new authorization token
There is a fake payload (for tutorial purpose)
"""
database = None

def initialize(self, app, webservice):
super(AuthHandler, self).initialize(app, webservice)
self.database = UsersDatabase(app.controller(ComponentDataController))

@exception(Exception, 500)
def post(self):
"""
:return The generated token
"""
if self.request_data != {"username": "pedal pi", "password": "pedal pi"}:
username = self.request_data["username"]
password = self.request_data["password"]

if not self.database.auth(username, password) \
and not WSProperties.auth_client_component(username, password):
self.unauthorized("Invalid username or password")
return

Expand Down
3 changes: 0 additions & 3 deletions webservice/handler/plugins_reload_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@

class PluginsReloadHandler(RequiresAuthMixing, AbstractRequestHandler):

def prepare(self):
self.auth()

def prepare(self):
self.auth()

Expand Down
17 changes: 17 additions & 0 deletions webservice/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from webservice.util.auth import generate_random_string


class WSProperties(object):
DATA_KEY = 'WebService'

DEVICE_NAME = 'device_name'
USER = 'user'

""" Default username for component clients"""
COMPONENT_USERNAME = generate_random_string(16)
""" Default password for component clients"""
COMPONENT_PASSWORD = generate_random_string(16)

@staticmethod
def auth_client_component(username, password):
"""
:param string username:
:param string password:
:return:
"""
return username == WSProperties.COMPONENT_USERNAME \
and password == WSProperties.COMPONENT_PASSWORD
12 changes: 11 additions & 1 deletion webservice/util/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,21 @@ def unauthorized(self, message):
self.send(401, {"error": message})


def generate_random_string(size):
"""
Generate a random string with size specified
Like python 3.6 secrets
:param int size: Size of the random string that will be generated
"""
return binascii.hexlify(os.urandom(size)).decode('ascii')


class JWTAuth(object):

AUTHORIZATION_HEADER = 'Authorization'
AUTHORIZATION_METHOD = 'bearer'
SECRET_KEY = binascii.hexlify(os.urandom(16)).decode('ascii')
SECRET_KEY = generate_random_string(16)

@staticmethod
def auth_token(token):
Expand Down
3 changes: 2 additions & 1 deletion webservice/util/handler_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import traceback
import sys
import traceback
from functools import wraps


class integer(object):
"""
Convert the informed args to integer
Expand Down
53 changes: 53 additions & 0 deletions wstest/handler/auth_handler_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import unittest

import requests
from webservice.properties import WSProperties
from wstest.handler.handler_test import Test


Expand All @@ -22,7 +27,55 @@ def test_post_correct_username(self):

self.assertEqual(Test.SUCCESS, response.status_code)

@unittest.skipIf('Pycharm' in os.environ['PWD'], 'Ignore if the test are running in Pycharm')
def test_post_component_username(self):
response = self.rest.auth(WSProperties.COMPONENT_USERNAME, WSProperties.COMPONENT_PASSWORD)

self.assertEqual(Test.SUCCESS, response.status_code)

def test_post_wrong_username(self):
response = self.rest.auth(username="wrong username")

self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_post_invalid_data(self):
response = self.rest.post('auth', {})

self.assertEqual(Test.SERVER_ERROR, response.status_code)

def test_post_wrong_password(self):
response = self.rest.auth(password="wrong password")

self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_auth_missing_authorization(self):
response = self.custom_get('banks', authorization=None)
self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_auth_invalid_token(self):
response = self.custom_get('banks', 'bearer invalid-token')
self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_auth_missing_token(self):
response = self.custom_get('banks', 'bearer')
self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_auth_invalid_header_authorization(self):
response = self.custom_get('banks', 'bearer-wrong {}'.format(self.rest.token))
self.assertEqual(Test.AUTH_ERROR, response.status_code)

def test_ignore_method(self):
response = self.rest.options('banks')
self.assertEqual(204, response.status_code)

def custom_get(self, url, authorization=None):
headers = {'content-type': 'application/json'}

if authorization:
headers['Authorization'] = authorization

print('[GET]', self.rest.address + url)
return requests.get(
self.rest.address + url,
headers=headers
)
11 changes: 9 additions & 2 deletions wstest/rest_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,18 @@ def delete(self, url):
headers=self.headers()
)

def options(self, url):
print('[OPTIONS]', self.address + url)
return requests.options(
self.address + url,
headers=self.headers()
)

# **********************
# Auth
# **********************
def auth(self, password="pedal pi"):
return self.post('auth', {"username": "pedal pi", "password": password})
def auth(self, username="pedal pi", password="pedal pi"):
return self.post('auth', {"username": username, "password": password})

# **********************
# Banks
Expand Down

0 comments on commit 06f6941

Please sign in to comment.