Skip to content

Commit

Permalink
Adding Authentication handlers for API and Web Server (#1105)
Browse files Browse the repository at this point in the history
* Adding Authentication handlers for API and Web Server

* Updated the requirements/base.txt

* Added jwt package in requirements/base.txt

* Modified as per the suggestions

* changes made as per suggestions

* More changes as suggested
  • Loading branch information
amanpro30 committed Jun 16, 2021
1 parent 2d8c709 commit 554d455
Show file tree
Hide file tree
Showing 10 changed files with 471 additions and 36 deletions.
61 changes: 61 additions & 0 deletions owtf/api/handlers/api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
owtf.api.handlers.api_token
~~~~~~~~~~~~~~~~~~~~~~~~
"""
from owtf.api.handlers.base import APIRequestHandler
from owtf.api.handlers.jwtauth import jwtauth
from uuid import uuid4
from owtf.models.api_token import ApiToken
import jwt
from owtf.settings import JWT_SECRET_KEY, JWT_OPTIONS
from owtf.lib.exceptions import APIError


@jwtauth
class ApiTokenGenerateHandler(APIRequestHandler):
"""Create the api token for a user."""

SUPPORTED_METHODS = ["GET"]

def get(self):
"""Get api token for a logged in user
**Example request**:
.. sourcecode:: http
GET /api/v1/generate/api_token/
Accept: application/json, text/javascript, */*
X-Requested-With: XMLHttpRequest
**Example response**:
.. sourcecode:: http
**ApiToken successful response**;
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json
{
"status": "success",
"data": {
"api_key": "b9e7157c-2150-4e34-b3f1-1777a75debb7"
}
}
"""
api_key = str(uuid4())
try:
token = self.request.headers.get("Authorization").split()[1]
payload = jwt.decode(token, JWT_SECRET_KEY, options=JWT_OPTIONS)
user_id = payload.get("user_id", None)
if not user_id:
raise APIError(400, "Invalid User Id")
ApiToken.add_api_token(self.session, api_key, user_id)
data = {"api_key": api_key}
return self.success(data)
except Exception:
raise APIError(400, "Invalid Token")
218 changes: 218 additions & 0 deletions owtf/api/handlers/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""
owtf.api.handlers.auth
~~~~~~~~~~~~~~~~~~~~~~~~
"""
from owtf.models.user_login_token import UserLoginToken
from owtf.api.handlers.base import APIRequestHandler
from owtf.lib.exceptions import APIError
from owtf.models.user import User
from datetime import datetime, timedelta
import bcrypt
import json
import jwt
import re
from owtf.settings import (
JWT_SECRET_KEY,
JWT_ALGORITHM,
JWT_EXP_DELTA_SECONDS,
is_password_valid_regex,
is_email_valid_regex,
)
from owtf.db.session import Session


class LogInHandler(APIRequestHandler):
"""LogIn using the correct credentials (email, password). After successfull login a JWT Token is generated"""

SUPPORTED_METHODS = ["POST"]

def post(self):
"""
**Example request**:
.. sourcecode:: http
POST /api/v1/login/ HTTP/1.1
Content-Type: application/json; charset=UTF-8
{
"email": "test@test.com",
"password": "Test@34335",
}
**Example response**:
.. sourcecode:: http
**Login successful response**;
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json; charset=UTF-8
{
"status": "success",
"data": {
"jwt-token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjozNSwiZXhwIjoxNjIzMjUyMjQwfQ.FjTpJySn3wprlaS26dC9LGBOMrtHJeJsTDJnyCKNmBk"
}
}
**Login failed response**;
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json; charset=UTF-8
{
"status": "fail",
"data": "Invalid login credentials"
}
"""
body_data = json.loads(self.request.body.decode("utf-8"))
email = body_data.get("email", None)
password = body_data.get("password", None)
if not email:
raise APIError(400, "Missing email value")
if not password:
raise APIError(400, "Missing password value")
user = User.find_by_email(self.session, email)[0]
if (
user
and user.password
and bcrypt.hashpw(password.encode("utf-8"), user.password.encode("utf-8")) == user.password.encode("utf-8")
):
payload = {"user_id": user.id, "exp": datetime.utcnow() + timedelta(seconds=JWT_EXP_DELTA_SECONDS)}
jwt_token = jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM)
data = {"jwt-token": jwt_token.decode("utf-8")}
UserLoginToken.add_user_login_token(self.session, jwt_token, user.id)
self.success(data)
else:
raise APIError(400, "Invalid login credentials")


class RegisterHandler(APIRequestHandler):
"""Registers a new user when he provides email, name, password and confirm password"""

SUPPORTED_METHODS = ["POST"]

def post(self):
"""
**Example request**:
.. sourcecode:: http
POST /api/v1/register/ HTTP/1.1
Content-Type: application/json; charset=UTF-8
{
"email": "test@test.com",
"password": "Test@34335",
"confirm_password": "Test@34335",
"name": "test"
}
**Example response**:
.. sourcecode:: http
**Successful registration response**
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json; charset=UTF-8
{
"status": "success",
"data": "User created successfully"
}
**Failed registration response**
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json; charset=UTF-8
{
"status": "fail",
"data": "Email already exists"
}
"""
body_data = json.loads(self.request.body.decode("utf-8"))
name = body_data.get("name", None)
email = body_data.get("email", None)
password = body_data.get("password", None)
confirm_password = body_data.get("confirm_password", None)

if not name:
raise APIError(400, "Missing username value")
if not email:
raise APIError(400, "Missing email value")
if not password:
raise APIError(400, "Missing password value")
if not confirm_password:
raise APIError(400, "Missing confirm password value")

already_taken = User.find_by_email(self.session, email)
match_password = re.search(is_password_valid_regex, password)
match_email = re.search(is_email_valid_regex, email)

if password != confirm_password:
raise APIError(400, "Password doesn't match")
elif not match_email:
raise APIError(400, "Choose a valid email")
elif not match_password:
raise APIError(400, "Choose a strong password")
elif already_taken:
raise APIError(400, "Email already exists")
else:
hashed_pass = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
user = {}
user["email"] = email
user["password"] = hashed_pass
user["name"] = name
User.add_user(self.session, user)
data = "User created successfully"
self.success(data)


class LogOutHandler(APIRequestHandler):
"""Logs out the current user and clears the cookie"""

def get(self):
"""
**Example request**:
.. sourcecode:: http
GET /api/v1/logout/ HTTP/1.1
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Type: application/json; charset=UTF-8
{
"status": "success",
"data": {
"status": "ok"
}
}
"""
auth = self.request.headers.get("Authorization")
if auth:
parts = auth.split()
token = parts[1]
session = Session()
UserLoginToken.delete_user_login_token(session, token)
data = "Logged out"
self.success(data)
else:
raise APIError(400, "Invalid Token")
63 changes: 63 additions & 0 deletions owtf/api/handlers/jwtauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
JSON Web Token auth for Tornado
"""
from sqlalchemy.sql.functions import user
from owtf.models.user_login_token import UserLoginToken
import jwt
from owtf.settings import JWT_SECRET_KEY, JWT_OPTIONS
from owtf.db.session import Session


def jwtauth(handler_class):
"""Handle Tornado JWT Auth"""

def wrap_execute(handler_execute):
def require_auth(handler, kwargs):

auth = handler.request.headers.get("Authorization")
if auth:
parts = auth.split()

if parts[0].lower() != "bearer" or len(parts) == 1 or len(parts) > 2:
handler._transforms = []
handler.set_status(401)
handler.write("invalid header authorization")
handler.finish()

token = parts[1]
try:
payload = jwt.decode(token, JWT_SECRET_KEY, options=JWT_OPTIONS)
user_id = payload.get("user_id", None)
session = Session()
user_token = UserLoginToken.find_by_userid_and_token(session, user_id, token)
if user_id is None or user_token is None:
handler._transforms = []
handler.set_status(401)
handler.write("Unauthorized")
handler.finish()

except Exception:
handler._transforms = []
handler.set_status(401)
handler.write("Unauthorized")
handler.finish()
else:
handler._transforms = []
handler.write("Missing authorization")
handler.finish()

return True

def _execute(self, transforms, *args, **kwargs):

try:
require_auth(self, kwargs)
except Exception:
return False

return handler_execute(self, transforms, *args, **kwargs)

return _execute

handler_class._execute = wrap_execute(handler_class._execute)
return handler_class
6 changes: 6 additions & 0 deletions owtf/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
URLSearchHandler,
)
from owtf.api.handlers.work import WorkerHandler, WorklistHandler, WorklistSearchHandler
from owtf.api.handlers.auth import LogInHandler, LogOutHandler, RegisterHandler
from owtf.api.handlers.api_token import ApiTokenGenerateHandler
from owtf.db.session import get_scoped_session
from owtf.models.plugin import Plugin
from owtf.settings import STATIC_ROOT
Expand Down Expand Up @@ -75,6 +77,10 @@
tornado.web.url(r"/api/v1/worklist/search/?$", WorklistSearchHandler, name="worklist_search_api_url"),
tornado.web.url(r"/api/v1/configuration/?$", ConfigurationHandler, name="configuration_api_url"),
tornado.web.url(r"/api/v1/dashboard/severitypanel/?$", DashboardPanelHandler),
tornado.web.url(r"/api/v1/register/?$", RegisterHandler, name="regisration_api_url"),
tornado.web.url(r"/api/v1/login/?$", LogInHandler, name="login_api_url"),
tornado.web.url(r"/api/v1/logout/?$", LogOutHandler, name="logout_api_url"),
tornado.web.url(r"/api/v1/generate/api_token/?$", ApiTokenGenerateHandler, name="apitokengenerator_api_url"),
]

UI_HANDLERS = [
Expand Down
30 changes: 30 additions & 0 deletions owtf/models/api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
owtf.models.api_token
~~~~~~~~~~~~~~~~
"""
from sqlalchemy import Column, Integer, Unicode, ForeignKey
from owtf.db.model_base import Model
import uuid


class ApiToken(Model):
__tablename__ = "api_tokens"

id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"))
key = Column(Unicode(255), nullable=False)

@classmethod
def find_by_userid(cls, session, user_id):
"""Find a api_token by user_id.
Returns None if not found.
"""
return session.query(ApiToken).filter_by(user_id=user_id).all()

@classmethod
def add_api_token(cls, session, key, user_id):
"""Adds an api_token to the DB"""
new_token = cls(user_id=user_id, key=key)
session.add(new_token)
session.commit()

0 comments on commit 554d455

Please sign in to comment.