Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Commit

Permalink
Merge 9ae13a0 into 2d68056
Browse files Browse the repository at this point in the history
  • Loading branch information
gleybersonandrade committed Nov 25, 2019
2 parents 2d68056 + 9ae13a0 commit d25a75b
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 4 deletions.
3 changes: 3 additions & 0 deletions etc/kytos/kytos.conf.template
Expand Up @@ -66,3 +66,6 @@ napps_pre_installed = []

vlan_pool = {}
# vlan_pool = {"00:00:00:00:00:00:00:01": {"1": [[1, 2], [5, 10]], "4": [[3, 4]]}}

# The jwt_secret parameter is responsible for signing JSON Web Tokens.
jwt_secret = {{ jwt_secret }}
12 changes: 10 additions & 2 deletions kytos/core/__init__.py
Expand Up @@ -3,11 +3,19 @@
from kytos.core.events import KytosEvent
from kytos.core.logs import NAppLog
from kytos.core.napps import KytosNApp, rest
from kytos.core.auth import authenticated

from .metadata import __version__

__all__ = ('Controller', 'KytosEvent', 'KytosNApp', 'log', 'rest',
'__version__')
__all__ = (
"authenticated",
"Controller",
"KytosEvent",
"KytosNApp",
"log",
"rest",
"__version__",
)

# Kept lowercase to be more user friendly.
log = NAppLog() # pylint: disable=invalid-name
328 changes: 328 additions & 0 deletions kytos/core/auth.py
@@ -0,0 +1,328 @@
"""Module with main classes related to Authentication."""
import datetime
import getpass
import hashlib
import logging
import time
from functools import wraps
from http import HTTPStatus

import jwt
from flask import jsonify, request

from kytos.core.config import KytosConfig
from kytos.core.events import KytosEvent

__all__ = ['authenticated']

LOG = logging.getLogger(__name__)


def authenticated(func):
"""Handle tokens from requests."""
@wraps(func)
def wrapper(*args, **kwargs):
"""Verify the requires of token."""
try:
content = request.headers.get("Authorization")
if content is None:
raise AttributeError
token = content.split("Bearer ")[1]
jwt.decode(token, key=Auth.get_jwt_secret())
except (
AttributeError,
IndexError,
jwt.ExpiredSignature,
jwt.exceptions.DecodeError,
) as exc:
msg = f"Token not sent or expired: {exc}"
return jsonify({"error": msg}), HTTPStatus.UNAUTHORIZED.value
return func(*args, **kwargs)

return wrapper


class Auth:
"""Module used to provide Kytos authentication routes."""

def __init__(self, controller):
"""Init method of Auth class takes the parameters below.
Args:
controller(kytos.core.controller): A Controller instance.
"""
self.controller = controller
self.namespace = "kytos.core.auth.users"
if self.controller.options.create_superuser is True:
self._create_superuser()

@classmethod
def get_jwt_secret(cls):
"""Return JWT secret defined in kytos conf."""
options = KytosConfig().options['daemon']
return options.jwt_secret

@classmethod
def _generate_token(cls, username, time_exp):
"""Generate a jwt token."""
return jwt.encode(
{
'username': username,
'iss': "Kytos NApps Server",
'exp': time_exp,
},
Auth.get_jwt_secret(),
algorithm='HS256',
)

def _create_superuser(self):
"""Create a superuser using Storehouse."""
def _create_superuser_callback(_event, box, error):
if box and not error:
LOG.info("Superuser successfully created")

username = input("Username: ")
email = input("Email: ")

while True:
password = getpass.getpass()
re_password = getpass.getpass('Retype password: ')
if password == re_password:
break
print('Passwords do not match. Try again')

user = {
"username": username,
"email": email,
"password": hashlib.sha512(password.encode()).hexdigest(),
}
content = {
"namespace": self.namespace,
"box_id": user["username"],
"data": user,
"callback": _create_superuser_callback,
}
event = KytosEvent(name="kytos.storehouse.create", content=content)
self.controller.buffers.app.put(event)

def register_core_auth_services(self):
"""
Register /kytos/core/ services over authentication.
It registers create, authenticate, list all, list specific, delete and
update users.
"""
self.controller.api_server.register_core_endpoint(
"auth/login/", self._authenticate_user
)
self.controller.api_server.register_core_endpoint(
"auth/users/", self._list_users
)
self.controller.api_server.register_core_endpoint(
"auth/users/<uid>", self._list_user
)
self.controller.api_server.register_core_endpoint(
"auth/users/", self._create_user, methods=["POST"]
)
self.controller.api_server.register_core_endpoint(
"auth/users/<uid>", self._delete_user, methods=["DELETE"]
)
self.controller.api_server.register_core_endpoint(
"auth/users/<uid>", self._update_user, methods=["PATCH"]
)

def _authenticate_user(self):
"""Authenticate a user using Storehouse."""
username = request.authorization["username"]
password = request.authorization["password"].encode()
try:
user = self._find_user(username)[0].get("data")
if user.get("password") != hashlib.sha512(password).hexdigest():
raise KeyError
time_exp = datetime.datetime.utcnow() + datetime.timedelta(
minutes=10
)
token = self._generate_token(username, time_exp)
return {"token": token.decode()}, HTTPStatus.OK.value
except (AttributeError, KeyError) as exc:
result = f"Incorrect username or password: {exc}"
return result, HTTPStatus.UNAUTHORIZED.value

def _find_user(self, uid):
"""Find a specific user using Storehouse."""
response = {}

def _find_user_callback(_event, box, error):
nonlocal response
if error or not box:
response = {
"answer": "User data cannot be shown",
"code": HTTPStatus.INTERNAL_SERVER_ERROR.value,
}
else:
response = {
"answer": {"data": box.data},
"code": HTTPStatus.OK.value,
}

content = {
"box_id": uid,
"namespace": self.namespace,
"callback": _find_user_callback,
}
event = KytosEvent(name="kytos.storehouse.retrieve", content=content)
self.controller.buffers.app.put(event)
while True:
time.sleep(0.1)
if response:
break
return response["answer"], response["code"]

@authenticated
def _list_user(self, uid):
"""List a specific user using Storehouse."""
answer, code = self._find_user(uid)
if code == HTTPStatus.OK.value:
del answer['data']['password']
return answer, code

@authenticated
def _list_users(self):
"""List all users using Storehouse."""
response = {}

def _list_users_callback(_event, boxes, error):
nonlocal response
if error:
response = {
"answer": "Users cannot be listed",
"code": HTTPStatus.INTERNAL_SERVER_ERROR.value,
}
else:
response = {
"answer": {"users": boxes},
"code": HTTPStatus.OK.value,
}

content = {
"namespace": self.namespace,
"callback": _list_users_callback,
}
event = KytosEvent(name="kytos.storehouse.list", content=content)
self.controller.buffers.app.put(event)
while True:
time.sleep(0.1)
if response:
break
return response["answer"], response["code"]

@authenticated
def _create_user(self):
"""Save a user using Storehouse."""
response = {}

def _create_user_callback(_event, box, error):
nonlocal response
if error or not box:
response = {
"answer": "User cannot be created",
"code": HTTPStatus.CONFLICT.value,
}
else:
response = {
"answer": "User successfully created",
"code": HTTPStatus.OK.value,
}

req = request.json
password = req["password"].encode()
data = {
"username": req["username"],
"email": req["email"],
"password": hashlib.sha512(password).hexdigest(),
}
content = {
"namespace": self.namespace,
"box_id": data["username"],
"data": data,
"callback": _create_user_callback,
}
event = KytosEvent(name="kytos.storehouse.create", content=content)
self.controller.buffers.app.put(event)
while True:
time.sleep(0.1)
if response:
break
return response["answer"], response["code"]

@authenticated
def _delete_user(self, uid):
"""Delete a user using Storehouse."""
response = {}

def _delete_user_callback(_event, box, error):
nonlocal response
if error or not box:
response = {
"answer": "User has not been deleted",
"code": HTTPStatus.INTERNAL_SERVER_ERROR.value,
}
else:
response = {
"answer": "User successfully deleted",
"code": HTTPStatus.OK.value,
}

content = {
"box_id": uid,
"namespace": self.namespace,
"callback": _delete_user_callback,
}
event = KytosEvent(name="kytos.storehouse.delete", content=content)
self.controller.buffers.app.put(event)
while True:
time.sleep(0.1)
if response:
break
return response["answer"], response["code"]

@authenticated
def _update_user(self, uid):
"""Update user data using Storehouse."""
response = {}

def _update_user_callback(_event, box, error):
nonlocal response
if error or not box:
response = {
"answer": "User has not been updated",
"code": HTTPStatus.INTERNAL_SERVER_ERROR.value,
}
else:
response = {
"answer": "User successfully updated",
"code": HTTPStatus.OK.value,
}

req = request.json
allowed = ["username", "email", "password"]

data = {}
for key, value in req.items():
if key in allowed:
data[key] = value

content = {
"namespace": self.namespace,
"box_id": uid,
"data": data,
"callback": _update_user_callback,
}
event = KytosEvent(name="kytos.storehouse.update", content=content)
self.controller.buffers.app.put(event)
while True:
time.sleep(0.1)
if response:
break
return response["answer"], response["code"]
4 changes: 4 additions & 0 deletions kytos/core/config.py
Expand Up @@ -76,6 +76,10 @@ def __init__(self):
action='store_true',
help="Enable all new Entities by default.")

parser.add_argument('-C', '--create_superuser',
action='store_true',
help="Create a kytos superuser.")

self.conf_parser, self.parser = conf_parser, parser
self.parse_args()

Expand Down
4 changes: 4 additions & 0 deletions kytos/core/controller.py
Expand Up @@ -30,6 +30,7 @@
from kytos.core.api_server import APIServer
# from kytos.core.tcp_server import KytosRequestHandler, KytosServer
from kytos.core.atcp_server import KytosServer, KytosServerProtocol
from kytos.core.auth import Auth
from kytos.core.buffers import KytosBuffers
from kytos.core.config import KytosConfig
from kytos.core.connection import ConnectionState
Expand Down Expand Up @@ -120,6 +121,8 @@ def __init__(self, options=None, loop=None):
self.options.api_port,
self.napps_manager, self.options.napps)

self.auth = Auth(self)

self._register_endpoints()
#: Adding the napps 'enabled' directory into the PATH
#: Now you can access the enabled napps with:
Expand Down Expand Up @@ -264,6 +267,7 @@ def _register_endpoints(self):
self.rest_reload_napp)
self.api_server.register_core_endpoint('reload/all',
self.rest_reload_all_napps)
self.auth.register_core_auth_services()

def register_rest_endpoint(self, url, function, methods):
"""Deprecate in favor of @rest decorator."""
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.txt
Expand Up @@ -61,4 +61,4 @@ yala==1.7.0
zipp==0.6.0 # via importlib-metadata

# The following packages are considered to be unsafe in a requirements file:
# setuptools==41.2.0 # via sphinx, tox
# setuptools==41.4.0 # via sphinx, tox

0 comments on commit d25a75b

Please sign in to comment.