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()