diff --git a/solvable/Dockerfile b/solvable/Dockerfile index d021b1a..610ec66 100644 --- a/solvable/Dockerfile +++ b/solvable/Dockerfile @@ -1,19 +1,26 @@ FROM eu.gcr.io/avatao-challengestore/tutorial-framework +# Define variables to use later ENV TFW_SERVER_DIR="/srv/.tfw" \ - TFW_LOGIN_APP_DIR="/tmp/source_code_server" \ - TFW_IDE_WD="/home/${AVATAO_USER}/workdir" \ + TFW_LOGIN_SERVICE_DIR="/srv/login_service" \ + TFW_IDE_WD="/home/${AVATAO_USER}/workdir" \ TFW_TERMINADO_WD="/home/${AVATAO_USER}/workdir" +# Copy TFW related stuff to a dedicated directory COPY solvable/src ${TFW_SERVER_DIR}/ -COPY solvable/src/source_code_server/server.py ${TFW_LOGIN_APP_DIR}/ -COPY solvable/src/source_code_server/users.db ${TFW_LOGIN_APP_DIR}/ -COPY solvable/src/source_code_server/login_component.py ${TFW_IDE_WD}/ -RUN chown -R ${AVATAO_USER} ${TFW_IDE_WD} &&\ - chmod -R 755 ${TFW_IDE_WD} &&\ - chown -R root ${TFW_SERVER_DIR} &&\ - chmod -R 700 ${TFW_SERVER_DIR} +# Copy webservice to a dedicated directory +COPY solvable/src/webservice/ ${TFW_LOGIN_SERVICE_DIR}/ +ADD solvable/src/webservice/frontend-deps.tar ${TFW_LOGIN_SERVICE_DIR}/static +# Create IDE directory, add a file to it and give proper permissions to AVATAO_USER +RUN mkdir -p ${TFW_IDE_WD} &&\ + echo "This is a file in ${TFW_IDE_WD}" > ${TFW_IDE_WD}/text.txt &&\ + chown -R ${AVATAO_USER}: ${TFW_IDE_WD} && chmod -R 755 ${TFW_IDE_WD} + +# Hide TFW related code from user +RUN chown -R root:root ${TFW_SERVER_DIR} && chmod -R 700 ${TFW_SERVER_DIR} + +# Make AVATAO_USER's home writeable and set it as WORKDIR VOLUME ["/home/${AVATAO_USER}"] WORKDIR /home/${AVATAO_USER} diff --git a/solvable/nginx/login.conf b/solvable/nginx/login.conf deleted file mode 100644 index aaf994b..0000000 --- a/solvable/nginx/login.conf +++ /dev/null @@ -1,3 +0,0 @@ -location = /login { - proxy_pass http://127.0.0.1:${TFW_LOGIN_APP_PORT}; -} diff --git a/solvable/nginx/webservice.conf b/solvable/nginx/webservice.conf new file mode 100644 index 0000000..a0ea11e --- /dev/null +++ b/solvable/nginx/webservice.conf @@ -0,0 +1,3 @@ +location /webservice/ { + proxy_pass http://127.0.0.1:6666/; +} diff --git a/solvable/src/source_code_server/login_component.py b/solvable/src/source_code_server/login_component.py deleted file mode 100644 index 3437d42..0000000 --- a/solvable/src/source_code_server/login_component.py +++ /dev/null @@ -1,27 +0,0 @@ -import sqlite3 - - -def get_db(): - return sqlite3.connect('users.db') - - -def authorize_login(email, password): - """ - This method checks if a user is authorized and has admin privileges. - :param email: The email address of the user. - :param password: The password of the user. - :return: A tuple, the first element is the email address if the user exists, - and None if they don't; the second element is a boolean, which is True if - the user has admin privileges. - """ - conn = get_db() - sql_statement = '''SELECT email, is_admin FROM users - WHERE email="{}" AND password="{}"''' - # The problem with this approach is that it substitutes any value received - # from the user, even if it is a valid SQL statement! - result = conn.execute(sql_statement.format(email, password)).fetchone() - if result is None: - return None, False - else: - email, is_admin = result - return email, is_admin == 1 diff --git a/solvable/src/source_code_server/server.py b/solvable/src/source_code_server/server.py deleted file mode 100644 index ddeb81c..0000000 --- a/solvable/src/source_code_server/server.py +++ /dev/null @@ -1,27 +0,0 @@ -import json, sys -from tornado.ioloop import IOLoop -from tornado.web import RequestHandler, Application - -from tfw.config import TFWENV - -sys.path.append(TFWENV.IDE_WD) -from login_component import authorize_login - - -class LoginHandler(RequestHandler): - def post(self, *args, **kwargs): - request = json.loads(self.request.body) - email, is_admin = authorize_login( - request['email'], - request['password'] - ) - self.write({ - 'email': email, - 'is_admin': is_admin - }) - - -if __name__ == '__main__': - application = Application([(r'/login', LoginHandler)]) - application.listen(TFWENV.LOGIN_APP_PORT) - IOLoop.instance().start() diff --git a/solvable/src/source_code_server/users.db b/solvable/src/source_code_server/users.db deleted file mode 100644 index cde24ab..0000000 Binary files a/solvable/src/source_code_server/users.db and /dev/null differ diff --git a/solvable/src/webservice/frontend-deps.tar b/solvable/src/webservice/frontend-deps.tar new file mode 100644 index 0000000..4d3f79a Binary files /dev/null and b/solvable/src/webservice/frontend-deps.tar differ diff --git a/solvable/src/webservice/server.py b/solvable/src/webservice/server.py new file mode 100644 index 0000000..c30e75c --- /dev/null +++ b/solvable/src/webservice/server.py @@ -0,0 +1,120 @@ +from os import urandom, getenv +from os.path import exists, join, dirname, realpath +from hashlib import sha512 + +import sqlite3 +from flask import Flask, render_template, request, g, session, url_for + + +def setup_db(filename): + connection = sqlite3.connect(filename) + cur = connection.cursor() + cur.execute('''CREATE TABLE users + ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL, + pwhash TEXT NOT NULL + ) + ''') + connection.commit() + connection.close() + + +BASEURL = getenv('BASEURL', '') +DBFILE = join(dirname(realpath(__file__)), '.db.db') +SALT = 'justsomerandombytes'.encode() +if not exists(DBFILE): + setup_db(DBFILE) +app = Flask(__name__) +app.secret_key = urandom(32) + + +def get_url(endpoint): + return f'{BASEURL}{url_for(endpoint)}' +app.jinja_env.globals.update(get_url=get_url) + + +@app.before_request +def init_database(): + g.db = sqlite3.connect(DBFILE) + g.db.row_factory = sqlite3.Row + + +@app.teardown_request +def close_database(exception): + db = getattr(g, 'db', None) + if db is not None: + db.close() + + +@app.route('/', methods=['GET', 'POST']) +def index(): + if request.method == 'POST': + cur = g.db.cursor() + cur.execute('SELECT * FROM users WHERE username=? AND pwhash=?', + [request.form['username'], sha512(request.form['password'].encode()+SALT).hexdigest()]) + query = cur.fetchone() + if not query: + return render_template('login.html', alert='Invalid credentials!') + else: + session['logged_in'] = True + session['username'] = request.form['username'] + return render_template('internal.html') + + if session.get('logged_in'): + return render_template('internal.html') + return render_template('login.html') + + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if request.method == 'POST': + cur = g.db.cursor() + + if not request.form['username'] or not request.form['password'] or not request.form['passwordconfirm']: + return render_template('register.html', alert='You need to fill everything.') + if request.form['password'] != request.form['passwordconfirm']: + return render_template('register.html', alert='Passwords do not match! Please try again.') + + cur.execute('SELECT * FROM users WHERE username=?', [request.form['username']]) + if cur.fetchone() is not None: + return render_template('register.html', alert='Username already in use.') + + cur.execute('INSERT INTO users(username, pwhash) VALUES(?,?)', + [request.form['username'], + sha512(request.form['password'].encode()+SALT).hexdigest()]) + g.db.commit() + + return render_template('login.html', success='Account "{}" successfully registered. You can log in now!'.format(request.form['username'])) + + return render_template('register.html') + + +@app.route('/logout') +def logout(): + try: + session.pop('logged_in') + session.pop('username') + except KeyError: + pass + return render_template('login.html') + + +@app.errorhandler(401) +@app.errorhandler(404) +@app.route('/error') +def error(error): + return render_template('error.html', error=error), error.code + + +# 500 needs a separate handler, as Flask would print +# the actual piece of code that caused the exception +# for some bizarre reason +@app.errorhandler(500) +@app.route('/error') +def servererror(error): + return render_template('error.html', error='500: Internal server error'), 500 + + +if __name__ == '__main__': + app.run(host='127.0.0.1', debug=False, port=6666) diff --git a/solvable/src/webservice/templates/base.html b/solvable/src/webservice/templates/base.html new file mode 100644 index 0000000..76da6ae --- /dev/null +++ b/solvable/src/webservice/templates/base.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + +
+

SomeSite

+

We provide stuff!

+
+
+
+
+ {% if alert or success %} +
+ × +
{{alert}}{{success}}
+
+ {% endif %} + {% block content %} + {% endblock %} +
+
+
+ + + diff --git a/solvable/src/webservice/templates/error.html b/solvable/src/webservice/templates/error.html new file mode 100644 index 0000000..d46a43b --- /dev/null +++ b/solvable/src/webservice/templates/error.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} + +{% block content %} +

Oops, something went wrong! :(

+

{{error}}

+{% endblock %} diff --git a/solvable/src/webservice/templates/internal.html b/solvable/src/webservice/templates/internal.html new file mode 100644 index 0000000..a79d981 --- /dev/null +++ b/solvable/src/webservice/templates/internal.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} +

Welcome, {{session.username}}

+

You have successfully logged in

+ + Logout + +{% endblock %} diff --git a/solvable/src/webservice/templates/login.html b/solvable/src/webservice/templates/login.html new file mode 100644 index 0000000..c09cb1c --- /dev/null +++ b/solvable/src/webservice/templates/login.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + + + + + +{% block content %} +

Login

+
+
+ + +
+
+ + +
+
Not a member yet? Register here.
+ +
+{% endblock %} diff --git a/solvable/src/webservice/templates/register.html b/solvable/src/webservice/templates/register.html new file mode 100644 index 0000000..445f90e --- /dev/null +++ b/solvable/src/webservice/templates/register.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +

You can register using this form.

+
+
+ + +
+
+ + +
+
+ + +
+ +
+{% endblock %} diff --git a/solvable/supervisor/login.conf b/solvable/supervisor/login.conf deleted file mode 100644 index cf8ab04..0000000 --- a/solvable/supervisor/login.conf +++ /dev/null @@ -1,4 +0,0 @@ -[program:login] -directory=%(ENV_TFW_LOGIN_APP_DIR)s -command=python3 server.py -autostart=false diff --git a/solvable/supervisor/webservice.conf b/solvable/supervisor/webservice.conf new file mode 100644 index 0000000..e7dbf2c --- /dev/null +++ b/solvable/supervisor/webservice.conf @@ -0,0 +1,5 @@ +[program:webservice] +directory=%(ENV_TFW_LOGIN_SERVICE_DIR)s +environment=BASEURL="/webservice" +command=python3 server.py +autostart=true