SFS/fs.py

451 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 '1' in 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:
print(flask.request.form)
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\n"
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 += "/d/{}/{}\n".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.login 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.login 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 )