from flask import Blueprint, request, session, redirect, render_template
import core
import tools
import logging
import bcrypt
import json
import traceback
import sys
from whenareyou import whenareyou
try:
import queue as Queue
except ImportError:
import Queue
db = None
configuration_data = None
log = logging.getLogger()
api = Blueprint('api', __name__, template_folder='templates')
@api.route('/new_user', methods=["GET","POST"])
[docs]def new_user():
'''
Create new user in the database
:param: username
:param: password
:param: first_name
:param: email
:param: city
:param: country
:param: state
'''
log.info(":API:/api/new_user")
response = {"type": None, "data": {}, "text": None}
try:
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
username = str(request_data["username"])
log.debug("Username is {0}".format(username))
password = str(request_data["password"])
first_name = str(request_data["first_name"])
last_name = str(request_data["last_name"])
email = str(request_data["email"])
city = str(request_data["city"])
country = str(request_data["country"])
state = str(request_data["state"])
check_list = [username, password, first_name, last_name, email, city, country, state]
passed = tools.check_string(check_list)
if passed:
log.debug("Attempting to create new user with username {0} and email {1}".format(username, email))
# Check to see if the username exists
users = db["users"]
if users.find_one(username=username):
# If that username is already taken
taken_message = "Username {0} is already taken".format(username)
log.debug(taken_message)
response["type"] = "error"
response["text"] = taken_message
else:
# Add the new user to the database
log.info(":{0}:Adding a new user to the database".format(username))
db.begin()
# Hash the password
log.debug("Hashing password")
hashed = bcrypt.hashpw(password.encode('utf8'), bcrypt.gensalt())
log.debug("Hashed password is {0}".format(hashed))
is_admin = username in configuration_data["admins"]
try:
db['users'].insert({
"username": username,
"first_name": first_name,
"last_name": last_name,
"email": email,
"password": hashed,
"admin": is_admin,
"default_plugin": "search",
"notifications": json.dumps(["email"]),
"ip": request.environ.get('HTTP_X_REAL_IP', request.remote_addr),
"news_site": "http://reuters.com",
"city": city,
"country": country,
"state": state,
"temp_unit": "fahrenheit",
"timezone": whenareyou(city)
})
db.commit()
response["type"] = "success"
response["text"] = "Thank you {0}, you are now registered for W.I.L.L".format(first_name)
except:
db.rollback()
response["type"] = "error"
response["text"] = "There was an error in signing you up for W.I.L.L. Please check the information you entered"
else:
log.warning(":{0}:Failed SQL evaluation".format(username))
response["type"] = "error"
response["text"] = "Invalid input, valid chars are {0}".format(tools.valid_chars)
except KeyError:
log.error("Needed data not found in new user request")
response["type"] = "error"
response["text"] = "Couldn't find required data in request. " \
"To create a new user, a username, password, first name, last name," \
"and email is required"
return tools.return_json(response)
@api.route("/settings", methods=["POST"])
[docs]def settings():
"""
:param username:
:param password:
:param Optional - setting to be changed:
Change the users settings
:return:
"""
log.info(":API:/api/settings")
response = {"type": None, "text": None, "data": {}}
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
if "username" in request_data.keys() and "password" in request_data.keys():
username = request_data["username"]
password = request_data["password"]
if tools.check_string(request_data.values()):
user_table = db["users"].find_one(username=username)
if user_table:
db_hash = user_table["password"]
if bcrypt.checkpw(password.encode('utf8'), db_hash.encode('utf8')):
#TODO: write a framework that allows changing of notifications
immutable_settings = ["username", "admin", "id", "user_token", "notifications", "password"]
db.begin()
log.info(":{0}:Changing settings for user".format(username))
try:
for setting in request_data.keys():
if setting not in immutable_settings:
db["users"].upsert({"username": username, setting: request.form[setting]}, ['username'])
db.commit()
response["type"] = "success"
response["text"] = "Updated settings"
except Exception as db_error:
log.debug("Exception {0}, {1} occurred while trying to commit changes to the database".format(
db_error.message, db_error.args
))
response["type"] = "error"
response["text"] = "Error encountered while trying to update db, changes not committed"
db.rollback()
else:
response["type"] = "error"
response["text"] = "User {0} doesn't exist".format(username)
else:
response["type"] = "error"
response["text"] = "Invalid input"
else:
response["type"] = "error"
response["text"] = "Couldn't find username or password in request data"
return tools.return_json(response)
@api.route('/get_sessions', methods=["GET", "POST"])
[docs]def get_sessions():
"""
Return list of active sessions for user
:param: username
:param: password
:return: list of sessions
"""
log.info(":API:/api/get_sessions")
response = {"type": None, "data": {}, "text": None}
sessions = core.sessions
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
try:
username = request_data["username"]
password = request_data["password"]
if tools.check_string(request_data.values()):
db_hash = db['users'].find_one(username=username)["password"]
user_auth = bcrypt.checkpw(password.encode('utf8'), db_hash.encode('utf8'))
if user_auth:
response["data"].update({"sessions":[]})
for user_session in sessions:
if sessions[user_session]["username"] == username:
response["data"]["sessions"].append(session)
response["type"] = "success"
response["text"] = "Fetched active sessions"
else:
response["type"] = "error"
response["text"] = "Invalid username/password combination"
else:
response["type"] = "error"
response["text"] = "One of the submitted parameters contained an invalid character. " \
"Valid characters are {0}".format(tools.valid_chars)
except KeyError:
response["type"] = "error"
response["text"] = "Couldn't find username and password in request"
return tools.return_json(response)
@api.route('/start_session', methods=["GET","POST"])
[docs]def start_session():
'''
:param: username
:param: password
Generate a session id and start a new session
:return:
'''
log.info(":API:/api/start_session")
# Check the information that the user has submitted
response = {"type": None, "data": {}, "text": None}
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
try:
if request.method == "POST":
username = request_data["username"]
password = request_data["password"]
client = "API-POST"
elif request.method == "GET":
username = request.args.get("username", "")
password = request.args.get("password", "")
client = "API-GET"
if not (username and password):
raise KeyError()
if tools.check_string([username, password]):
log.info(":{0}:Checking password".format(username))
users = db["users"]
user_data = users.find_one(username=username)
if user_data:
user_data = db["users"].find_one(username=username)
# Check the password
db_hash = user_data["password"]
user_auth = bcrypt.checkpw(password.encode('utf8'), db_hash.encode('utf8'))
if user_auth:
log.info(":{0}:Authentication successful".format(username))
# Return the session id to the user
session_id = tools.gen_session(username, client, db)
if session_id:
response["type"] = "success"
response["text"] = "Authentication successful"
response["data"].update({"session_id": session_id})
else:
response["type"] = "error"
response["text"] = "Invalid username/password"
else:
response["type"] = "error"
response["text"] = "Couldn't find user with username {0}".format(username)
else:
response["type"] = "error"
response["text"] = "Invalid input"
except KeyError:
response["type"] = "error"
response["text"] = "Couldn't find username and password in request data"
# Render the response as json
if request.method == "GET":
session.update({"session_data": response})
if response["type"] == "success":
return redirect("/")
log.debug("Rendering command template")
return render_template("command.html")
else:
return tools.return_json(response)
@api.route('/end_session', methods=["GET", "POST"])
[docs]def end_session():
"""
End a session
:param session_id:
:return End the session:
"""
log.info(":API:/api/end_session")
response = {"type": None, "data": {}, "text": None}
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
try:
session_id = request_data["session_id"]
# Check for the session id in the core.sessions dictionary
if session_id in core.sessions.keys():
log.info(":{0}:Ending session".format(session_id))
del core.sessions[session_id]
response["type"] = "success"
response["text"] = "Ended session"
else:
response["type"] = "error"
response["text"] = "Session id {0} wasn't found in core.sessions".format(session_id)
except KeyError:
response["type"] = "error"
response["text"] = "Couldn't find session id in request data"
# Render the response as json
return tools.return_json(response)
@api.route('/check_session', methods=["GET", "POST"])
[docs]def check_session():
"""
Check if a session is valid
:param: session_id
:return:
"""
log.info(":API:/api/check_session")
response = {"type": None, "text": None, "data": {}}
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
try:
session_id = request_data["session_id"]
session_valid = (session_id in core.sessions.keys())
response["data"].update({"valid": session_valid})
response["type"] = "success"
if tools.check_string(session_id):
if session_valid:
response["text"] = "Session id {0} is valid".format(session_id)
else:
response["text"] = "Session id {0} is invalid".format(session_id)
else:
response["type"] = "error"
response["text"] = "Invalid input"
except KeyError:
response["type"] = "error"
response["text"] = "Couldn't find session_id in request data"
response["data"].update({"valid": False})
return tools.return_json(response)
@api.route('/respond', methods=["GET", "POST"])
[docs]def command_response():
"""
Api path for responding to a command question
:param session_id:
:param command_id:
:return:
"""
log.info(":API:/api/respond")
response = {"type": None, "text": None, "data": {}}
if request.is_json:
request_data = request.get_json()
try:
log.debug(request_data.keys())
command_id = request_data["command_id"]
session_id = request_data["session_id"]
response_value = request_data["value"]
#Validate the JSON response object
if tools.check_string([command_id, session_id]):
if session_id in core.sessions.keys():
session_data = core.sessions[session_id]
session_commands = session_data["commands"]
response_command = None
for command_obj in session_commands:
if command_obj["id"] == command_id:
response_command = command_obj
if response_command:
if "function" in response_command.keys() and "event" in response_command.keys():
response_function = response_command["function"]
log.info(":{0}: Executing response function {1} with response {2}".format(
command_id, response_function, response_value
))
#Execute the response
try:
response_result = response_function(response_value, response_command["event"])
log.info(":{0}:Successfully executed response, returning {1}".format(
session_id, tools.fold(response_result)
))
response = response_result
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
error_string = repr(traceback.format_exception(exc_type, exc_value,
exc_traceback))
log.error(error_string)
username = session_data["username"]
user_table = db["users"].find_one(username=username)
if user_table:
response["type"] = "error"
if user_table["admin"]:
response["text"] = error_string
else:
response["text"] = "An error has occurred while trying to fetch a response." \
"Please contact will@willbeddow.com to report the error and " \
"get more information"
else:
log.error("USER {0} NOT FOUND IN DATABASE. WARNING.".format(username))
response["type"] = "error"
response["text"] = "A database error has occurred. Please contact will@willbeddow.com" \
"to report the error, along with the circumstances under which it" \
"occurred."
else:
response["type"] = "error"
response["text"] = "Command {0} didn't register for a response or didn't" \
" register the required data for a response.".format(command_id)
else:
response["type"] = "error"
response["text"] = "Couldn't find a command object in session {0} with command id {1}".format(
session_id, command_id
)
else:
response["type"] = "error"
response["text"] = "Invalid session id {0}".format(session_id)
else:
response["type"] = "error"
response["text"] = "Submitted response data {0} failed string validation. Valid characters are {0}".format(
tools.valid_chars
)
except KeyError:
response["type"] = "error"
response["text"] = "command_id, session_id, and JSON response object required"
else:
response["type"] = "error"
response["text"] = "/api/respond requires a JSON request"
return tools.return_json(response)
@api.route('/command', methods=["GET", "POST"])
[docs]def process_command():
"""
Api path for processing a command
:param command:
:param session_id:
:return response object:
"""
log.info(":API:/api/command")
response = {"type": None, "data": {}, "text": None}
if request.is_json:
request_data = request.get_json()
else:
request_data = request.form
try:
command = request_data["command"]
session_id = request_data["session_id"]
log.debug(":{1}:Processing command {0}".format(command, session_id))
if session_id in core.sessions.keys():
# Add the command to the core.sessions command queue
session_data = core.sessions[session_id]
log.info(":{1}:Adding command {0} to the command queue".format(command, session_id))
command_data = tools.create_command_obj(session_id, command)
command_response = core.sessions_monitor.command(
command_data, core.sessions[session_id], db, add_to_updates_queue=False
)
if session_id in core.commands.keys():
core.commands[session_id].append(command_data)
else:
core.commands.update({session_id: [command_data]})
session_data["commands"].append(command_data)
core.commands.update(command_data)
log.info(":{0}:Returning command response {1}".format(session_id, tools.fold(str(command_response))))
response = command_response
else:
log.info(":{0}:Couldn't find session id in sessions".format(session_id))
response["type"] = "error"
response["text"] = "Invalid session id"
except KeyError:
log.debug("Couldn't find session id and command in request data")
response["type"] = "error"
response["text"] = "Couldn't find session id and command in request data"
return tools.return_json(response)