450 lines
17 KiB
Python
450 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import flask
|
|
import flask_login as fll
|
|
import datetime
|
|
from db import db, File, User, levels_users
|
|
import settings
|
|
from werkzeug.utils import secure_filename
|
|
import os
|
|
import os.path
|
|
_here = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
login_manager = fll.LoginManager()
|
|
|
|
app = flask.Flask(__name__)
|
|
app.config.from_pyfile('settings.cfg')
|
|
|
|
app.config['UPLOAD_FOLDER'] = settings.UPLOAD_FOLDER
|
|
app.config['MAX_CONTENT_LENGTH'] = settings.MAX_FILE_SIZE * 1024 * 1024
|
|
|
|
#######################################################################
|
|
# Plugin management #
|
|
#######################################################################
|
|
|
|
# Login manager
|
|
login_manager.init_app(app)
|
|
|
|
# Database management
|
|
db.app = app
|
|
db.init_app(app)
|
|
db.create_all()
|
|
db.session.commit()
|
|
|
|
# Insert admin if not exist
|
|
if User.query.limit(1).first() is None:
|
|
admin = User(login='admin', name='Admin', is_admin=levels_users['Admin'])
|
|
admin.set_password('admin')
|
|
db.session.add(admin)
|
|
db.session.commit()
|
|
|
|
# Login management
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
if user_id is None :
|
|
return None
|
|
return User.query.get(user_id)
|
|
|
|
@login_manager.unauthorized_handler
|
|
def unauthorized_callback():
|
|
return flask.redirect('/login?next=' + flask.request.path)
|
|
|
|
|
|
#######################################################################
|
|
# Global variables or fuctions #
|
|
#######################################################################
|
|
URL_DEFAULT = '/myrepo'
|
|
|
|
@app.route('/')
|
|
def home():
|
|
user = fll.current_user
|
|
if not hasattr(user, 'is_admin'):
|
|
user = None
|
|
return flask.render_template("index.html", user=user)
|
|
|
|
@app.route('/favicon.ico')
|
|
def favicon():
|
|
return flask.redirect('/static/favicon.ico')
|
|
#######################################################################
|
|
# Login, logout #
|
|
#######################################################################
|
|
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
if fll.current_user.is_authenticated:
|
|
return flask.redirect(URL_DEFAULT)
|
|
if flask.request.method=="POST":
|
|
username = flask.request.form['username']
|
|
password = flask.request.form['password']
|
|
user = User.query.get(username)
|
|
if user and user.is_admin > levels_users['Inactive'] and user.check_password(password):
|
|
fll.login_user(user)
|
|
next = flask.request.args.get('next')
|
|
if next is not None and next[0]=='/':
|
|
return flask.redirect(next)
|
|
else:
|
|
return flask.redirect(URL_DEFAULT)
|
|
else:
|
|
app.logger.warning('Login fail for user {} from {}'.format(user, flask.request.remote_addr))
|
|
flask.flash('Nom d\'utilisateur ou mot de passe incorrect')
|
|
return flask.render_template('login.html', user=None)
|
|
|
|
@app.route('/logout')
|
|
def logout():
|
|
fll.logout_user()
|
|
return flask.redirect('/')
|
|
|
|
#######################################################################
|
|
# Upload form #
|
|
#######################################################################
|
|
|
|
def check_filename_in_db(filename, user):
|
|
exist = File.query.filter_by(name=filename, user=user.login).first()
|
|
return exist is not None
|
|
|
|
def check_filename(filename, user):
|
|
filepath = genpath(filename, user)
|
|
if os.path.lexists(filepath) or check_filename_in_db(filename, user):
|
|
fn, ext = os.path.splitext(filename)
|
|
i = 0
|
|
if settings.NMAX_EXTENSION == 0:
|
|
return None
|
|
filename = '{}_{:04d}{}'.format(fn, i, ext)
|
|
while os.path.lexists(genpath(filename, user)) or check_filename_in_db(filename, user):
|
|
i+=1
|
|
filename = '{}_{:04d}{}'.format(fn, i, ext)
|
|
if i > settings.NMAX_EXTENSION:
|
|
break
|
|
else:
|
|
return filename
|
|
return None
|
|
return filename
|
|
|
|
def genpath(filename, user):
|
|
if isinstance(user, str):
|
|
username = user
|
|
else:
|
|
username = user.login
|
|
return os.path.join(app.config['UPLOAD_FOLDER'], username, filename)
|
|
|
|
@app.route('/up', methods=['GET', 'POST'])
|
|
def uploaderpage():
|
|
user = fll.current_user
|
|
if not hasattr(user, 'is_admin') or user.is_admin < levels_users['Simple user']:
|
|
user = None
|
|
|
|
if not flask.request.method=="POST":
|
|
return flask.render_template('up.html', user=user)
|
|
|
|
if flask.request.method=="POST" :
|
|
# Parse global things
|
|
if 'graphical' in flask.request.form and flask.request.form['graphical'] == '1':
|
|
graphical = True
|
|
else:
|
|
graphical = False
|
|
if 'visible' in flask.request.form and flask.request.form['visible']:
|
|
visible = True
|
|
else:
|
|
visible = False
|
|
if 'filepwd' in flask.request.form and len(flask.request.form['filepwd'])>0:
|
|
filepwd = flask.request.form['filepwd']
|
|
else:
|
|
filepwd = None
|
|
|
|
# Check auth first
|
|
if user is None:
|
|
if 'username' not in flask.request.form or 'password' not in flask.request.form:
|
|
return returnuploader(graphical, 'ERR: You have to provide creedentials or log in', None)
|
|
username = flask.request.form['username']
|
|
password = flask.request.form['password']
|
|
user = User.query.get(username)
|
|
if not user or user.is_admin <= levels_users['Inactive'] or not user.check_password(password):
|
|
return returnuploader(graphical, 'ERR: Incorrect login credentials', None)
|
|
fll.login_user(user)
|
|
|
|
# When auth ok, check for files !
|
|
if 'file[]' not in flask.request.files:
|
|
return returnuploader(graphical, 'ERR: File have to be provided (file[] field)', user)
|
|
|
|
lfiles = flask.request.files.getlist('file[]')
|
|
lnewnames = flask.request.form.getlist('filename[]')
|
|
ldesc = flask.request.form.getlist('desc[]')
|
|
|
|
done = 0
|
|
ret="OK"
|
|
for i in range(len(lfiles)):
|
|
# if user does not select file, browser also
|
|
# submit an empty part without filename
|
|
f = lfiles[i]
|
|
if f.filename == '':
|
|
continue
|
|
if i<len(lnewnames) and len(lnewnames[i])>0:
|
|
name = lnewnames[i]
|
|
else:
|
|
name = f.filename
|
|
if i<len(ldesc) and len(ldesc[i])>0:
|
|
desc = ldesc[i]
|
|
else:
|
|
desc = ''
|
|
name = secure_filename(name)
|
|
name1 = check_filename(name, user)
|
|
if name1 is None:
|
|
return returnuploader(graphical, 'ERR: Already existing name {}'.format(name), user)
|
|
|
|
# Add to database
|
|
toadd = File(name=name1, user=user.login, visible=visible, date=datetime.datetime.now(), desc=desc)
|
|
toadd.set_password(filepwd)
|
|
db.session.add(toadd)
|
|
db.session.commit()
|
|
|
|
# Save it to disk
|
|
path = genpath(name1, user)
|
|
try:
|
|
if not os.path.isdir(os.path.dirname(path)):
|
|
os.mkdir(os.path.dirname(path))
|
|
f.save(path)
|
|
ret += "\n/d/{}/{}".format(user.login, name1)
|
|
except IOError as err:
|
|
return returnuploader(graphical, 'ERR: Something went wrong when trying to write file to disk', user)
|
|
|
|
done += 1
|
|
|
|
# return
|
|
if done == 0:
|
|
return returnuploader(graphical, 'ERR: You have to provide at least one valid file', user)
|
|
|
|
if graphical:
|
|
return flask.redirect(URL_DEFAULT)
|
|
else:
|
|
return ret
|
|
|
|
def returnuploader(graphical, info, user):
|
|
"""
|
|
Function to create return value for uploaderpage function
|
|
If graphical is True, return the web page, otherwise return simple text
|
|
|
|
graphical:bool: it is graphical session ?
|
|
info:str: text to send
|
|
user:User instance or None if not logged in
|
|
"""
|
|
if not graphical:
|
|
return info
|
|
else:
|
|
flask.flash(info)
|
|
return flask.render_template('up.html', user=user)
|
|
|
|
#######################################################################
|
|
# Download pages #
|
|
#######################################################################
|
|
@app.route('/d/<user>/<filename>', defaults={'raw': False}, methods=['GET', 'POST'])
|
|
@app.route('/d/<user>/<filename>/raw', defaults = {'raw': True}, methods=['GET', 'POST'])
|
|
def dl(user, filename, raw=False):
|
|
cuser = fll.current_user
|
|
if not hasattr(cuser, 'is_admin') or cuser.is_admin < levels_users['Simple user']:
|
|
cuser = None
|
|
fl = File.query.filter_by(name=filename, user=user).first()
|
|
if fl is None:
|
|
return flask.abort(404)
|
|
pwd = None
|
|
if flask.request.method=="POST":
|
|
if 'password' in flask.request.form:
|
|
pwd = flask.request.form['password']
|
|
if fl.check_password(pwd):
|
|
path = genpath(filename, user)
|
|
folder = os.path.dirname(path)
|
|
fn = os.path.basename(path)
|
|
if raw:
|
|
return flask.send_from_directory(folder, filename=fn, as_attachment=False)
|
|
else:
|
|
return flask.send_from_directory(folder, filename=fn, as_attachment=False)
|
|
else:
|
|
if raw:
|
|
return 'ERR: Incorrect password'
|
|
else:
|
|
if flask.request.method == "POST":
|
|
flask.flash('Incorrect password')
|
|
return flask.render_template('dl_pwd.html', user=cuser, filename=filename)
|
|
pass
|
|
|
|
#######################################################################
|
|
# Show repos #
|
|
#######################################################################
|
|
@app.route('/myrepo')
|
|
@fll.login_required
|
|
def myrepo():
|
|
return flask.redirect('/d/{}'.format(fll.current_user.login))
|
|
|
|
@app.route('/d/<user>')
|
|
def repo(user):
|
|
user = User.query.get(user)
|
|
if user is None:
|
|
return flask.abort(404)
|
|
cuser = fll.current_user
|
|
if not hasattr(cuser, 'is_admin') or cuser.is_admin < levels_users['Simple user']:
|
|
cuser = None
|
|
|
|
ismine = user.login == cuser.login if cuser is not None else False
|
|
isadmin = cuser.is_admin>=levels_users['Admin'] or ismine if cuser is not None else False
|
|
|
|
if isadmin:
|
|
l = File.query.filter_by(user=user.login).all()
|
|
else:
|
|
l = File.query.filter_by(user=user.login, visible=True).all()
|
|
|
|
return flask.render_template('repo.html', user=cuser, isadmin=isadmin, ismine=ismine, folder=user.login, l=l)
|
|
|
|
#######################################################################
|
|
# Edit files #
|
|
#######################################################################
|
|
@app.route('/f/<id>/edit', methods=['GET', 'POST'])
|
|
@fll.login_required
|
|
def editfile(id):
|
|
user = fll.current_user
|
|
f = File.query.get(id)
|
|
if f is None:
|
|
return flask.redirect(URL_DEFAULT)
|
|
if f.user != user and user.is_admin<levels_users['Admin']:
|
|
return flask.redirect(URL_DEFAULT)
|
|
|
|
if flask.request.method == "POST":
|
|
if 'desc' in flask.request.form:
|
|
f.desc = flask.request.form['desc']
|
|
f.visible = False
|
|
if 'visible' in flask.request.form:
|
|
if flask.request.form['visible']=='1':
|
|
f.visible = True
|
|
if 'password' in flask.request.form and len(flask.request.form['password'])>0:
|
|
f.set_password(flask.request.form['password'])
|
|
# TODO: Add name change but this have to be done with file also ! <04-11-20, Léo Viallon-Galinier> #
|
|
db.session.commit()
|
|
return flask.redirect('/d/{}'.format(f.user))
|
|
else:
|
|
return flask.render_template('file_edit.html', user=user, f=f)
|
|
|
|
@app.route('/f/<id>/del')
|
|
@fll.login_required
|
|
def delfile(id):
|
|
user = fll.current_user
|
|
f = File.query.get(id)
|
|
if f is None:
|
|
return flask.redirect(URL_DEFAULT)
|
|
if f.user != user and user.is_admin<levels_users['Admin']:
|
|
return flask.redirect(URL_DEFAULT)
|
|
|
|
confirm = flask.request.args.get('confirm')
|
|
if confirm is not None:
|
|
fpath = genpath(f.name, f.user)
|
|
if os.path.isfile(fpath):
|
|
os.remove(fpath)
|
|
db.session.delete(f)
|
|
db.session.commit()
|
|
return flask.redirect('/d/{}'.format(f.user))
|
|
return flask.render_template('file_del.html', user=user, f=f)
|
|
|
|
|
|
#######################################################################
|
|
# Users management #
|
|
#######################################################################
|
|
@app.route('/admin/users')
|
|
@fll.login_required
|
|
def admin_users():
|
|
if fll.current_user.is_admin < levels_users['Admin users']:
|
|
return flask.redirect(URL_DEFAULT)
|
|
users = User.query.all()
|
|
return flask.render_template('admin_users.html', user=fll.current_user, users=users)
|
|
|
|
@app.route('/admin/users/<user>', methods=['GET', 'POST'])
|
|
@fll.login_required
|
|
def edituser(user):
|
|
new = user=='new'
|
|
user = User.query.get(user)
|
|
if not new and user is None:
|
|
return flask.redirect('/admin/users')
|
|
if fll.current_user.is_admin >= levels_users['Admin users'] and \
|
|
(new or fll.current_user.is_admin >= user.is_admin):
|
|
if flask.request.method=="POST":
|
|
username = flask.request.form['username']
|
|
name = flask.request.form['name']
|
|
password = flask.request.form['password']
|
|
level = int(flask.request.form['level'])
|
|
if level not in levels_users.values():
|
|
flask.flash('Level unknown')
|
|
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
|
if fll.current_user.is_admin < levels_users['Admin']:
|
|
if not level < fll.current_user.is_admin:
|
|
flask.flash('Invalid level')
|
|
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
|
if len(password)<2 and new:
|
|
flask.flash('Mot de passe trop court')
|
|
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
|
if new and User.query.get(username) is not None:
|
|
flask.flash('Login déjà utilisé ! Choisissez un autre !')
|
|
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
|
if new:
|
|
usertoadd = User(login=username, name=name, is_admin=level)
|
|
usertoadd.set_password(password)
|
|
db.session.add(usertoadd)
|
|
else:
|
|
user.name = name
|
|
if len(password)>=2:
|
|
user.set_password(password)
|
|
user.is_admin = level
|
|
db.session.commit()
|
|
return flask.redirect('/admin/users')
|
|
else:
|
|
return flask.render_template("admin_user_edit.html", user=fll.current_user, useredit=user, levels_users=levels_users)
|
|
return flask.redirect(URL_DEFAULT)
|
|
|
|
@app.route('/admin/users/<user>/del')
|
|
@fll.login_required
|
|
def deluser(user):
|
|
user = User.query.get(user)
|
|
if user is None:
|
|
return flask.redirect('/admin/users')
|
|
if fll.current_user.is_admin >= levels_users['Admin'] or \
|
|
fll.current_user.is_admin >= levels_users['Admin users'] and \
|
|
fll.current_user.is_admin > user.is_admin:
|
|
confirm = flask.request.args.get('confirm')
|
|
if confirm is not None:
|
|
delete_files(user=user)
|
|
db.session.delete(user)
|
|
db.session.commit()
|
|
return flask.redirect('/admin/users')
|
|
return flask.render_template("admin_user_del.html", user=fll.current_user, useredit=user)
|
|
|
|
@app.route('/password', methods=['GET', 'POST'])
|
|
@fll.login_required
|
|
def change_password():
|
|
if flask.request.method=="POST":
|
|
password = flask.request.form['password']
|
|
if len(password) < 5:
|
|
flask.flash('Mot de passe trop court !')
|
|
else:
|
|
fll.current_user.set_password(password)
|
|
db.session.commit()
|
|
return flask.redirect(URL_DEFAULT)
|
|
return flask.render_template("password.html", user=fll.current_user)
|
|
|
|
#######################################################################
|
|
# Files managament #
|
|
#######################################################################
|
|
|
|
def delete_files(user=None):
|
|
if isinstance(user, str):
|
|
username = user
|
|
else:
|
|
username = user.login
|
|
lf = File.query.filter_by(user=username).all()
|
|
for f in lf:
|
|
fpath = genpath(f.name, f.user)
|
|
if os.path.isfile(fpath):
|
|
os.remove(fpath)
|
|
db.session.delete(f)
|
|
db.session.commit()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True, host='127.0.0.1', port=8080 )
|
|
|