Add and fix API v2 and db

* Add basic role routes
* Add API v2 404 handler
* Add API v2 home handler pointing to the wiki
* Add tons more todos
* Add get_*_columns and get_*_column functions for many db models
* Modify and add tons of model and controller functions
This commit is contained in:
luukas 2022-05-08 00:07:55 +03:00
parent 721c9cfe12
commit bf59e2de6c
18 changed files with 317 additions and 61 deletions

View File

@ -66,7 +66,7 @@ class CraftyPermsController:
@staticmethod @staticmethod
def add_server_creation(user_id): def add_server_creation(user_id):
"""Increase the "Server Creation" counter for this use """Increase the "Server Creation" counter for this user
Args: Args:
user_id (int): The modifiable user's ID user_id (int): The modifiable user's ID

View File

@ -16,6 +16,10 @@ class RolesController:
def get_all_roles(): def get_all_roles():
return HelperRoles.get_all_roles() return HelperRoles.get_all_roles()
@staticmethod
def get_all_role_ids():
return HelperRoles.get_all_role_ids()
@staticmethod @staticmethod
def get_roleid_by_name(role_name): def get_roleid_by_name(role_name):
return HelperRoles.get_roleid_by_name(role_name) return HelperRoles.get_roleid_by_name(role_name)
@ -36,8 +40,12 @@ class RolesController:
if key == "role_id": if key == "role_id":
continue continue
elif key == "servers": elif key == "servers":
added_servers = role_data["servers"].difference(base_data["servers"]) added_servers = set(role_data["servers"]).difference(
removed_servers = base_data["servers"].difference(role_data["servers"]) set(base_data["servers"])
)
removed_servers = set(base_data["servers"]).difference(
set(role_data["servers"])
)
elif base_data[key] != role_data[key]: elif base_data[key] != role_data[key]:
up_data[key] = role_data[key] up_data[key] = role_data[key]
up_data["last_update"] = Helpers.get_time_as_string() up_data["last_update"] = Helpers.get_time_as_string()
@ -73,12 +81,8 @@ class RolesController:
role = HelperRoles.get_role(role_id) role = HelperRoles.get_role(role_id)
if role: if role:
servers_query = PermissionsServers.get_servers_from_role(role_id) server_ids = PermissionsServers.get_server_ids_from_role(role_id)
# TODO: this query needs to be narrower role["servers"] = list(server_ids)
servers = set()
for s in servers_query:
servers.add(s.server_id.server_id)
role["servers"] = servers
# logger.debug("role: ({}) {}".format(role_id, role)) # logger.debug("role: ({}) {}".format(role_id, role))
return role return role
else: else:

View File

@ -1,6 +1,5 @@
import logging import logging
from typing import Optional import typing as t
import typing
from app.classes.models.users import HelperUsers from app.classes.models.users import HelperUsers
from app.classes.models.crafty_permissions import ( from app.classes.models.crafty_permissions import (
@ -28,7 +27,7 @@ class UsersController:
"quantity": {"type": "number", "minimum": 0}, "quantity": {"type": "number", "minimum": 0},
"enabled": {"type": "boolean"}, "enabled": {"type": "boolean"},
} }
self.user_jsonschema_props: typing.Final = { self.user_jsonschema_props: t.Final = {
"username": { "username": {
"type": "string", "type": "string",
"maxLength": 20, "maxLength": 20,
@ -90,7 +89,7 @@ class UsersController:
return HelperUsers.get_all_users() return HelperUsers.get_all_users()
@staticmethod @staticmethod
def get_all_user_ids(): def get_all_user_ids() -> t.List[int]:
return HelperUsers.get_all_user_ids() return HelperUsers.get_all_user_ids()
@staticmethod @staticmethod
@ -134,8 +133,12 @@ class UsersController:
if key == "user_id": if key == "user_id":
continue continue
elif key == "roles": elif key == "roles":
added_roles = user_data["roles"].difference(base_data["roles"]) added_roles = set(user_data["roles"]).difference(
removed_roles = base_data["roles"].difference(user_data["roles"]) set(base_data["roles"])
)
removed_roles = set(base_data["roles"]).difference(
set(user_data["roles"])
)
elif key == "password": elif key == "password":
if user_data["password"] is not None and user_data["password"] != "": if user_data["password"] is not None and user_data["password"] != "":
up_data["password"] = self.helper.encode_pass(user_data["password"]) up_data["password"] = self.helper.encode_pass(user_data["password"])
@ -177,14 +180,12 @@ class UsersController:
self.users_helper.update_user(user_id, up_data) self.users_helper.update_user(user_id, up_data)
def raw_update_user( def raw_update_user(self, user_id: int, up_data: t.Optional[t.Dict[str, t.Any]]):
self, user_id: int, up_data: typing.Optional[typing.Dict[str, typing.Any]]
):
"""Directly passes the data to the model helper. """Directly passes the data to the model helper.
Args: Args:
user_id (int): The id of the user to update. user_id (int): The id of the user to update.
up_data (typing.Optional[typing.Dict[str, typing.Any]]): Update data. up_data (t.Optional[t.Dict[str, t.Any]]): Update data.
""" """
self.users_helper.update_user(user_id, up_data) self.users_helper.update_user(user_id, up_data)
@ -286,8 +287,8 @@ class UsersController:
name: str, name: str,
user_id: str, user_id: str,
superuser: bool = False, superuser: bool = False,
server_permissions_mask: Optional[str] = None, server_permissions_mask: t.Optional[str] = None,
crafty_permissions_mask: Optional[str] = None, crafty_permissions_mask: t.Optional[str] = None,
): ):
return self.users_helper.add_user_api_key( return self.users_helper.add_user_api_key(
name, user_id, superuser, server_permissions_mask, crafty_permissions_mask name, user_id, superuser, server_permissions_mask, crafty_permissions_mask

View File

@ -192,7 +192,7 @@ class PermissionsCrafty:
@staticmethod @staticmethod
def add_server_creation(user_id): def add_server_creation(user_id):
"""Increase the "Server Creation" counter for this use """Increase the "Server Creation" counter for this user
Args: Args:
user_id (int): The modifiable user's ID user_id (int): The modifiable user's ID

View File

@ -1,5 +1,6 @@
import logging import logging
import datetime import datetime
import typing as t
from peewee import ( from peewee import (
CharField, CharField,
DoesNotExist, DoesNotExist,
@ -35,8 +36,11 @@ class HelperRoles:
@staticmethod @staticmethod
def get_all_roles(): def get_all_roles():
query = Roles.select() return Roles.select()
return query
@staticmethod
def get_all_role_ids() -> t.List[int]:
return [role.role_id for role in Roles.select(Roles.role_id).execute()]
@staticmethod @staticmethod
def get_roleid_by_name(role_name): def get_roleid_by_name(role_name):
@ -49,6 +53,24 @@ class HelperRoles:
def get_role(role_id): def get_role(role_id):
return model_to_dict(Roles.get(Roles.role_id == role_id)) return model_to_dict(Roles.get(Roles.role_id == role_id))
@staticmethod
def get_role_columns(
role_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Roles, column) for column in column_names]
return model_to_dict(
Roles.select(*columns).where(Roles.role_id == role_id).get(),
only=columns,
)
@staticmethod
def get_role_column(role_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Roles, column_name)
return model_to_dict(
Roles.select(column).where(Roles.role_id == role_id).get(),
only=[column],
)[column_name]
@staticmethod @staticmethod
def add_role(role_name): def add_role(role_name):
role_id = Roles.insert( role_id = Roles.insert(

View File

@ -1,6 +1,6 @@
from enum import Enum from enum import Enum
import logging import logging
import typing import typing as t
from peewee import ( from peewee import (
ForeignKeyField, ForeignKeyField,
CharField, CharField,
@ -52,14 +52,14 @@ class PermissionsServers:
@staticmethod @staticmethod
def get_permissions_list(): def get_permissions_list():
permissions_list: typing.List[EnumPermissionsServer] = [] permissions_list: t.List[EnumPermissionsServer] = []
for member in EnumPermissionsServer.__members__.items(): for member in EnumPermissionsServer.__members__.items():
permissions_list.append(member[1]) permissions_list.append(member[1])
return permissions_list return permissions_list
@staticmethod @staticmethod
def get_permissions(permissions_mask): def get_permissions(permissions_mask):
permissions_list: typing.List[EnumPermissionsServer] = [] permissions_list: t.List[EnumPermissionsServer] = []
for member in EnumPermissionsServer.__members__.items(): for member in EnumPermissionsServer.__members__.items():
if PermissionsServers.has_permission(permissions_mask, member[1]): if PermissionsServers.has_permission(permissions_mask, member[1]):
permissions_list.append(member[1]) permissions_list.append(member[1])
@ -96,17 +96,29 @@ class PermissionsServers:
# Role_Servers Methods # Role_Servers Methods
# ********************************************************************************** # **********************************************************************************
@staticmethod @staticmethod
def get_role_servers_from_role_id(roleid): def get_role_servers_from_role_id(roleid: t.Union[str, int]):
return RoleServers.select().where(RoleServers.role_id == roleid) return RoleServers.select().where(RoleServers.role_id == roleid)
@staticmethod @staticmethod
def get_servers_from_role(role_id): def get_servers_from_role(role_id: t.Union[str, int]):
return ( return (
RoleServers.select() RoleServers.select()
.join(Servers, JOIN.INNER) .join(Servers, JOIN.INNER)
.where(RoleServers.role_id == role_id) .where(RoleServers.role_id == role_id)
) )
@staticmethod
def get_server_ids_from_role(role_id: t.Union[str, int]) -> t.List[int]:
# FIXME: somehow retrieve only the server ids, not the whole servers
return [
role_servers.server_id.server_id
for role_servers in (
RoleServers.select(RoleServers.server_id).where(
RoleServers.role_id == role_id
)
)
]
@staticmethod @staticmethod
def get_roles_from_server(server_id): def get_roles_from_server(server_id):
return ( return (

View File

@ -1,5 +1,6 @@
import logging import logging
import datetime import datetime
import typing as t
from peewee import ( from peewee import (
ForeignKeyField, ForeignKeyField,
CharField, CharField,
@ -9,6 +10,7 @@ from peewee import (
IntegerField, IntegerField,
FloatField, FloatField,
) )
from playhouse.shortcuts import model_to_dict
from app.classes.shared.main_models import DatabaseShortcuts from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.models.base_model import BaseModel from app.classes.models.base_model import BaseModel
@ -162,6 +164,24 @@ class HelperServers:
except IndexError: except IndexError:
return {} return {}
@staticmethod
def get_server_columns(
server_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Servers, column) for column in column_names]
return model_to_dict(
Servers.select(*columns).where(Servers.server_id == server_id).get(),
only=columns,
)
@staticmethod
def get_server_column(server_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Servers, column_name)
return model_to_dict(
Servers.select(column).where(Servers.server_id == server_id).get(),
only=[column],
)[column_name]
# ********************************************************************************** # **********************************************************************************
# Servers Methods # Servers Methods
# ********************************************************************************** # **********************************************************************************

View File

@ -100,9 +100,13 @@ class HelperUsers:
return query return query
@staticmethod @staticmethod
def get_all_user_ids(): def get_all_user_ids() -> t.List[int]:
query = Users.select(Users.user_id).where(Users.username != "system") return [
return query user.user_id
for user in Users.select(Users.user_id)
.where(Users.username != "system")
.execute()
]
@staticmethod @staticmethod
def get_user_lang_by_id(user_id): def get_user_lang_by_id(user_id):
@ -148,6 +152,24 @@ class HelperUsers:
# logger.debug("user: ({}) {}".format(user_id, {})) # logger.debug("user: ({}) {}".format(user_id, {}))
return {} return {}
@staticmethod
def get_user_columns(
user_id: t.Union[str, int], column_names: t.List[str]
) -> t.List[t.Any]:
columns = [getattr(Users, column) for column in column_names]
return model_to_dict(
Users.select(*columns).where(Users.user_id == user_id).get(),
only=columns,
)
@staticmethod
def get_user_column(user_id: t.Union[str, int], column_name: str) -> t.Any:
column = getattr(Users, column_name)
return model_to_dict(
Users.select(column).where(Users.user_id == user_id).get(),
only=[column],
)[column_name]
@staticmethod @staticmethod
def check_system_user(user_id): def check_system_user(user_id):
try: try:
@ -284,11 +306,10 @@ class HelperUsers:
@staticmethod @staticmethod
def get_user_roles_names(user_id): def get_user_roles_names(user_id):
roles_list = [] roles = UserRoles.select(UserRoles.role_id).where(UserRoles.user_id == user_id)
roles = UserRoles.select().where(UserRoles.user_id == user_id) return [
for r in roles: HelperRoles.get_role_column(role.role_id, "role_name") for role in roles
roles_list.append(HelperRoles.get_role(r.role_id)["role_name"]) ]
return roles_list
@staticmethod @staticmethod
def add_role_to_user(user_id, role_id): def add_role_to_user(user_id, role_id):

View File

@ -29,11 +29,11 @@ logger = logging.getLogger(__name__)
class PanelHandler(BaseHandler): class PanelHandler(BaseHandler):
def get_user_roles(self) -> Dict[str, list]: def get_user_roles(self) -> Dict[str, list]:
user_roles = {} user_roles = {}
for user in self.controller.users.get_all_users(): for user_id in self.controller.users.get_all_user_ids():
user_roles_list = self.controller.users.get_user_roles_names(user.user_id) user_roles_list = self.controller.users.get_user_roles_names(user_id)
# user_servers = # user_servers =
# self.controller.servers.get_authorized_servers(user.user_id) # self.controller.servers.get_authorized_servers(user.user_id)
user_roles[user.user_id] = user_roles_list user_roles[user_id] = user_roles_list
return user_roles return user_roles
def get_role_servers(self) -> set: def get_role_servers(self) -> set:

View File

@ -1,7 +1,13 @@
from app.classes.web.routes.api.index_handler import ApiIndexHandler
from app.classes.web.routes.api.not_found import ApiNotFoundHandler
from app.classes.web.routes.api.auth.invalidate_tokens import ( from app.classes.web.routes.api.auth.invalidate_tokens import (
ApiAuthInvalidateTokensHandler, ApiAuthInvalidateTokensHandler,
) )
from app.classes.web.routes.api.auth.login import ApiAuthLoginHandler from app.classes.web.routes.api.auth.login import ApiAuthLoginHandler
from app.classes.web.routes.api.roles.index import ApiRolesIndexHandler
from app.classes.web.routes.api.roles.role.index import ApiRolesRoleIndexHandler
from app.classes.web.routes.api.roles.role.servers import ApiRolesRoleServersHandler
from app.classes.web.routes.api.roles.role.users import ApiRolesRoleUsersHandler
from app.classes.web.routes.api.servers.index import ApiServersIndexHandler from app.classes.web.routes.api.servers.index import ApiServersIndexHandler
from app.classes.web.routes.api.servers.server.action import ( from app.classes.web.routes.api.servers.server.action import (
ApiServersServerActionHandler, ApiServersServerActionHandler,
@ -22,34 +28,60 @@ from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandl
def api_handlers(handler_args): def api_handlers(handler_args):
return [ return [
# Auth routes # Auth routes
(r"/api/v2/auth/login", ApiAuthLoginHandler, handler_args), (r"/api/v2/auth/login/?", ApiAuthLoginHandler, handler_args),
( (
r"/api/v2/auth/invalidate_tokens", r"/api/v2/auth/invalidate_tokens/?",
ApiAuthInvalidateTokensHandler, ApiAuthInvalidateTokensHandler,
handler_args, handler_args,
), ),
# User routes # User routes
(r"/api/v2/users", ApiUsersIndexHandler, handler_args), (r"/api/v2/users/?", ApiUsersIndexHandler, handler_args),
(r"/api/v2/users/([a-z0-9_]+)", ApiUsersUserIndexHandler, handler_args), (r"/api/v2/users/([a-z0-9_]+)/?", ApiUsersUserIndexHandler, handler_args),
(r"/api/v2/users/(@me)", ApiUsersUserIndexHandler, handler_args), (r"/api/v2/users/(@me)/?", ApiUsersUserIndexHandler, handler_args),
(r"/api/v2/users/([a-z0-9_]+)/pfp", ApiUsersUserPfpHandler, handler_args), (r"/api/v2/users/([a-z0-9_]+)/pfp/?", ApiUsersUserPfpHandler, handler_args),
(r"/api/v2/users/(@me)/pfp", ApiUsersUserPfpHandler, handler_args), (r"/api/v2/users/(@me)/pfp/?", ApiUsersUserPfpHandler, handler_args),
(r"/api/v2/users/([a-z0-9_]+)/public", ApiUsersUserPublicHandler, handler_args),
(r"/api/v2/users/(@me)/public", ApiUsersUserPublicHandler, handler_args),
# Server routes
(r"/api/v2/servers", ApiServersIndexHandler, handler_args),
(r"/api/v2/servers/([0-9]+)", ApiServersServerIndexHandler, handler_args),
(r"/api/v2/servers/([0-9]+)/stats", ApiServersServerStatsHandler, handler_args),
( (
r"/api/v2/servers/([0-9]+)/action/([a-z_]+)", r"/api/v2/users/([a-z0-9_]+)/public/?",
ApiUsersUserPublicHandler,
handler_args,
),
(r"/api/v2/users/(@me)/public/?", ApiUsersUserPublicHandler, handler_args),
# Server routes
(r"/api/v2/servers/?", ApiServersIndexHandler, handler_args),
(r"/api/v2/servers/([0-9]+)/?", ApiServersServerIndexHandler, handler_args),
(
r"/api/v2/servers/([0-9]+)/stats/?",
ApiServersServerStatsHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/action/([a-z_]+)/?",
ApiServersServerActionHandler, ApiServersServerActionHandler,
handler_args, handler_args,
), ),
(r"/api/v2/servers/([0-9]+)/logs", ApiServersServerLogsHandler, handler_args), (r"/api/v2/servers/([0-9]+)/logs/?", ApiServersServerLogsHandler, handler_args),
(r"/api/v2/servers/([0-9]+)/users", ApiServersServerUsersHandler, handler_args),
( (
r"/api/v2/servers/([0-9]+)/public", r"/api/v2/servers/([0-9]+)/users/?",
ApiServersServerUsersHandler,
handler_args,
),
(
r"/api/v2/servers/([0-9]+)/public/?",
ApiServersServerPublicHandler, ApiServersServerPublicHandler,
handler_args, handler_args,
), ),
(r"/api/v2/roles/?", ApiRolesIndexHandler, handler_args),
(r"/api/v2/roles/([a-z0-9_]+)/?", ApiRolesRoleIndexHandler, handler_args),
(
r"/api/v2/roles/([a-z0-9_]+)/servers/?",
ApiRolesRoleServersHandler,
handler_args,
),
(
r"/api/v2/roles/([a-z0-9_]+)/users/?",
ApiRolesRoleUsersHandler,
handler_args,
),
(r"/api/v2/?", ApiIndexHandler, handler_args),
(r"/api/v2/(.*)", ApiNotFoundHandler, handler_args),
] ]

View File

@ -0,0 +1,17 @@
from app.classes.web.base_api_handler import BaseApiHandler
WIKI_API_LINK = "https://wiki.craftycontrol.com/en/4/docs/API V2"
class ApiIndexHandler(BaseApiHandler):
def get(self):
self.finish_json(
200,
{
"status": "ok",
"data": {
"version": self.controller.helper.get_version_string(),
"message": f"Please see the API documentation at {WIKI_API_LINK}",
},
},
)

View File

@ -0,0 +1,9 @@
from app.classes.web.base_api_handler import BaseApiHandler
class ApiNotFoundHandler(BaseApiHandler):
def get(self, page: str):
self.finish_json(
404,
{"status": "error", "error": "API_HANDLER_NOT_FOUND", "page": page},
)

View File

@ -0,0 +1,33 @@
from playhouse.shortcuts import model_to_dict
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesIndexHandler(BaseApiHandler):
def get(self):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
# GET /api/v2/roles?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true"
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
# TODO: permissions
self.finish_json(
200,
{
"status": "ok",
"data": self.controller.roles.get_all_role_ids()
if get_only_ids
else [model_to_dict(r) for r in self.controller.roles.get_all_roles()],
},
)

View File

@ -0,0 +1,24 @@
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesRoleIndexHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
# TODO: permissions
self.finish_json(
200,
{"status": "ok", "data": self.controller.roles.get_role(role_id)},
)

View File

@ -0,0 +1,27 @@
from app.classes.models.server_permissions import PermissionsServers
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesRoleServersHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
200,
{
"status": "ok",
"data": PermissionsServers.get_server_ids_from_role(role_id),
},
)

View File

@ -0,0 +1,36 @@
from app.classes.web.base_api_handler import BaseApiHandler
class ApiRolesRoleUsersHandler(BaseApiHandler):
def get(self, role_id: str):
auth_data = self.authenticate_user()
if not auth_data:
return
(
_,
_,
_,
superuser,
_,
) = auth_data
if not superuser:
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
all_user_ids = self.controller.users.get_all_user_ids()
user_roles = {}
for user_id in all_user_ids:
user_roles_list = self.controller.users.get_user_roles_names(user_id)
user_roles[user_id] = user_roles_list
role = self.controller.roles.get_role(role_id)
user_ids = []
for user_id in all_user_ids:
for role_user in user_roles[user_id]:
if role_user == role["role_name"]:
user_ids.append(user_id)
self.finish_json(200, {"status": "ok", "data": user_ids})

View File

@ -28,10 +28,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
if EnumPermissionsCrafty.USER_CONFIG in exec_user_crafty_permissions: if EnumPermissionsCrafty.USER_CONFIG in exec_user_crafty_permissions:
if get_only_ids: if get_only_ids:
data = [ data = self.controller.users.get_all_user_ids()
user.user_id
for user in self.controller.users.get_all_user_ids().execute()
]
else: else:
data = [ data = [
{key: getattr(user_res, key) for key in PUBLIC_USER_ATTRS} {key: getattr(user_res, key) for key in PUBLIC_USER_ATTRS}

View File

@ -212,6 +212,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
) )
# TODO: make this more efficient # TODO: make this more efficient
# TODO: add permissions and roles because I forgot
user_obj = HelperUsers.get_user_model(user_id) user_obj = HelperUsers.get_user_model(user_id)
self.controller.management.add_to_audit_log( self.controller.management.add_to_audit_log(