Compare commits

...

6 Commits

Author SHA1 Message Date
grimhilt
88e3f0280d not push 2023-09-14 23:21:02 +02:00
grimhilt
17460f937f remove all unnecessary \ 2023-09-12 15:40:25 +02:00
grimhilt
f192ba3759 list all playlist the user has the perms on (not recursive) 2023-09-12 15:34:12 +02:00
grimhilt
3b50112c10 add permissions to files api 2023-09-12 15:08:54 +02:00
grimhilt
5eefea90fa fix update of playlist when there is the same file multiple times 2023-09-12 14:15:26 +02:00
grimhilt
dcdf82cade auto generate secret_key 2023-09-11 21:25:55 +02:00
16 changed files with 308 additions and 77 deletions

View File

@ -3,20 +3,36 @@
# Deployment (from source)
- ``git clone https://github.com/grimhilt/artemio-server.git``
-
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
sudo apt install libjpeg-dev zlib1g-dev
pip install Pillow
sudo apt install libmpv-dev
gunicorn -w 1 -b 0.0.0.0:5500 index:api --access-logfile access.log --error-logfile error.log
# Documentation
## API
/api/login
/api/logout
### Permissions
| Name | Child Of | Description
| --- | --- | ---
CREATE_USER |
CREATE_ROLE |
CREATE_PLAYLIST |
VIEW_PLAYLIST | EDIT_PLAYLIST
OWN_PLAYLIST |
EDIT_PLAYLIST | OWN_PLAYLIST
ACTIVATE_PLAYLIST |
### Playlists (*/api/playlists*)
The user need to be logged in for every routes
*The user need to be logged in for every routes.*
| Method | Endpoint | Permission | Description
| --- | --- | --- | --- |
@ -26,13 +42,24 @@ The user need to be logged in for every routes
| POST | ``/api/playlists/:id`` | EDIT_PLAYLIST | Add file to playlist
| POST | ``/api/playlists/:id/order`` | EDIT_PLAYLIST | Change file order
| POST | ``/api/playlists/:id/seconds`` | EDIT_PLAYLIST | Change display time of a file
| POST | ``/api/playlists/:id/remove_file`` | EDIT_PLAYLIST |
| PUT | ``/api/playlists/:id/update`` | OWN_PLAYLIST |
| POST | ``/api/playlists/:id/remove_file`` | EDIT_PLAYLIST | Remove file from the playlist
| PUT | ``/api/playlists/:id/update`` | OWN_PLAYLIST | Update properties of the playlist
| POST | ``/api/playlists/:id/activate`` | ACTIVATE_PLAYLIST |
| POST | ``/api/playlists/:id/disactivate`` | ACTIVATE_PLAYLIST |
### Users
### Playlists (*/api/playlists*)
*The user need to be logged in for every routes.*
| Method | Endpoint | Permission | Description
| --- | --- | --- | --- |
| GET | ``/api/files`` | EDIT_PLAYLIST | List all existing files
| GET | ``/api/files/:id`` | VIEW_PLAYLIST | Return the file
| POST | ``/api/files/upload`` | EDIT_PLAYLIST | Upload one or multiple files
| DELETE | ``/api/files/:id`` | OWN_PLAYLIST | Delete the file
### Roles
###
### Files

View File

@ -3,6 +3,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_login import LoginManager
from os import path
from config.config import get_secret_key
import logging
@ -15,7 +16,7 @@ def create_api():
CORS(app)
logging.getLogger('flask_cors').level = logging.DEBUG
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
app.secret_key = b'_5#y2L"F4Qfj8zxec]'
app.secret_key = get_secret_key()
login_manager = LoginManager()
login_manager.init_app(app)

View File

@ -3,6 +3,7 @@ from ..models import Playlist, PlaylistFile, File, Role
from .. import db
from datetime import datetime
from ..dao.Playlist import PlaylistDao
from ..dao.UsersDao import UsersDao
from flask_login import current_user
from screen.ScreenManager import ScreenManager
@ -50,17 +51,18 @@ class PlaylistAbl:
def get_playlist(playlist_id):
(query, files) = PlaylistDao.get_playlist(playlist_id)
query = query.as_dict_with_roles()
return jsonify({ \
'id': query['id'], \
'name': query['name'], \
'owner_id': query['owner_id'], \
'view': query['view'], \
'edit': query['edit'], \
return jsonify({
'id': query['id'],
'name': query['name'],
'owner_id': query['owner_id'],
'view': query['view'],
'edit': query['edit'],
'files': files})
@staticmethod
def list():
playlists = db.session.query(Playlist).all()
user_id = current_user.as_dict()['id']
playlists = UsersDao.playlists(user_id)
res = []
for playlist in playlists:
p = playlist.as_dict()
@ -73,11 +75,11 @@ class PlaylistAbl:
# EDIT PLAYLIST CONTENT
@staticmethod
def add_file(playlist_id, data):
new_playlist_file = PlaylistFile( \
playlist_id=playlist_id, \
file_id=data['file_id'], \
position=data['position'], \
seconds=data['seconds'] \
new_playlist_file = PlaylistFile(
playlist_id=playlist_id,
file_id=data['file_id'],
position=data['position'],
seconds=data['seconds']
)
db.session.add(new_playlist_file)
@ -87,7 +89,7 @@ class PlaylistAbl:
@staticmethod
def change_order(playlist_id, data):
db.session.query(PlaylistFile) \
.filter(PlaylistFile.file_id == data['file_id']) \
.filter(PlaylistFile.id == data['pfid']) \
.filter(PlaylistFile.playlist_id == playlist_id) \
.update({'position': data['position']})
db.session.commit()
@ -96,7 +98,7 @@ class PlaylistAbl:
@staticmethod
def change_seconds(playlist_id, data):
db.session.query(PlaylistFile) \
.filter(PlaylistFile.file_id == data['file_id']) \
.filter(PlaylistFile.id == data['pfid']) \
.filter(PlaylistFile.playlist_id == playlist_id) \
.update({'seconds': data['seconds']})
db.session.commit()
@ -105,7 +107,7 @@ class PlaylistAbl:
@staticmethod
def remove_file(playlist_id, data):
query = db.session.query(PlaylistFile) \
.filter(PlaylistFile.id == data['file_id']) \
.filter(PlaylistFile.id == data['pfid']) \
.first()
db.session.delete(query)
db.session.commit()

View File

@ -1,13 +1,13 @@
from flask import Blueprint, request, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.security import check_password_hash
from flask_login import current_user
from ..models import User, Role
from api.dao.UsersDao import UsersDao
from .. import db
def is_current_admin():
return current_user.as_dict()['roles'][0]['parent_id'] is None
class UserAbl:
@staticmethod
@ -27,30 +27,14 @@ class UserAbl:
if bit == '1' and bit != user_perms[position]:
return jsonify(message="You don't have the permission to give permission(s) you don't have"), 403
# create the user
new_user = User( \
login=login, \
password=generate_password_hash(password, method='sha256') \
)
db.session.add(new_user)
db.session.flush()
# create the permissions for the user
new_role = Role( \
name=login, \
user_id=new_user.as_dict()['id'], \
parent_id=current_user.as_dict()['roles'][0]['id'], \
permissions=permissions)
db.session.add(new_role)
new_user.roles.append(new_role)
db.session.flush()
# create user
new_user = UsersDao.create(login, password, permissions, current_user)
db.session.commit()
return jsonify(new_user.as_dict())
@staticmethod
def update(user_id, data):
# todo
return jsonify()
@staticmethod

View File

@ -1,11 +1,15 @@
from flask import Blueprint, request, jsonify, send_file
from flask_login import login_required
from ..permissions import Perm, permissions
from ..models import File
from .. import db
files = Blueprint('files', __name__)
FILE_DIR = './data/'
FILE_DIR = '../data/'
@files.route('/files', methods=['POST'])
@login_required
@permissions.require([Perm.EDIT_PLAYLIST])
def upload():
res = []
for file_key in request.files:
@ -22,6 +26,8 @@ def upload():
return jsonify(res)
@files.route('/files', methods=['GET'])
@login_required
@permissions.require([Perm.EDIT_PLAYLIST])
def list():
files = db.session.query(File).all()
res = []
@ -30,12 +36,17 @@ def list():
return jsonify(res)
@files.route('/files/<int:file_id>', methods=['GET'])
@login_required
@permissions.require([Perm.VIEW_PLAYLIST])
def load(file_id):
file = db.session.query(File).filter(File.id == file_id).first()
return send_file(('../../data/' + file.name), mimetype=file.type)
@files.route('/files/<int:file_id>', methods=['DELETE'])
@login_required
@permissions.require([Perm.OWN_PLAYLIST])
def delete(file_id):
# todo warning if file is still in use
rows = db.session.query(File).filter(File.id == file_id).all()
for row in rows:
db.session.delete(row)

View File

@ -20,6 +20,7 @@ def create():
@playlist.route('/playlists', methods=["GET"])
@login_required
@permissions.require([Perm.VIEW_PLAYLIST])
def list():
return PlaylistAbl.list()

View File

@ -0,0 +1,15 @@
from .. import db
from ..models import User, Role, Playlist, ParentRole
class ParentRoleDao:
def get_children(role_id):
children = db.session.query(ParentRole) \
.filter(ParentRole.parent_id == role_id) \
.all()
return children
def get_parents(role_id):
parents = db.session.query(ParentRole) \
.filter(ParentRole.child_id == role_id) \
.all()
return parents

View File

@ -13,3 +13,32 @@ class PlaylistDao:
files.append(file)
return (query, files)
def get_playlist_q(playlist_id):
query = db.session.query(Playlist).filter(Playlist.id == playlist_id).first()
return query
def has_role_view_d(playlist_id, user_id):
has_role_to_view = db.session.query(Playlist) \
.filter(Playlist.id == playlist_id) \
.filter(
Playlist.view.any(
# check if a role belongs to this user
Role.user_id == user_id or
# check if a this user has a role to view
Role.users.any(User.id == user_id)
)) \
.first()
return has_role_to_view
def has_role_edit_d(playlist_id, user_id):
has_role_to_edit = db.session.query(Playlist) \
.filter(
Playlist.edit.any(
# check if a role belongs to this user
Role.user_id == user_id or
# check if a this user has a role to edit
Role.users.any(User.id == user_id)
)) \
.first()
return has_role_to_edit

31
src/api/dao/RolesDao.py Normal file
View File

@ -0,0 +1,31 @@
from .. import db
from ..models import User, Role, Playlist, ParentRole
from .ParentRoleDao import ParentRoleDao
class RolesDao:
def create(name, user_id, parent_id, permissions):
new_role = Role(
name=name,
user_id=user_id,
parent_id=parent_id,
permissions=permissions)
db.session.add(new_role)
# get all parents
parents = ParentRoleDao.get_parents(parent_id)
parent_ids = [parent_id]
for parent in parents:
parent_ids.append(parent.as_dict()['parent_id'])
# add all parents
for id in parent_ids:
parent_role = ParentRole(
parent_id=id,
child_id=user_id
)
db.session.add(parent_role)
db.session.flush()
return new_role

74
src/api/dao/UsersDao.py Normal file
View File

@ -0,0 +1,74 @@
from .. import db
from werkzeug.security import generate_password_hash, check_password_hash
from ..models import User, Role, Playlist
from .RolesDao import RolesDao
class UsersDao:
def create(login, password, permissions, current_user):
# create the user
new_user = User(
login=login,
password=generate_password_hash(password, method='sha256')
)
db.session.add(new_user)
db.session.flush()
# create role for the user
new_role = RolesDao.create(
name=login,
user_id=new_user.as_dict()['id'],
parent_id=current_user.as_dict()['roles'][0]['id'],
permissions=permissions)
new_user.roles.append(new_role)
db.session.flush()
return new_user
def has_role_view_q(user_id):
has_role_to_view = db.session.query(User) \
.filter(User.id == user_id) \
.filter(
User.roles.any(
Role.users.any(Role.playlists_view is not None)
)) \
.first()
return has_role_to_view
def has_role_edit_q(user_id):
has_role_to_edit = db.session.query(User) \
.filter(User.id == user_id) \
.filter(
User.roles.any(
Role.users.any(Role.playlists_edit is not None)
)) \
.first()
return has_role_to_edit
def playlists(user_id):
# todo recursion on user parenting
playlists = db.session.query(Playlist) \
.filter(
# all playlist where user can view
Playlist.view.any(
# check if a role belongs to this user
Role.user_id == user_id or
# check if a this user has a role to view
Role.users.any(User.id == user_id) \
) |
# all playlist where user can edit
Playlist.edit.any(
# check if a role belongs to this user
Role.user_id == user_id or
# check if a this user has a role to edit
Role.users.any(User.id == user_id)
) |
(Playlist.owner_id == user_id)
) \
.all()
return playlists

View File

@ -55,16 +55,36 @@ class UserRole(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), primary_key=True)
class ParentRole(db.Model):
__tablename__ = 'ParentRole'
parent_id = db.Column(db.Integer, db.ForeignKey('role.id'), primary_key=True)
child_id = db.Column(db.Integer, db.ForeignKey('role.id'), primary_key=True)
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), default=None)
permissions = db.Column(db.Integer, default=0)
parent_id = db.Column(db.Integer, db.ForeignKey('role.id'), default=None)
children = db.relationship('Role', secondary="ParentRole",
primaryjoin=id == ParentRole.parent_id,
secondaryjoin=id == ParentRole.child_id,
backref='parents')
users = db.relationship('User', secondary='UserRole', back_populates='roles')
playlists_view = db.relationship('Playlist', secondary='PlaylistView', back_populates='view')
playlists_edit = db.relationship('Playlist', secondary='PlaylistEdit', back_populates='edit')
def as_full_dict(self):
res = self.as_dict()
res['parents'] = [parent.as_dict() for parent in self.parents]
res['children'] = [child.as_dict() for child in self.children]
return res
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
@ -76,7 +96,7 @@ class User(db.Model, UserMixin):
def as_dict(self):
res = self.as_dict_unsafe()
res['roles'] = [role.as_dict() for role in self.roles]
res['roles'] = [role.as_full_dict() for role in self.roles]
del res['password']
return res

View File

@ -4,6 +4,9 @@ from flask import request, jsonify
from flask_login import current_user
from . import db
from .models import Playlist, PlaylistFile, User, Role, UserRole
from .dao.Playlist import PlaylistDao
from .dao.UsersDao import UsersDao
class Perm(IntEnum):
CREATE_USER = 0
@ -26,7 +29,7 @@ class permissions:
check_perm = CheckPermissionFactory(perm)
print(args, kwargs)
if not check_perm.is_valid(kwargs):
return jsonify( \
return jsonify(
message=check_perm.message), \
check_perm.status_code
return func(*args, **kwargs)
@ -59,11 +62,10 @@ def CheckPermissionFactory(perm):
def get_playlist_id(args):
if 'playlist_id' in args:
return args['playlist_id']
json = request.get_json()
if 'playlist_id' in json:
print("in")
json = request.get_json(silent=True)
if json is not None and 'playlist_id' in json:
return json['playlist_id']
return
return None
def checkBit(permissions, index):
binStr = bin(permissions)
@ -84,12 +86,14 @@ class CheckOwnPlaylist:
def is_valid(self, args):
playlist_id = get_playlist_id(args)
query = db.session.query(Playlist).filter(Playlist.id == playlist_id).first()
if playlist_id is None:
return False
query = PlaylistDao.get_playlist_q(playlist_id)
if query is None:
self.message = "This playlist doesn't exist"
self.status_code = 404
return False
print(query.as_dict())
return query.as_dict()['owner_id'] == current_user.as_dict()['id']
class CheckViewPlaylist:
@ -109,15 +113,18 @@ class CheckViewPlaylist:
playlist_id = get_playlist_id(args)
user_id = current_user.as_dict()['id']
has_role_to_view = db.session.query(Playlist) \
.filter( \
Playlist.view.any( \
# check if a role belongs to this user
Role.user_id == user_id or \
# check if a this user has a role to view
Role.users.any(User.id == user_id) \
)) \
.first()
# if playlist_id is none then there is not precise playlist
# to compare the permissions, so we check if the user has
# a permission on any playlist
has_role_to_view = None
if playlist_id is not None:
# check if has role on one precise playlist
has_role_to_view = PlaylistDao.has_role_to_view(playlist_id, user_id)
else:
# check if has role to view any playlist
has_role_to_view = UsersDao.has_role_view_q(user_id)
return has_role_to_view is not None
class CheckEditPlaylist:
@ -136,15 +143,18 @@ class CheckEditPlaylist:
playlist_id = get_playlist_id(args)
user_id = current_user.as_dict()['id']
has_role_to_edit = db.session.query(Playlist) \
.filter( \
Playlist.edit.any( \
# check if a role belongs to this user
Role.user_id == user_id or \
# check if a this user has a role to edit
Role.users.any(User.id == user_id) \
)) \
.first()
# if playlist_id is none then there is not precise playlist
# to compare the permissions, so we check if the user has
# a permission on any playlist
has_role_to_edit = None
if playlist_id is not None:
# check if has role on one precise playlist
has_role_to_edit = PlaylistDao.has_role_to_edit(playlist_id, user_id)
else:
# check if has role to view any playlist
has_role_to_edit = UsersDao.has_role_edit_q(user_id)
return has_role_to_edit is not None
class CheckCreateUser:

1
src/config/SECRET_KEY Normal file
View File

@ -0,0 +1 @@
b'Dn\xe2\x96\xd9\xe7Z;\xd7;\x03\xbe\xa8J\xc7\xda\xd2\xe6\xfa\xe6HU( '

19
src/config/config.py Normal file
View File

@ -0,0 +1,19 @@
import os
import random
import string
SECRET_KEY_FILE = './config/SECRET_KEY'
def get_secret_key():
if os.path.isfile(SECRET_KEY_FILE):
# read the secret key from the file
with open(SECRET_KEY_FILE, 'r') as file:
secret_key = file.read().strip()
return secret_key
else:
# generate a new secret key
secret_key = os.urandom(24)
# save it to the file
with open(SECRET_KEY_FILE, 'w') as file:
file.write(str(secret_key))
return secret_key

View File

@ -1,12 +1,16 @@
from api import create_api
from screen.ScreenManager import ScreenManager
#api = create_api()
#screen_manager = ScreenManager().getInstance()
api = create_api()
screen_manager = ScreenManager().getInstance()
if __name__ == '__main__':
#api.run(host="0.0.0.0", port=5500, debug=True)
#api.run(host="0.0.0.0", port=5500)
#api.run()
api.run(host="0.0.0.0", port=5500)
def test():
from screen.SlideShow import SlideShow
import tkinter as tk
import mpv

View File

@ -7,6 +7,8 @@ import imageio
import vlc
import mpv
DATA_DIR = "../data/"
class SlideShow:
def __init__(self, root, files):
print(files)
@ -62,7 +64,7 @@ class MediaFactory:
def image_player(self):
print("image player")
path = './data/' + self.file['name']
path = DATA_DIR + self.file['name']
image = Image.open(path)
image = self.parent.resize_full_screen(image)
@ -78,7 +80,7 @@ class VideoPlayer:
def __init__(self, parent, file):
self.file = file
self.parent = parent
self.path = './data/' + self.file['name']
self.path = DATA_DIR + self.file['name']
#self.mpv_instance = mpv.MPV(wid=str(self.parent.canvas.winfo_id()))
instance = vlc.Instance()
player = instance.media_player_new()