JWT login and multi API keys!

This commit is contained in:
luukas 2022-01-15 02:23:50 +02:00
parent ea3f36809d
commit 93857f90db
36 changed files with 1254 additions and 731 deletions

View File

@ -18,6 +18,7 @@ from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__)
@ -70,3 +71,7 @@ class Crafty_Perms_Controller:
@staticmethod
def add_server_creation(user_id):
return crafty_permissions.add_server_creation(user_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys):
return crafty_permissions.get_api_key_permissions_list(key)

View File

@ -31,10 +31,6 @@ class Management_Controller:
def get_latest_hosts_stats():
return management_helper.get_latest_hosts_stats()
@staticmethod
def new_api_token():
return management_helper.new_api_token()
#************************************************************************************************
# Commands Methods
#************************************************************************************************

View File

@ -39,7 +39,9 @@ class Roles_Controller:
@staticmethod
def update_role(role_id, role_data={}, permissions_mask="00000000"):
def update_role(role_id: str, role_data = None, permissions_mask: str = "00000000"):
if role_data is None:
role_data = {}
base_data = Roles_Controller.get_role_with_servers(role_id)
up_data = {}
added_servers = set()

View File

@ -14,7 +14,7 @@ from app.classes.shared.console import console
from app.classes.shared.main_models import db_helper
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.models.users import users_helper
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.roles import roles_helper
from app.classes.models.servers import servers_helper
@ -42,11 +42,6 @@ class Server_Perms_Controller:
permissions_list = server_permissions.get_role_permissions_list(role_id)
return permissions_list
@staticmethod
def get_server_permissions_foruser(user_id, server_id):
permissions_list = server_permissions.get_user_permissions_list(user_id, server_id)
return permissions_list
@staticmethod
def add_role_server(server_id, role_id, rs_permissions="00000000"):
return server_permissions.add_role_server(server_id, role_id, rs_permissions)
@ -78,8 +73,30 @@ class Server_Perms_Controller:
return server_permissions.get_role_permissions_list(role_id)
@staticmethod
def get_user_permissions_list(user_id, server_id):
return server_permissions.get_user_permissions_list(user_id, server_id)
def get_user_id_permissions_list(user_id: str, server_id: str):
return server_permissions.get_user_id_permissions_list(user_id, server_id)
@staticmethod
def get_api_key_id_permissions_list(key_id: str, server_id: str):
key = users_helper.get_user_api_key(key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_user_id_permissions_list(user_id: str, server_id: str):
return server_permissions.get_user_id_permissions_list(user_id, server_id)
@staticmethod
def get_api_key_id_permissions_list(key_id: str, server_id: str):
key = users_helper.get_user_api_key(key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_authorized_servers_stats_from_roles(user_id):

View File

@ -17,7 +17,7 @@ from app.classes.shared.console import console
from app.classes.shared.main_models import db_helper
from app.classes.models.servers import servers_helper
from app.classes.models.roles import roles_helper
from app.classes.models.users import users_helper
from app.classes.models.users import users_helper, ApiKeys
from app.classes.models.server_permissions import server_permissions, Enum_Permissions_Server
from app.classes.shared.server import Server
@ -82,18 +82,42 @@ class Servers_Controller:
return servers_helper.get_all_servers_stats()
@staticmethod
def get_authorized_servers_stats(user_id):
def get_authorized_servers_stats_api_key(api_key: ApiKeys):
server_data = []
authorized_servers = Servers_Controller.get_authorized_servers(user_id)
authorized_servers = Servers_Controller.get_authorized_servers(api_key.user.user_id)
for s in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
user_permissions = server_permissions.get_user_permissions_list(user_id, s.get('server_id'))
key_permissions = server_permissions.get_api_key_permissions_list(api_key, s.get('server_id'))
if Enum_Permissions_Server.Commands in key_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0],
"user_command_permission": user_command_permission})
return server_data
@staticmethod
def get_authorized_servers_stats(user_id):
server_data = []
print('test 1')
authorized_servers = Servers_Controller.get_authorized_servers(user_id)
print('test 2')
for s in authorized_servers:
latest = servers_helper.get_latest_server_stats(s.get('server_id'))
# TODO
user_permissions = server_permissions.get_user_id_permissions_list(user_id, s.get('server_id'))
if Enum_Permissions_Server.Commands in user_permissions:
user_command_permission = True
else:
user_command_permission = False
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)[0], "user_command_permission":user_command_permission})
server_data.append({
'server_data': s,
'stats': db_helper.return_rows(latest)[0],
'user_command_permission': user_command_permission
})
return server_data
@staticmethod
@ -112,17 +136,21 @@ class Servers_Controller:
return servers_helper.server_id_exists(server_id)
@staticmethod
def server_id_authorized(serverId, user_id):
authorized = 0
def server_id_authorized(server_id_a, user_id):
print("Server id authorized: ")
user_roles = users_helper.user_role_query(user_id)
for role in user_roles:
authorized = server_permissions.get_role_servers_from_role_id(role.role_id)
#authorized = db_helper.return_rows(authorized)
if authorized.count() == 0:
return False
for server_id_b in server_permissions.get_role_servers_from_role_id(role.role_id):
if server_id_a == server_id_b:
return True
return False
@staticmethod
def server_id_authorized_api_key(server_id: str, api_key: ApiKeys) -> bool:
# TODO
return Servers_Controller.server_id_authorized(server_id, api_key.user.user_id)
# There is no view server permission
# permission_helper.both_have_perm(api_key)
@staticmethod
def set_update(server_id, value):

View File

@ -2,6 +2,8 @@ import os
import time
import logging
import sys
from typing import Optional
import yaml
import asyncio
import shutil
@ -13,6 +15,7 @@ from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.users import Users, users_helper
from app.classes.shared.authentication import authentication
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
from app.classes.models.management import management_helper
@ -31,10 +34,6 @@ class Users_Controller:
def get_id_by_name(username):
return users_helper.get_user_id_by_name(username)
@staticmethod
def get_user_by_api_token(token: str):
return users_helper.get_user_by_api_token(token)
@staticmethod
def get_user_lang_by_id(user_id):
return users_helper.get_user_lang_by_id(user_id)
@ -52,7 +51,11 @@ class Users_Controller:
users_helper.set_support_path(user_id, support_path)
@staticmethod
def update_user(user_id, user_data={}, user_crafty_data={}):
def update_user(user_id: str, user_data=None, user_crafty_data=None):
if user_crafty_data is None:
user_crafty_data = {}
if user_data is None:
user_data = {}
base_data = users_helper.get_user(user_id)
up_data = {}
added_roles = set()
@ -64,9 +67,6 @@ class Users_Controller:
elif key == "roles":
added_roles = user_data['roles'].difference(base_data['roles'])
removed_roles = base_data['roles'].difference(user_data['roles'])
elif key == "regen_api":
if user_data['regen_api']:
up_data['api_token'] = management_helper.new_api_token()
elif key == "password":
if user_data['password'] is not None and user_data['password'] != "":
up_data['password'] = helper.encode_pass(user_data['password'])
@ -77,13 +77,12 @@ class Users_Controller:
logger.debug("user: {} +role:{} -role:{}".format(user_data, added_roles, removed_roles))
for role in added_roles:
users_helper.get_or_create(user_id=user_id, role_id=role)
# TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point
permissions_mask = user_crafty_data.get('permissions_mask', '000')
if 'server_quantity' in user_crafty_data:
limit_server_creation = user_crafty_data['server_quantity'][
Enum_Permissions_Crafty.Server_Creation.name]
for key in user_crafty_data:
if key == "permissions_mask":
permissions_mask = user_crafty_data['permissions_mask']
if key == "server_quantity":
limit_server_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Server_Creation.name]
limit_user_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.User_Config.name]
limit_role_creation = user_crafty_data['server_quantity'][Enum_Permissions_Crafty.Roles_Config.name]
else:
@ -98,8 +97,8 @@ class Users_Controller:
users_helper.update_user(user_id, up_data)
@staticmethod
def add_user(username, password=None, email="default@example.com", api_token=None, enabled=True, superuser=False):
return users_helper.add_user(username, password=password, email=email, api_token=api_token, enabled=enabled, superuser=superuser)
def add_user(username, password=None, email="default@example.com", enabled: bool = True, superuser: bool = False):
return users_helper.add_user(username, password=password, email=email, enabled=enabled, superuser=superuser)
@staticmethod
def remove_user(user_id):
@ -109,9 +108,19 @@ class Users_Controller:
def user_id_exists(user_id):
return users_helper.user_id_exists(user_id)
#************************************************************************************************
@staticmethod
def get_user_id_by_api_token(token: str) -> str:
token_data = authentication.check_no_iat(token)
return token_data['user_id']
@staticmethod
def get_user_by_api_token(token: str):
_, user = authentication.check(token)
return user
# ************************************************************************************************
# User Roles Methods
#************************************************************************************************
# ************************************************************************************************
@staticmethod
def get_user_roles_id(user_id):
@ -132,3 +141,29 @@ class Users_Controller:
@staticmethod
def user_role_query(user_id):
return users_helper.user_role_query(user_id)
# ************************************************************************************************
# Api Keys Methods
# ************************************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
return users_helper.get_user_api_keys(user_id)
@staticmethod
def get_user_api_key(key_id: str):
return users_helper.get_user_api_key(key_id)
@staticmethod
def add_user_api_key(name: str, user_id: str, superuser: bool = False,
server_permissions_mask: Optional[str] = None,
crafty_permissions_mask: Optional[str] = None):
return users_helper.add_user_api_key(name, user_id, superuser, server_permissions_mask, crafty_permissions_mask)
@staticmethod
def delete_user_api_keys(user_id: str):
return users_helper.delete_user_api_keys(user_id)
@staticmethod
def delete_user_api_key(key_id: str):
return users_helper.delete_user_api_key(key_id)

View File

@ -5,7 +5,8 @@ import datetime
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.users import Users
from app.classes.models.users import Users, ApiKeys
from app.classes.shared.permission_helper import permission_helper
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
@ -191,4 +192,18 @@ class Permissions_Crafty:
User_Crafty.save(user_crafty)
return user_crafty.created_server
@staticmethod
def get_api_key_permissions_list(key: ApiKeys):
user = key.user
if user.superuser and key.superuser:
return crafty_permissions.get_permissions_list()
else:
user_permissions_mask = crafty_permissions.get_crafty_permissions_mask(user.user_id)
key_permissions_mask: str = key.crafty_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_list = crafty_permissions.get_permissions(permissions_mask)
return permissions_list
crafty_permissions = Permissions_Crafty()

View File

@ -7,7 +7,8 @@ from app.classes.shared.helpers import helper
from app.classes.shared.console import console
from app.classes.models.servers import Servers
from app.classes.models.roles import Roles
from app.classes.models.users import users_helper
from app.classes.models.users import users_helper, ApiKeys, Users
from app.classes.shared.permission_helper import permission_helper
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
@ -78,10 +79,7 @@ class Permissions_Servers:
@staticmethod
def has_permission(permission_mask, permission_tested: Enum_Permissions_Server):
result = False
if permission_mask[permission_tested.value] == '1':
result = True
return result
return permission_mask[permission_tested.value] == '1'
@staticmethod
def set_permission(permission_mask, permission_tested: Enum_Permissions_Server, value):
@ -94,6 +92,14 @@ class Permissions_Servers:
def get_permission(permission_mask, permission_tested: Enum_Permissions_Server):
return permission_mask[permission_tested.value]
@staticmethod
def get_token_permissions(permissions_mask, api_permissions_mask):
permissions_list = []
for member in Enum_Permissions_Server.__members__.items():
if permission_helper.both_have_perm(permissions_mask, api_permissions_mask, member[1]):
permissions_list.append(member[1])
return permissions_list
#************************************************************************************************
# Role_Servers Methods
@ -146,7 +152,9 @@ class Permissions_Servers:
Role_Servers.save(role_server)
@staticmethod
def delete_roles_permissions(role_id, removed_servers={}):
def delete_roles_permissions(role_id, removed_servers=None):
if removed_servers is None:
removed_servers = {}
return Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute()
@staticmethod
@ -155,21 +163,52 @@ class Permissions_Servers:
return Role_Servers.delete().where(Role_Servers.server_id == server_id).execute()
@staticmethod
def get_user_permissions_list(user_id, server_id):
permissions_mask = ''
permissions_list = []
def get_user_id_permissions_mask(user_id, server_id: str):
user = users_helper.get_user_model(user_id)
return server_permissions.get_user_permissions_mask(user, server_id)
user = users_helper.get_user(user_id)
if user['superuser'] == True:
@staticmethod
def get_user_permissions_mask(user: Users, server_id: str):
if user.superuser:
permissions_mask = '1' * len(server_permissions.get_permissions_list())
else:
roles_list = users_helper.get_user_roles_id(user['user_id'])
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
permissions_mask = role_server[0].permissions
return permissions_mask
@staticmethod
def get_user_id_permissions_list(user_id, server_id: str):
user = users_helper.get_user_model(user_id)
return server_permissions.get_user_permissions_list(user, server_id)
@staticmethod
def get_user_permissions_list(user: Users, server_id: str):
if user.superuser:
permissions_list = server_permissions.get_permissions_list()
else:
roles_list = users_helper.get_user_roles_id(user_id)
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == int(server_id)).execute()
if len(role_server) > 0:
permissions_mask = role_server[0].permissions
else:
permissions_mask = '00000000'
permissions_mask = server_permissions.get_user_permissions_mask(user, server_id)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list
@staticmethod
def get_api_key_id_permissions_list(key_id, server_id: str):
key = ApiKeys.get(ApiKeys.token_id == key_id)
return server_permissions.get_api_key_permissions_list(key, server_id)
@staticmethod
def get_api_key_permissions_list(key: ApiKeys, server_id: str):
user = key.user
if user.superuser and key.superuser:
return server_permissions.get_permissions_list()
else:
roles_list = users_helper.get_user_roles_id(user['user_id'])
role_server = Role_Servers.select().where(Role_Servers.role_id.in_(roles_list)).where(Role_Servers.server_id == server_id).execute()
user_permissions_mask = role_server[0].permissions
key_permissions_mask = key.server_permissions
permissions_mask = permission_helper.combine_masks(user_permissions_mask, key_permissions_mask)
permissions_list = server_permissions.get_permissions(permissions_mask)
return permissions_list
server_permissions = Permissions_Servers()

View File

@ -2,6 +2,7 @@ import os
import sys
import logging
import datetime
from typing import Optional, List, Union
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
@ -41,14 +42,32 @@ class Users(Model):
email = CharField(default="default@example.com")
enabled = BooleanField(default=True)
superuser = BooleanField(default=False)
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
lang = CharField(default="en_EN")
support_logs = CharField(default = '')
valid_tokens_from = DateTimeField(default=datetime.datetime.now)
class Meta:
table_name = "users"
database = database
# ************************************************************************************************
# API Keys Class
# ************************************************************************************************
class ApiKeys(Model):
token_id = AutoField()
name = CharField(default='', unique=True, index=True)
created = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(Users, backref='api_token', index=True)
server_permissions = CharField(default='00000000')
crafty_permissions = CharField(default='000')
superuser = BooleanField(default=False)
class Meta:
table_name = 'api_keys'
database = database
#************************************************************************************************
# User Roles Class
#************************************************************************************************
@ -86,18 +105,6 @@ class helper_users:
except DoesNotExist:
return None
@staticmethod
def get_user_by_api_token(token: str):
query = Users.select().where(Users.api_token == token)
if query.exists():
user = model_to_dict(Users.get(Users.api_token == token))
# I know it should apply it without setting it but I'm just making sure
user = users_helper.add_user_roles(user)
return user
else:
return {}
@staticmethod
def user_query(user_id):
user_query = Users.select().where(Users.user_id == user_id)
@ -117,7 +124,6 @@ class helper_users:
'email': "default@example.com",
'enabled': True,
'superuser': True,
'api_token': None,
'roles': [],
'servers': [],
'support_logs': '',
@ -140,21 +146,21 @@ class helper_users:
return False
@staticmethod
def add_user(username, password=None, email=None, api_token=None, enabled=True, superuser=False):
def get_user_model(user_id: str) -> Users:
user = Users.get(Users.user_id == user_id)
user = users_helper.add_user_roles(user)
return user
@staticmethod
def add_user(username: str, password: Optional[str] = None, email: Optional[str] = None, enabled: bool = True, superuser: bool = False) -> str:
if password is not None:
pw_enc = helper.encode_pass(password)
else:
pw_enc = None
if api_token is None:
api_token = users_helper.new_api_token()
else:
if type(api_token) is not str and len(api_token) != 32:
raise ValueError("API token must be a 32 character string")
user_id = Users.insert({
Users.username: username.lower(),
Users.password: pw_enc,
Users.email: email,
Users.api_token: api_token,
Users.enabled: enabled,
Users.superuser: superuser,
Users.created: helper.get_time_as_string()
@ -162,7 +168,9 @@ class helper_users:
return user_id
@staticmethod
def update_user(user_id, up_data={}):
def update_user(user_id, up_data=None):
if up_data is None:
up_data = {}
if up_data:
Users.update(up_data).where(Users.user_id == user_id).execute()
@ -183,14 +191,6 @@ class helper_users:
return False
return True
@staticmethod
def new_api_token():
while True:
token = helper.random_string_generator(32)
test = list(Users.select(Users.user_id).where(Users.api_token == token))
if len(test) == 0:
return token
#************************************************************************************************
# User_Roles Methods
#************************************************************************************************
@ -223,7 +223,7 @@ class helper_users:
}).execute()
@staticmethod
def add_user_roles(user):
def add_user_roles(user: Union[dict, Users]):
if type(user) == dict:
user_id = user['user_id']
else:
@ -237,7 +237,11 @@ class helper_users:
for r in roles_query:
roles.add(r.role_id.role_id)
if type(user) == dict:
user['roles'] = roles
else:
user.roles = roles
#logger.debug("user: ({}) {}".format(user_id, user))
return user
@ -257,5 +261,36 @@ class helper_users:
def remove_roles_from_role_id(role_id):
User_Roles.delete().where(User_Roles.role_id == role_id).execute()
# ************************************************************************************************
# ApiKeys Methods
# ************************************************************************************************
@staticmethod
def get_user_api_keys(user_id: str):
return ApiKeys.select().where(ApiKeys.user_id == user_id).execute()
@staticmethod
def get_user_api_key(key_id: str) -> ApiKeys:
return ApiKeys.get(ApiKeys.token_id == key_id)
@staticmethod
def add_user_api_key(name: str, user_id: str, superuser: bool = False, server_permissions_mask: Optional[str] = None, crafty_permissions_mask: Optional[str] = None):
return ApiKeys.insert({
ApiKeys.name: name,
ApiKeys.user_id: user_id,
**({ApiKeys.server_permissions: server_permissions_mask} if server_permissions_mask is not None else {}),
**({ApiKeys.crafty_permissions: crafty_permissions_mask} if crafty_permissions_mask is not None else {}),
ApiKeys.superuser: superuser
}).execute()
@staticmethod
def delete_user_api_keys(user_id: str):
ApiKeys.delete().where(ApiKeys.user_id == user_id).execute()
@staticmethod
def delete_user_api_key(key_id: str):
ApiKeys.delete().where(ApiKeys.token_id == key_id).execute()
users_helper = helper_users()

View File

@ -0,0 +1,76 @@
import logging
import time
from typing import Optional, Dict, Any, Tuple
import jwt
from jwt import PyJWTError
from app.classes.models.users import users_helper, ApiKeys
from app.classes.shared.helpers import helper
logger = logging.getLogger(__name__)
class Authentication:
def __init__(self):
self.secret = "my secret"
self.secret = helper.get_setting('apikey_secret', None)
if self.secret is None or self.secret == 'random':
self.secret = helper.random_string_generator(64)
@staticmethod
def generate(user_id, extra=None):
if extra is None:
extra = {}
return jwt.encode(
{
'user_id': user_id,
'iat': int(time.time()),
**extra
},
authentication.secret,
algorithm="HS256"
)
@staticmethod
def read(token):
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
@staticmethod
def check_no_iat(token) -> Optional[Dict[str, Any]]:
try:
return jwt.decode(token, authentication.secret, algorithms=["HS256"])
except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error)
return None
@staticmethod
def check(token) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
try:
data = jwt.decode(token, authentication.secret, algorithms=["HS256"])
except PyJWTError as error:
logger.debug("Error while checking JWT token: ", exc_info=error)
return None
iat: int = data['iat']
key: Optional[ApiKeys] = None
if 'token_id' in data:
key_id = data['token_id']
key = users_helper.get_user_api_key(key_id)
if key is None:
return None
user_id: str = data['user_id']
user = users_helper.get_user(user_id)
# TODO: Have a cache or something so we don't constantly have to query the database
if int(user.get('valid_tokens_from').timestamp()) < iat:
# Success!
return key, data, user
else:
return None
@staticmethod
def check_bool(token) -> bool:
return authentication.check(token) is not None
authentication = Authentication()

View File

@ -41,29 +41,33 @@ class MainPrompt(cmd.Cmd, object):
self.universal_exit()
def do_migrations(self, line):
if (line == 'up'):
if line == 'up':
self.migration_manager.up()
elif (line == 'down'):
elif line == 'down':
self.migration_manager.down()
elif (line == 'done'):
elif line == 'done':
console.info(self.migration_manager.done)
elif (line == 'todo'):
elif line == 'todo':
console.info(self.migration_manager.todo)
elif (line == 'diff'):
elif line == 'diff':
console.info(self.migration_manager.diff)
elif (line == 'info'):
elif line == 'info':
console.info('Done: {}'.format(self.migration_manager.done))
console.info('FS: {}'.format(self.migration_manager.todo))
console.info('Todo: {}'.format(self.migration_manager.diff))
elif (line.startswith('add ')):
elif line.startswith('add '):
migration_name = line[len('add '):]
self.migration_manager.create(migration_name, False)
else:
console.info('Unknown migration command')
def do_threads(self, line):
@staticmethod
def do_threads(_line):
for thread in threading.enumerate():
print(f'Name: {thread.name} IDENT: {thread.ident}')
if sys.version_info >= (3, 8):
print(f'Name: {thread.name} Identifier: {thread.ident} TID/PID: {thread.native_id}')
else:
print(f'Name: {thread.name} Identifier: {thread.ident}')
def universal_exit(self):
logger.info("Stopping all server daemons / threads")
@ -75,7 +79,6 @@ class MainPrompt(cmd.Cmd, object):
sys.exit(0)
time.sleep(1)
@staticmethod
def help_exit():
console.help("Stops the server if running, Exits the program")

View File

@ -164,25 +164,6 @@ class Helpers:
cmd_out[ci] += c
return cmd_out
def check_for_old_logs(self, db_helper):
servers = db_helper.get_all_defined_servers()
for server in servers:
logs_path = os.path.split(server['log_path'])[0]
latest_log_file = os.path.split(server['log_path'])[1]
logs_delete_after = int(server['logs_delete_after'])
if logs_delete_after == 0:
continue
log_files = list(filter(
lambda val: val != latest_log_file,
os.listdir(logs_path)
))
for log_file in log_files:
log_file_path = os.path.join(logs_path, log_file)
if self.check_file_exists(log_file_path) and \
self.is_file_older_than_x_days(log_file_path, logs_delete_after):
os.remove(log_file_path)
def get_setting(self, key, default_return=False):
try:

View File

@ -3,6 +3,8 @@ import pathlib
import time
import logging
import sys
from typing import Union
from app.classes.models.server_permissions import Enum_Permissions_Server
from app.classes.models.users import helper_users
from peewee import DoesNotExist
@ -18,10 +20,16 @@ from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
#Importing Models
from app.classes.models.crafty_permissions import crafty_permissions, Enum_Permissions_Crafty
# Importing Models
from app.classes.models.servers import servers_helper
#Importing Controllers
from app.classes.shared.console import console
from app.classes.shared.helpers import helper
from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
# Importing Controllers
from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
from app.classes.controllers.management_controller import Management_Controller
from app.classes.controllers.users_controller import Users_Controller
@ -29,11 +37,6 @@ from app.classes.controllers.roles_controller import Roles_Controller
from app.classes.controllers.server_perms_controller import Server_Perms_Controller
from app.classes.controllers.servers_controller import Servers_Controller
from app.classes.shared.server import Server
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
logger = logging.getLogger(__name__)
class Controller:
@ -173,7 +176,7 @@ class Controller:
@staticmethod
def add_system_user():
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", helper_users.new_api_token(), False, False)
helper_users.add_user("system", helper.random_string_generator(64), "default@example.com", False, False)
def get_server_settings(self, server_id):
for s in self.servers_list:
@ -183,17 +186,17 @@ class Controller:
logger.warning("Unable to find server object for server id {}".format(server_id))
return False
def get_server_obj(self, server_id):
def get_server_obj(self, server_id: Union[str, int]) -> Union[bool, Server]:
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
if str(s['server_id']) == str(server_id):
return s['server_obj']
logger.warning("Unable to find server object for server id {}".format(server_id))
return False
return False # TODO: Change to None
def get_server_data(self, server_id):
def get_server_data(self, server_id: str):
for s in self.servers_list:
if int(s['server_id']) == int(server_id):
if s['server_id'] == server_id:
return s['server_data_obj']
logger.warning("Unable to find server object for server id {}".format(server_id))
@ -406,7 +409,7 @@ class Controller:
for s in self.servers_list:
# if this is the droid... im mean server we are looking for...
if int(s['server_id']) == int(server_id):
if s['server_id'] == server_id:
server_data = self.get_server_data(server_id)
server_name = server_data['server_name']
backup_dir = self.servers.get_server_data_by_id(server_id)['backup_path']

View File

@ -10,6 +10,10 @@ from app.classes.minecraft.server_props import ServerProps
from app.classes.web.websocket_helper import websocket_helper
# To disable warning about unused import ; Users is imported from here in other places
Users = Users
logger = logging.getLogger(__name__)
peewee_logger = logging.getLogger('peewee')
peewee_logger.setLevel(logging.INFO)
@ -39,20 +43,16 @@ class db_builder:
username = default_data.get("username", 'admin')
password = default_data.get("password", 'crafty')
#api_token = helper.random_string_generator(32)
#
#Users.insert({
# Users.username: username.lower(),
# Users.password: helper.encode_pass(password),
# Users.api_token: api_token,
# Users.enabled: True,
# Users.superuser: True
#}).execute()
user_id = users_helper.add_user(username=username, password=password, email="default@example.com", superuser=True)
#users_helper.update_user(user_id, user_crafty_data={"permissions_mask":"111", "server_quantity":[-1,-1,-1]} )
#console.info("API token is {}".format(api_token))
@staticmethod
def is_fresh_install():
try:

View File

@ -4,13 +4,9 @@ import typing as t
import sys
import os
import re
from importlib import import_module
from functools import wraps
try:
from functools import cached_property
except ImportError:
from cached_property import cached_property
from functools import cached_property
from app.classes.shared.helpers import helper
from app.classes.shared.console import console
@ -21,7 +17,7 @@ try:
import peewee
from playhouse.migrate import (
SchemaMigrator as ScM,
SqliteMigrator as SqM,
SqliteMigrator,
Operation, SQL, operation, SqliteDatabase,
make_index_name, Context
)
@ -32,6 +28,22 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e.name))
sys.exit(1)
MIGRATE_TABLE = 'migratehistory'
MIGRATE_TEMPLATE = '''# Generated by database migrator
import peewee
def migrate(migrator, db):
"""
Write your migrations here.
"""
{migrate}
def rollback(migrator, db):
"""
Write your rollback migrations here.
"""
{rollback}'''
class MigrateHistory(peewee.Model):
"""
@ -41,30 +53,15 @@ class MigrateHistory(peewee.Model):
name = peewee.CharField(unique=True)
migrated_at = peewee.DateTimeField(default=datetime.utcnow)
# noinspection PyTypeChecker
def __unicode__(self) -> str:
"""
String representation of this migration
"""
return self.name
MIGRATE_TABLE = 'migratehistory'
MIGRATE_TEMPLATE = '''# Generated by database migrator
def migrate(migrator, database, **kwargs):
"""
Write your migrations here.
"""
{migrate}
def rollback(migrator, database, **kwargs):
"""
Write your rollback migrations here.
"""
{rollback}'''
VOID: t.Callable = lambda m, d: None
class Meta:
table_name = MIGRATE_TABLE
def get_model(method):
@ -75,11 +72,12 @@ def get_model(method):
@wraps(method)
def wrapper(migrator, model, *args, **kwargs):
if isinstance(model, str):
return method(migrator, migrator.orm[model], *args, **kwargs)
return method(migrator, migrator.table_dict[model], *args, **kwargs)
return method(migrator, model, *args, **kwargs)
return wrapper
# noinspection PyProtectedMember
class Migrator(object):
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
"""
@ -88,8 +86,8 @@ class Migrator(object):
if isinstance(database, peewee.Proxy):
database = database.obj
self.database: SqliteDatabase = database
self.orm: t.Dict[str, peewee.Model] = {}
self.operations: t.List[Operation] = []
self.table_dict: t.Dict[str, peewee.Model] = {}
self.operations: t.List[t.Union[Operation, callable]] = []
self.migrator = SqliteMigrator(database)
def run(self):
@ -113,13 +111,13 @@ class Migrator(object):
"""
Executes raw SQL.
"""
self.operations.append(self.migrator.sql(sql, *params))
self.operations.append(SQL(sql, *params))
def create_table(self, model: peewee.Model) -> peewee.Model:
"""
Creates model and table in database.
"""
self.orm[model._meta.table_name] = model
self.table_dict[model._meta.table_name] = model
model._meta.database = self.database
self.operations.append(model.create_table)
return model
@ -129,8 +127,8 @@ class Migrator(object):
"""
Drops model and table from database.
"""
del self.orm[model._meta.table_name]
self.operations.append(self.migrator.drop_table(model))
del self.table_dict[model._meta.table_name]
self.operations.append(lambda: model.drop_table(cascade=False))
@get_model
def add_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
@ -147,64 +145,16 @@ class Migrator(object):
return model
@get_model
def change_columns(self, model: peewee.Model, **fields: peewee.Field) -> peewee.Model:
"""
Changes fields.
"""
for name, field in fields.items():
old_field = model._meta.fields.get(name, field)
old_column_name = old_field and old_field.column_name
model._meta.add_field(name, field)
if isinstance(old_field, peewee.ForeignKeyField):
self.operations.append(self.migrator.drop_foreign_key_constraint(
model._meta.table_name, old_column_name))
if old_column_name != field.column_name:
self.operations.append(
self.migrator.rename_column(
model._meta.table_name, old_column_name, field.column_name))
if isinstance(field, peewee.ForeignKeyField):
on_delete = field.on_delete if field.on_delete else 'RESTRICT'
on_update = field.on_update if field.on_update else 'RESTRICT'
self.operations.append(self.migrator.add_foreign_key_constraint(
model._meta.table_name, field.column_name,
field.rel_model._meta.table_name, field.rel_field.name,
on_delete, on_update))
continue
self.operations.append(self.migrator.change_column(
model._meta.table_name, field.column_name, field))
if field.unique == old_field.unique:
continue
if field.unique:
index = (field.column_name,), field.unique
self.operations.append(self.migrator.add_index(
model._meta.table_name, *index))
model._meta.indexes.append(index)
else:
index = (field.column_name,), old_field.unique
self.operations.append(self.migrator.drop_index(
model._meta.table_name, *index))
model._meta.indexes.remove(index)
return model
@get_model
def drop_columns(self, model: peewee.Model, names: str, **kwargs) -> peewee.Model:
def drop_columns(self, model: peewee.Model, names: str) -> peewee.Model:
"""
Removes fields from model.
"""
fields = [field for field in model._meta.fields.values()
if field.name in names]
cascade = kwargs.pop('cascade', True)
for field in fields:
self.__del_field__(model, field)
if field.unique:
# Drop unique index
index_name = make_index_name(
model._meta.table_name, [field.column_name])
self.operations.append(self.migrator.drop_index(
@ -250,16 +200,15 @@ class Migrator(object):
Renames table in database.
"""
old_name = model._meta.table_name
del self.orm[model._meta.table_name]
del self.table_dict[model._meta.table_name]
model._meta.table_name = new_name
self.orm[model._meta.table_name] = model
self.table_dict[model._meta.table_name] = model
self.operations.append(self.migrator.rename_table(old_name, new_name))
return model
@get_model
def add_index(self, model: peewee.Model, *columns: str, **kwargs) -> peewee.Model:
def add_index(self, model: peewee.Model, *columns: str, unique=False) -> peewee.Model:
"""Create indexes."""
unique = kwargs.pop('unique', False)
model._meta.indexes.append((columns, unique))
columns_ = []
for col in columns:
@ -329,42 +278,8 @@ class Migrator(object):
return model
class SqliteMigrator(SqM):
def drop_table(self, model):
return lambda: model.drop_table(cascade=False)
@operation
def change_column(self, table: str, column_name: str, field: peewee.Field):
operations = [self.alter_change_column(table, column_name, field)]
if not field.null:
operations.extend([self.add_not_null(table, column_name)])
return operations
def alter_change_column(self, table: str, column_name: str, field: peewee.Field) -> Operation:
return self._update_column(table, column_name, lambda x, y: y)
@operation
def sql(self, sql: str, *params) -> SQL:
"""
Executes raw SQL.
"""
return SQL(sql, *params)
def alter_add_column(
self, table: str, column_name: str, field: peewee.Field, **kwargs) -> Operation:
"""
Fixes field name for ForeignKeys.
"""
name = field.name
op = super().alter_add_column(
table, column_name, field, **kwargs)
if isinstance(field, peewee.ForeignKeyField):
field.name = name
return op
# noinspection PyProtectedMember
class MigrationManager(object):
filemask = re.compile(r"[\d]+_[^\.]+\.py$")
def __init__(self, database: t.Union[peewee.Database, peewee.Proxy]):
@ -376,7 +291,7 @@ class MigrationManager(object):
self.database = database
@cached_property
def model(self) -> peewee.Model:
def model(self) -> t.Type[MigrateHistory]:
"""
Initialize and cache the MigrationHistory model.
"""
@ -487,7 +402,7 @@ class MigrationManager(object):
scope = {}
code = compile(code, '<string>', 'exec', dont_inherit=True)
exec(code, scope, None)
return scope.get('migrate', VOID), scope.get('rollback', VOID)
return scope.get('migrate', lambda m, d: None), scope.get('rollback', lambda m, d: None)
def up_one(self, name: str, migrator: Migrator,
fake: bool = False, rollback: bool = False) -> str:
@ -518,11 +433,11 @@ class MigrationManager(object):
except Exception:
self.database.rollback()
operation = 'Rollback' if rollback else 'Migration'
logger.exception('{} failed: {}'.format(operation, name))
operation_name = 'Rollback' if rollback else 'Migration'
logger.exception('{} failed: {}'.format(operation_name, name))
raise
def down(self, name: t.Optional[str] = None):
def down(self):
"""
Rolls back migrations.
"""

View File

@ -0,0 +1,23 @@
from enum import Enum
class PermissionHelper:
@staticmethod
def both_have_perm(a: str, b: str, permission_tested: Enum):
return permission_helper.combine_perm_bool(a[permission_tested.value], b[permission_tested.value])
@staticmethod
def combine_perm(a: str, b: str) -> str:
return '1' if (a == '1' and b == '1') else '0'
@staticmethod
def combine_perm_bool(a: str, b: str) -> bool:
return a == '1' and b == '1'
@staticmethod
def combine_masks(permission_mask_a: str, permission_mask_b: str) -> str:
both_masks = zip(list(permission_mask_a), list(permission_mask_b))
return ''.join(map(lambda x: permission_helper.combine_perm(x[0], x[1]), both_masks))
permission_helper = PermissionHelper()

View File

@ -35,13 +35,13 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated
def get(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
_, _, exec_user = self.current_user
error = bleach.clean(self.get_argument('error', "WTF Error!"))
template = "panel/denied.html"
page_data = {
'user_data': user_data,
'user_data': exec_user,
'error': error
}
@ -164,10 +164,13 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated
def post(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -178,17 +181,17 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
error = bleach.clean(self.get_argument('error', "WTF Error!"))
page_data = {
'user_data': user_data,
'user_data': exec_user,
'error': error
}
if page == "send_command":
command = self.get_body_argument('command', default=None, strip=True)
server_id = self.get_argument('id')
server_id = self.get_argument('id', None)
if server_id is None:
logger.warning("Server ID not found in send_command ajax call")
@ -200,11 +203,11 @@ class AjaxHandler(BaseHandler):
if srv_obj.check_running():
srv_obj.send_command(command)
self.controller.management.add_to_audit_log(user_data['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
self.controller.management.add_to_audit_log(exec_user['user_id'], "Sent command to {} terminal: {}".format(self.controller.servers.get_server_friendly_name(server_id), command), server_id, self.get_remote_ip())
elif page == "create_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_parent = helper.get_os_understandable_path(self.get_body_argument('file_parent', default=None, strip=True))
@ -227,7 +230,7 @@ class AjaxHandler(BaseHandler):
elif page == "create_dir":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
dir_parent = helper.get_os_understandable_path(self.get_body_argument('dir_parent', default=None, strip=True))
@ -248,7 +251,7 @@ class AjaxHandler(BaseHandler):
elif page == "unzip_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
server_id = self.get_argument('id', None)
@ -259,7 +262,7 @@ class AjaxHandler(BaseHandler):
elif page == "kill":
if not permissions['Commands'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Commands")
return
server_id = self.get_argument('id', None)
@ -272,11 +275,11 @@ class AjaxHandler(BaseHandler):
elif page == "eula":
server_id = self.get_argument('id', None)
svr = self.controller.get_server_obj(server_id)
svr.agree_eula(user_data['user_id'])
svr.agree_eula(exec_user['user_id'])
elif page == "restore_backup":
if not permissions['Backup'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups")
return
server_id = bleach.clean(self.get_argument('id', None))
@ -295,16 +298,21 @@ class AjaxHandler(BaseHandler):
elif page == "unzip_server":
path = self.get_argument('path', None)
helper.unzipServer(path, exec_user_id)
helper.unzipServer(path, exec_user['user_id'])
return
@tornado.web.authenticated
def delete(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -315,10 +323,10 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
if page == "del_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
@ -350,7 +358,7 @@ class AjaxHandler(BaseHandler):
if page == "del_backup":
if not permissions['Backup'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Backups")
return
file_path = helper.get_os_understandable_path(self.get_body_argument('file_path', default=None, strip=True))
@ -376,7 +384,7 @@ class AjaxHandler(BaseHandler):
elif page == "del_dir":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
dir_path = helper.get_os_understandable_path(self.get_body_argument('dir_path', default=None, strip=True))
@ -401,7 +409,7 @@ class AjaxHandler(BaseHandler):
elif page == "delete_server":
if not permissions['Config'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument('id', None)
@ -411,7 +419,7 @@ class AjaxHandler(BaseHandler):
elif page == "delete_server_files":
if not permissions['Config'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Config")
return
server_id = self.get_argument('id', None)
@ -421,10 +429,12 @@ class AjaxHandler(BaseHandler):
@tornado.web.authenticated
def put(self, page):
user_data = json.loads(self.get_secure_cookie("user_data"))
api_key, _, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
server_id = self.get_argument('id', None)
exec_user_id = user_data['user_id']
exec_user = helper_users.get_user(exec_user_id)
permissions = {
'Commands': Enum_Permissions_Server.Commands,
'Terminal': Enum_Permissions_Server.Terminal,
@ -435,10 +445,10 @@ class AjaxHandler(BaseHandler):
'Config': Enum_Permissions_Server.Config,
'Players': Enum_Permissions_Server.Players,
}
user_perms = self.controller.server_perms.get_server_permissions_foruser(exec_user_id, server_id)
user_perms = self.controller.server_perms.get_user_id_permissions_list(exec_user['user_id'], server_id)
if page == "save_file":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
file_contents = self.get_body_argument('file_contents', default=None, strip=True)
@ -460,7 +470,7 @@ class AjaxHandler(BaseHandler):
elif page == "rename_item":
if not permissions['Files'] in user_perms:
if not exec_user['superuser']:
if not superuser:
self.redirect("/panel/error?error=Unauthorized access to Files")
return
item_path = helper.get_os_understandable_path(self.get_body_argument('item_path', default=None, strip=True))

View File

@ -1,14 +1,11 @@
import os
import secrets
import threading
import tornado.web
import tornado.escape
import logging
import re
from app.classes.web.base_handler import BaseHandler
log = logging.getLogger(__name__)
bearer_pattern = re.compile(r'^Bearer', flags=re.IGNORECASE)
class ApiHandler(BaseHandler):
@ -28,8 +25,14 @@ class ApiHandler(BaseHandler):
def authenticate_user(self) -> bool:
try:
log.debug("Searching for specified token")
# TODO: YEET THIS
user_data = self.controller.users.get_user_by_api_token(self.get_argument('token'))
api_token = self.get_argument('token', '')
if api_token is None and self.request.headers.get('Authorization'):
api_token = bearer_pattern.sub('', self.request.headers.get('Authorization'))
elif api_token is None:
api_token = self.get_cookie('token')
user_data = self.controller.users.get_user_by_api_token(api_token)
log.debug("Checking results")
if user_data:
# Login successful! Check perms
@ -40,11 +43,11 @@ class ApiHandler(BaseHandler):
else:
logging.debug("Auth unsuccessful")
self.access_denied("unknown", "the user provided an invalid token")
return
return False
except Exception as e:
log.warning("An error occured while authenticating an API user: %s", e)
self.access_denied("unknown"), "an error occured while authenticating the user"
return
return False
class ServersStats(ApiHandler):

View File

@ -4,10 +4,12 @@ import bleach
from typing import (
Union,
List,
Optional
Optional, Tuple, Dict, Any
)
from app.classes.shared.authentication import authentication
from app.classes.shared.main_controller import Controller
from app.classes.models.users import ApiKeys
logger = logging.getLogger(__name__)
@ -17,7 +19,8 @@ class BaseHandler(tornado.web.RequestHandler):
nobleach = {bool, type(None)}
redactables = ("pass", "api")
def initialize(self, controller : Controller = None, tasks_manager=None, translator=None):
# noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller = None, tasks_manager=None, translator=None):
self.controller = controller
self.tasks_manager = tasks_manager
self.translator = translator
@ -28,8 +31,9 @@ class BaseHandler(tornado.web.RequestHandler):
self.request.remote_ip
return remote_ip
def get_current_user(self):
return self.get_secure_cookie("user", max_age_days=1)
current_user: Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]
def get_current_user(self) -> Optional[Tuple[Optional[ApiKeys], Dict[str, Any], Dict[str, Any]]]:
return authentication.check(self.get_cookie("token"))
def autobleach(self, name, text):
for r in self.redactables:

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import requests
import tornado.web
import tornado.escape
from app.classes.shared.authentication import authentication
from app.classes.shared.helpers import Helpers, helper
from app.classes.web.base_handler import BaseHandler
from app.classes.shared.console import console
@ -27,7 +28,7 @@ except ModuleNotFoundError as e:
class PublicHandler(BaseHandler):
def set_current_user(self, user):
def set_current_user(self, user_id: str = None):
expire_days = helper.get_setting('cookie_expire')
@ -35,8 +36,8 @@ class PublicHandler(BaseHandler):
if not expire_days:
expire_days = "5"
if user:
self.set_secure_cookie("user", tornado.escape.json_encode(user), expires_days=int(expire_days))
if user_id is not None:
self.set_cookie("token", authentication.generate(user_id), expires_days=int(expire_days))
else:
self.clear_cookie("user")
@ -45,12 +46,7 @@ class PublicHandler(BaseHandler):
error = bleach.clean(self.get_argument('error', "Invalid Login!"))
error_msg = bleach.clean(self.get_argument('error_msg', ''))
page_data = {
'version': helper.get_version_string(),
'error': error
}
page_data['lang'] = tornado.locale.get("en_EN")
page_data = {'version': helper.get_version_string(), 'error': error, 'lang': tornado.locale.get("en_EN")}
# sensible defaults
template = "public/404.html"
@ -112,7 +108,7 @@ class PublicHandler(BaseHandler):
# Valid Login
if login_result:
self.set_current_user(entered_username)
self.set_current_user(user_data.user_id)
logger.info("User: {} Logged in from IP: {}".format(user_data, self.get_remote_ip()))
# record this login
@ -140,15 +136,6 @@ class PublicHandler(BaseHandler):
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
else:
profile_url = "/static/assets/images/faces-clipart/pic-3.png"
cookie_data = {
"username": user_data.username,
"user_id": user_data.user_id,
"email": user_data.email,
"profile_url": profile_url,
"account_type": user_data.superuser,
}
self.set_secure_cookie('user_data', json.dumps(cookie_data))
next_page = "/panel/dashboard"
self.redirect(next_page)

View File

@ -28,13 +28,13 @@ class ServerHandler(BaseHandler):
@tornado.web.authenticated
def get(self, page):
# name = tornado.escape.json_decode(self.current_user)
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user_id = exec_user_data['user_id']
exec_user = self.controller.users.get_user_by_id(exec_user_id)
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
exec_user_role = set()
if exec_user['superuser'] == 1:
if superuser:
defined_servers = self.controller.list_defined_servers()
exec_user_role.add("Super User")
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
@ -42,8 +42,8 @@ class ServerHandler(BaseHandler):
for role in self.controller.roles.get_all_roles():
list_roles.append(self.controller.roles.get_role(role.role_id))
else:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user_id)
defined_servers = self.controller.servers.get_authorized_servers(exec_user_id)
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(exec_user["user_id"])
defined_servers = self.controller.servers.get_authorized_servers(exec_user["user_id"])
list_roles = []
for r in exec_user['roles']:
role = self.controller.roles.get_role(r)
@ -54,7 +54,7 @@ class ServerHandler(BaseHandler):
page_data = {
'version_data': helper.get_version_string(),
'user_data': exec_user_data,
'user_data': exec_user,
'user_role' : exec_user_role,
'roles' : list_roles,
'user_crafty_permissions' : exec_user_crafty_permissions,
@ -71,13 +71,13 @@ class ServerHandler(BaseHandler):
'hosts_data': self.controller.management.get_latest_hosts_stats(),
'menu_servers': defined_servers,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"])
}
if exec_user['superuser'] == 1:
if superuser == 1:
page_data['roles'] = list_roles
if page == "step1":
if not exec_user['superuser'] and not self.controller.crafty_perms.can_create_server(exec_user_id):
if not superuser and not self.controller.crafty_perms.can_create_server(exec_user["user_id"]):
self.redirect("/panel/error?error=Unauthorized access: not a server creator or server limit reached")
return
@ -93,17 +93,17 @@ class ServerHandler(BaseHandler):
@tornado.web.authenticated
def post(self, page):
exec_user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user_id = exec_user_data['user_id']
exec_user = self.controller.users.get_user_by_id(exec_user_id)
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
template = "public/404.html"
page_data = {
'version_data': "version_data_here",
'user_data': exec_user_data,
'version_data': "version_data_here", # TODO
'user_data': exec_user,
'show_contribute': helper.get_setting("show_contribute_link", True),
'lang': self.controller.users.get_user_lang_by_id(exec_user_id)
'lang': self.controller.users.get_user_lang_by_id(exec_user["user_id"])
}
if page == "command":
@ -151,11 +151,11 @@ class ServerHandler(BaseHandler):
return
self.controller.management.send_command(exec_user_data['user_id'], server_id, self.get_remote_ip(), command)
self.controller.management.send_command(exec_user['user_id'], server_id, self.get_remote_ip(), command)
if page == "step1":
if not exec_user['superuser']:
if not superuser:
user_roles = self.controller.roles.get_all_roles()
else:
user_roles = self.controller.roles.get_all_roles()
@ -185,7 +185,7 @@ class ServerHandler(BaseHandler):
return
new_server_id = self.controller.import_jar_server(server_name, import_server_path,import_server_jar, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"imported a jar server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
@ -201,7 +201,7 @@ class ServerHandler(BaseHandler):
if new_server_id == "false":
self.redirect("/panel/error?error=Zip file not accessible! You can fix this permissions issue with sudo chown -R crafty:crafty {} And sudo chmod 2775 -R {}".format(import_server_path, import_server_path))
return
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"imported a zip server named \"{}\"".format(server_name), # Example: Admin imported a server named "old creative"
new_server_id,
self.get_remote_ip())
@ -213,21 +213,21 @@ class ServerHandler(BaseHandler):
return
server_type, server_version = server_parts
# TODO: add server type check here and call the correct server add functions if not a jar
role_ids = self.controller.users.get_user_roles_id(exec_user_id)
role_ids = self.controller.users.get_user_roles_id(exec_user["user_id"])
new_server_id = self.controller.create_jar_server(server_type, server_version, server_name, min_mem, max_mem, port)
self.controller.management.add_to_audit_log(exec_user_data['user_id'],
self.controller.management.add_to_audit_log(exec_user['user_id'],
"created a {} {} server named \"{}\"".format(server_version, str(server_type).capitalize(), server_name), # Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip())
# These lines create a new Role for the Server with full permissions and add the user to it if he's not a superuser
if len(captured_roles) == 0:
if not exec_user['superuser']:
if not superuser:
new_server_uuid = self.controller.servers.get_server_data_by_id(new_server_id).get("server_uuid")
role_id = self.controller.roles.add_role("Creator of Server with uuid={}".format(new_server_uuid))
self.controller.server_perms.add_role_server(new_server_id, role_id, "11111111")
self.controller.users.add_role_to_user(exec_user_id, role_id)
self.controller.crafty_perms.add_server_creation(exec_user_id)
self.controller.users.add_role_to_user(exec_user["user_id"], role_id)
self.controller.crafty_perms.add_server_creation(exec_user["user_id"])
else:
for role in captured_roles:

View File

@ -20,6 +20,7 @@ MAX_STREAMED_SIZE = 1024 * 1024 * 1024
@tornado.web.stream_request_body
class UploadHandler(tornado.web.RequestHandler):
# noinspection PyAttributeOutsideInit
def initialize(self, controller: Controller=None, tasks_manager=None, translator=None):
self.controller = controller
self.tasks_manager = tasks_manager
@ -27,8 +28,19 @@ class UploadHandler(tornado.web.RequestHandler):
def prepare(self):
self.do_upload = True
user_data = json.loads(self.get_secure_cookie('user_data'))
user_id = user_data['user_id']
api_key, token_data, exec_user = self.current_user
superuser = exec_user['superuser']
if api_key is not None:
superuser = superuser and api_key.superuser
user_id = exec_user['user_id']
if superuser:
exec_user_crafty_permissions = self.controller.crafty_perms.list_defined_crafty_permissions()
elif api_key is not None:
exec_user_crafty_permissions = self.controller.crafty_perms.get_api_key_permissions_list(api_key)
else:
exec_user_crafty_permissions = self.controller.crafty_perms.get_crafty_permissions_list(
exec_user["user_id"])
server_id = self.request.headers.get('X-ServerId', None)
@ -42,8 +54,7 @@ class UploadHandler(tornado.web.RequestHandler):
console.warning('Server ID not found in upload handler call')
self.do_upload = False
user_permissions = self.controller.server_perms.get_user_permissions_list(user_id, server_id)
if Enum_Permissions_Server.Files not in user_permissions:
if Enum_Permissions_Server.Files not in exec_user_crafty_permissions:
logger.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
console.warning(f'User {user_id} tried to upload a file to {server_id} without permissions!')
self.do_upload = False

View File

@ -5,6 +5,7 @@ import sys
from urllib.parse import parse_qsl
from app.classes.models.users import Users
from app.classes.shared.authentication import authentication
from app.classes.shared.helpers import helper
from app.classes.web.websocket_helper import websocket_helper
from app.classes.shared.console import console
@ -19,7 +20,14 @@ except ModuleNotFoundError as e:
console.critical("Import Error: Unable to load {} module".format(e, e.name))
sys.exit(1)
class SocketHandler(tornado.websocket.WebSocketHandler):
page = None
page_query_params = None
controller = None
tasks_manager = None
translator = None
io_loop = None
def initialize(self, controller=None, tasks_manager=None, translator=None):
self.controller = controller
@ -34,24 +42,11 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
return remote_ip
def get_user_id(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
user_data_cookie = user_data_cookie_raw.decode('utf-8')
user_id = json.loads(user_data_cookie)['user_id']
return user_id
_, _, user = authentication.check(self.get_cookie('token'))
return user.user_id
def check_auth(self):
user_data_cookie_raw = self.get_secure_cookie('user_data')
if user_data_cookie_raw and user_data_cookie_raw.decode('utf-8'):
user_data_cookie = user_data_cookie_raw.decode('utf-8')
user_id = json.loads(user_data_cookie)['user_id']
query = Users.select().where(Users.user_id == user_id)
if query.exists():
return True
return False
return authentication.check_bool(self.get_cookie('token'))
def open(self):
logger.debug('Checking WebSocket authentication')
@ -74,10 +69,11 @@ class SocketHandler(tornado.websocket.WebSocketHandler):
logger.debug('Opened WebSocket connection')
# websocket_helper.broadcast('notification', 'New client connected')
def on_message(self, rawMessage):
@staticmethod
def on_message(raw_message):
logger.debug('Got message from WebSocket connection {}'.format(rawMessage))
message = json.loads(rawMessage)
logger.debug('Got message from WebSocket connection {}'.format(raw_message))
message = json.loads(raw_message)
logger.debug('Event Type: {}, Data: {}'.format(message['event'], message['data']))
def on_close(self):

View File

@ -5,6 +5,7 @@
"language": "en_EN",
"cookie_expire": 30,
"cookie_secret": "random",
"apikey_secret": "random",
"show_errors": true,
"history_max_age": 7,
"stats_update_frequency": 30,

View File

@ -18,19 +18,22 @@
<li class="nav-item dropdown user-dropdown">
<a class="nav-link dropdown-toggle" id="UserDropdown" href="#" data-toggle="dropdown" aria-expanded="false">
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image"> </a>
<img class="img-xs rounded-circle profile-picture" src="{{ data['user_data'].get('profile_url') }}" alt="Profile image"> </a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="UserDropdown">
<div class="dropdown-header text-center">
<img class="img-md rounded-circle profile-picture" src="{{ data['user_data']['profile_url'] }}" alt="Profile image">
<img class="img-md rounded-circle profile-picture" src="{{ data['user_data'].get('profile_url') }}" alt="Profile image">
<p class="mb-1 mt-3 font-weight-semibold">{{ data['user_data']['username'] }}</p>
<p class="font-weight-light text-muted mb-0">Roles: </p>
{% for r in data['user_role'] %}
<p class="font-weight-light text-muted mb-0">{{ r }}</p>
{% end %}
{% if data['api_key'] %}
<p class="mt-3">Logged in as API key "{{ data['api_key']['name'] }}"</p>
{% end %}
<p class="font-weight-light text-muted mb-0">Email: {{ data['user_data']['email'] }}</p>
</div>
<a class="dropdown-item" href="/panel/support_logs"><i class="dropdown-item-icon mdi mdi-download-outline text-primary"></i> Support Logs</i></a>
{% if "Super User" in data['user_role'] %}
{% if data['superuser'] %}
<a class="dropdown-item" href="/panel/activity_logs"><i class="dropdown-item-icon mdi mdi-calendar-check-outline text-primary"></i> Activity</a>
{% end %}
<a class="dropdown-item" href="/public/logout"><i class="dropdown-item-icon mdi mdi-power text-primary"></i>Sign Out</a>

View File

@ -42,7 +42,6 @@
<tr class="rounded">
<th>User</th>
<th>Enabled</th>
<th>API Token</th>
<th>Allowed Servers</th>
<th>Assigned Roles</th>
<th>Edit</th>
@ -64,9 +63,6 @@
{% end %}
</td>
<td>
<button data-toggle="tooltip" title="Show API Key" data-id="{{ user.api_token }}" type="button" class="btn btn-info show_button">Show</button>
</td>
<td id="server_list_{{user.user_id}}">
<ul id="{{user.user_id}}">
{% for item in data['auth-servers'][user.user_id] %}

View File

@ -229,7 +229,6 @@
$( document ).ready(function() {
console.log( "ready!" );
});

View File

@ -44,6 +44,10 @@
<i class="fas fa-cogs"></i>Config</a>
</li>
{% if not data['new_user'] %}
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}" role="tab" aria-selected="false">
<i class="fas fa-key"></i>API Keys</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/add_user?id={{ data['user']['user_id'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
@ -177,14 +181,6 @@
{% end %}
</label>
<label for="regen_api" class="form-check-label ml-4 mb-4">
{% if data['new_user'] %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" checked="" value="1" disabled >Regenerate API Key
{% else %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" value="1">Regenerate API Key
{% end %}
</label>
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['user']['superuser'] %}
<input type="checkbox" onclick="superConfirm()" class="form-check-input" id="superuser" name="superuser" checked="" value="1" {{ data['super-disabled'] }} >Super User
@ -215,7 +211,7 @@
<br />
Last IP: {{ data['user']['last_ip'] }}
<br />
API Key: {{ data['user']['api_token'] }}
API Key: TODO! <!-- TODO -->
<br />
</p>
</blockquote>

View File

@ -0,0 +1,254 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit User API Keys{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
Edit User API Keys - {{ data['user']['user_id'] }}
<br/>
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['user_id'] }}&subpage=config"
role="tab"
aria-selected="false">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user_apikeys?id={{ data['user']['user_id'] }}"
role="tab"
aria-selected="true">
<i class="fas fa-key"></i>API Keys</a>
</li>
</ul>
<div class="row">
<div class="col-md-7 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-key"></i> API Keys</h4>
</div>
<div class="card-body">
<div class="form-group">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr class="rounded">
<!--<th>ID</th>-->
<th>Name</th>
<th>Created</th>
<th>Superuser</th>
<th>Permissions</th>
<th>Buttons</th>
</tr>
</thead>
<tbody>
{% for apikey in data['api_keys'] %}
<tr>
<!--<td>{-{ apikey.token_id }-}</td>-->
<td>{{ apikey.name }}</td>
<td>{{ apikey.created.strftime('%d/%m/%Y %H:%M:%S') }}</td>
<td>
{% if apikey.superuser %}
<span class="text-success">
<i class="fas fa-check-square"></i> Yes
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> No
</span>
{% end %}
</td>
<td>Server: {{ apikey.server_permissions }}
Crafty: {{ apikey.crafty_permissions }}</td>
<td>
<button
class="btn btn-danger delete-api-key"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>{{ translate('panelConfig', 'delete', data['lang']) }}</button>
<button
class="btn btn-outline-primary get-a-token"
data-key-id="{{ apikey.token_id }}"
data-key-name="{{ apikey.name }}"
>Get a token
</button>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-5 col-sm-12">
<div class="card">
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-plus"></i> Create new API key</h4>
</div>
<div class="card-body">
<form id="user_form" class="forms-sample" method="post"
action="/panel/edit_user_apikeys">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<div class="form-group">
<label class="form-label" for="username">Name <small
class="text-muted ml-1"> - What you wish to
call this API key</small> </label>
<input type="text" class="form-control" name="name" id="name"
placeholder="API Key">
</div>
<table class="table table-hover mb-3">
<thead>
<tr class="rounded">
<th>Permission Name</th>
<th>Authorized ?</th>
</tr>
</thead>
<tbody>
{% for permission in data['server_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
{% for permission in data['crafty_permissions_all'] %}
<tr>
<td><label
for="permission_{{ permission.name }}">{{ permission.name }}</label>
</td>
<td>
<input type="checkbox" class=""
id="permission_{{ permission.name }}"
name="permission_{{ permission.name }}" value="1">
</td>
</tr>
{% end %}
</tbody>
</table>
<label for="superuser">Superuser</label>
<input type="checkbox" class="" id="superuser"
name="superuser" value="1">
<br/>
<button type="submit" class="btn btn-success mr-2"><i class="fas fa-plus"></i>
Create
</button>
<button type="reset" class="btn btn-light"><i
class="fas fa-undo-alt"></i> {{ translate('panelConfig', 'cancel', data['lang']) }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$(document).ready(function () {
console.log("ready!");
$('.delete-api-key').click(function () {
var keyId = $(this).data("key-id");
var keyName = $(this).data("key-name");
bootbox.confirm({
title: `Remove API key ${keyName}?`,
message: "Do you want to delete this API key? This cannot be undone.",
buttons: {
cancel: {
label: '<i class="fas fa-times"></i> {{ translate("panelConfig", "cancel", data['lang']) }}'
},
confirm: {
label: '<i class="fas fa-check"></i> {{ translate("serverBackups", "confirm", data['lang']) }}'
}
},
callback: function (result) {
var token = getCookie("_xsrf")
$.ajax({
type: "DELETE",
headers: {'X-XSRFToken': token},
url: '/panel/remove_apikey?id=' + keyId,
success: function (data) {
location.reload();
},
});
}
});
})
$('.get-a-token').click(function () {
var keyId = $(this).data("key-id");
var keyName = $(this).data("key-name");
var token = getCookie("_xsrf")
$.ajax({
type: "POST",
headers: {'X-XSRFToken': token},
url: '/panel/get_token?id=' + keyId,
success: function (data) {
bootbox.alert({
title: `API token for ${keyName}`,
message: `Here is an API token for ${keyName}:\n<pre style="white-space: pre-wrap;color:white;word-break:break-all;background: grey;border-radius: 5px;">${data}</pre>`
});
},
});
})
});
</script>
{% end %}

View File

@ -0,0 +1,12 @@
import peewee
import datetime
def migrate(migrator, database, **kwargs):
migrator.add_columns('users', valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now))
migrator.drop_columns('users', ['api_token'])
def rollback(migrator, database, **kwargs):
migrator.drop_columns('users', ['valid_tokens_from'])
migrator.add_columns('users', api_token=peewee.CharField(default="", unique=True, index=True))

View File

@ -0,0 +1,23 @@
import peewee
import datetime
from app.classes.models.users import Users
def migrate(migrator, db):
class ApiKeys(peewee.Model):
token_id = peewee.AutoField()
name = peewee.CharField(default='', unique=True, index=True)
created = peewee.DateTimeField(default=datetime.datetime.now)
user = peewee.ForeignKeyField(Users, backref='api_token', index=True)
server_permissions = peewee.CharField(default='00000000')
crafty_permissions = peewee.CharField(default='000')
superuser = peewee.BooleanField(default=False)
class Meta:
table_name = 'api_keys'
migrator.create_table(ApiKeys)
def rollback(migrator, db):
migrator.drop_table('api_keys')

View File

@ -370,5 +370,10 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Warning: </strong>Crafty doesn't work properly when JavaScript isn't enabled!"
},
"apiKeys": {
"deleteKeyConfirmation": "Do you want to delete this API key? This cannot be undone.",
"deleteKeyConfirmationTitle": "Remove API key ${keyId}?"
}
}

View File

@ -371,5 +371,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Varoitus: </strong>Crafty ei toimi kunnolla ilman JavaScriptiä!"
},
"apiKeys": {
"deleteKeyConfirmation": "Haluatko varmasti poistaa tämän API-avaimen? Tämä on peruuttamaton toimenpide!",
"deleteKeyConfirmationTitle": "Poistetaanko API-avain ${keyId}?"
}
}

View File

@ -346,5 +346,9 @@
},
"base": {
"doesNotWorkWithoutJavascript": "<strong>Attention: </strong>Crafty ne fonctionne pas correctement si JavaScript n'est pas activé !"
},
"apiKeys": {
"deleteKeyConfirmation": "Es-tu sûr de vouloir supprimer cette clé API? Tu ne pourras plus revenir en arrière.",
"deleteKeyConfirmationTitle": "Supprimer la clé API ${keyId}?"
}
}

View File

@ -16,3 +16,4 @@ cached_property==1.5.2
apscheduler~=3.8.1
cron-validator~=1.0.3
tzlocal~=4.1
pyjwt~=2.3