From 36cf666a0c8ea32c702338e2931dc109859269a7 Mon Sep 17 00:00:00 2001 From: nico Date: Sat, 18 Apr 2020 23:47:15 +0200 Subject: Initial release JoplinWebApi Joplin WebAPI to manage Joplin users and the directory structure + add user creation endpoint + add user deletion endpoint + add user password change endpoint + add README and highlight css + add gitignore file + add uwsgi template file --- app.py | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 app.py (limited to 'app.py') diff --git a/app.py b/app.py new file mode 100644 index 0000000..decd9df --- /dev/null +++ b/app.py @@ -0,0 +1,172 @@ +import shutil +from pathlib import Path + +import flask +import markdown +from flask_caching import Cache +from flask_htpasswd import HtPasswdAuth +from flask_restful import reqparse, Api, Resource + +from config import Config + +app = flask.Flask(__name__) +api = Api(app) + +# app config +if app.config["ENV"] == "development": + app.config.from_object("config.DevelopConfig") +else: + app.config.from_object(Config) + +cache = Cache(app) +htpasswd = HtPasswdAuth(app) +htpasswd.users.autosave = True +parser = reqparse.RequestParser() + +messages = { + 'OK': { + 'status': 200, + 'message': 'OK' + }, + 'Created': { + 'status': 201, + 'message': "User creation succeeded" + }, + 'Unauthorized Request': { + 'status': 401, + 'message': 'Unauthorized Request', + }, + 'Unprocessable Entity':{ + 'status': 422, + 'message': 'Missing parameter' + }, + 'Conflict': { + 'status': 409, + 'message': 'Username conflict' + }, + 'InternalServerError': { + 'status': 500, + 'message': 'Something went wrong, please contact the administrator.' + } +} + + +@app.route('/joplin/') +@cache.cached(timeout=21600) +def index(): + # codehilite css + with open(Path(app.root_path).joinpath('./static/hilite.css'), 'r') as hilite_file: + css = ''.format(css=hilite_file.read()) + + # Open the README file + with open(Path(app.root_path).joinpath('./static/Readme.md'), 'r') as markdown_file: + + # Read the content of the file + content = markdown_file.read() + + html = ''.join([css, content]) + + # render finished HTML + return flask.Response(markdown.markdown(html, extensions=["fenced_code", "codehilite"]), status=200) + + +@app.route('/joplin/auth-test') +@htpasswd.required +def auth_test(user): + return 'Hello {user}'.format(user=user) + + +class NewUser(Resource): + """ + class to create new htpasswd user entry + """ + def post(self, username): + parser.add_argument('password') + parser.add_argument('invite') + + args = parser.parse_args() + password = args['password'] + invitecode = args['invite'] + path = app.config['JOPLIN_DIR'] + + # break early + if invitecode != app.config['INVITE_CODE']: + return flask.jsonify(messages['Unauthorized Request']) + + if None in [password, invitecode]: + return flask.jsonify(messages['Unprocessable Entity']) + + if username not in htpasswd.users.users(): + # firstly try to create the folder to break if permissions aren't correct + try: + Path.mkdir(Path(path).joinpath('./%s' % username), mode=0o750, exist_ok=True) + + except OSError: + return flask.jsonify(messages['InternalServerError']) + # create user entry + htpasswd.users.set_password(username, password) + + return flask.jsonify(messages['Created']) + else: + return flask.jsonify(messages['Conflict']) + + +class ChangePW(Resource): + """ + class to update a users password + """ + def post(self, username): + parser.add_argument('password') + parser.add_argument('new_password') + + args = parser.parse_args() + password = args['password'] + new_password = args['new_password'] + + if None in [password, new_password]: + return flask.jsonify(messages['Unprocessable Entity']) + + # check_password return False if password mismatch and None if no user is found + if htpasswd.users.check_password(username, password): + htpasswd.users.update(username, new_password) + + return flask.jsonify(messages['OK']) + else: + return flask.jsonify(messages['Unauthorized Request']) + + +class DelUser(Resource): + """ + class to delete a user + """ + def delete(self, username): + parser.add_argument('password') + + args = parser.parse_args() + password = args['password'] + + if password is None: + return flask.jsonify(messages['Unprocessable Entity']) + + # check_password return False if password mismatch and None if no user is found + if htpasswd.users.check_password(username, password): + htpasswd.users.delete(username) + + try: + # remove users files recursively + shutil.rmtree(Path(app.config['JOPLIN_DIR']).joinpath('./%s' % username)) + except FileNotFoundError: + pass + + return flask.Response(flask.jsonify([]), status=204) + else: + return flask.jsonify(messages['Unauthorized Request']) + + +api.add_resource(NewUser, '/joplin//create') +api.add_resource(ChangePW, '/joplin//changepw') +api.add_resource(DelUser, '/joplin/') + + +if __name__ == '__main__': + app.run() -- cgit v1.2.3-18-g5258