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
def add_server_creation(user_id):
"""Increase the "Server Creation" counter for this use
"""Increase the "Server Creation" counter for this user
Args:
user_id (int): The modifiable user's ID

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import logging
import datetime
import typing as t
from peewee import (
CharField,
DoesNotExist,
@ -35,8 +36,11 @@ class HelperRoles:
@staticmethod
def get_all_roles():
query = Roles.select()
return query
return Roles.select()
@staticmethod
def get_all_role_ids() -> t.List[int]:
return [role.role_id for role in Roles.select(Roles.role_id).execute()]
@staticmethod
def get_roleid_by_name(role_name):
@ -49,6 +53,24 @@ class HelperRoles:
def get_role(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
def add_role(role_name):
role_id = Roles.insert(

View File

@ -1,6 +1,6 @@
from enum import Enum
import logging
import typing
import typing as t
from peewee import (
ForeignKeyField,
CharField,
@ -52,14 +52,14 @@ class PermissionsServers:
@staticmethod
def get_permissions_list():
permissions_list: typing.List[EnumPermissionsServer] = []
permissions_list: t.List[EnumPermissionsServer] = []
for member in EnumPermissionsServer.__members__.items():
permissions_list.append(member[1])
return permissions_list
@staticmethod
def get_permissions(permissions_mask):
permissions_list: typing.List[EnumPermissionsServer] = []
permissions_list: t.List[EnumPermissionsServer] = []
for member in EnumPermissionsServer.__members__.items():
if PermissionsServers.has_permission(permissions_mask, member[1]):
permissions_list.append(member[1])
@ -96,17 +96,29 @@ class PermissionsServers:
# Role_Servers Methods
# **********************************************************************************
@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)
@staticmethod
def get_servers_from_role(role_id):
def get_servers_from_role(role_id: t.Union[str, int]):
return (
RoleServers.select()
.join(Servers, JOIN.INNER)
.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
def get_roles_from_server(server_id):
return (

View File

@ -1,5 +1,6 @@
import logging
import datetime
import typing as t
from peewee import (
ForeignKeyField,
CharField,
@ -9,6 +10,7 @@ from peewee import (
IntegerField,
FloatField,
)
from playhouse.shortcuts import model_to_dict
from app.classes.shared.main_models import DatabaseShortcuts
from app.classes.models.base_model import BaseModel
@ -162,6 +164,24 @@ class HelperServers:
except IndexError:
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
# **********************************************************************************

View File

@ -100,9 +100,13 @@ class HelperUsers:
return query
@staticmethod
def get_all_user_ids():
query = Users.select(Users.user_id).where(Users.username != "system")
return query
def get_all_user_ids() -> t.List[int]:
return [
user.user_id
for user in Users.select(Users.user_id)
.where(Users.username != "system")
.execute()
]
@staticmethod
def get_user_lang_by_id(user_id):
@ -148,6 +152,24 @@ class HelperUsers:
# logger.debug("user: ({}) {}".format(user_id, {}))
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
def check_system_user(user_id):
try:
@ -284,11 +306,10 @@ class HelperUsers:
@staticmethod
def get_user_roles_names(user_id):
roles_list = []
roles = UserRoles.select().where(UserRoles.user_id == user_id)
for r in roles:
roles_list.append(HelperRoles.get_role(r.role_id)["role_name"])
return roles_list
roles = UserRoles.select(UserRoles.role_id).where(UserRoles.user_id == user_id)
return [
HelperRoles.get_role_column(role.role_id, "role_name") for role in roles
]
@staticmethod
def add_role_to_user(user_id, role_id):

View File

@ -29,11 +29,11 @@ logger = logging.getLogger(__name__)
class PanelHandler(BaseHandler):
def get_user_roles(self) -> Dict[str, list]:
user_roles = {}
for user in self.controller.users.get_all_users():
user_roles_list = self.controller.users.get_user_roles_names(user.user_id)
for user_id in self.controller.users.get_all_user_ids():
user_roles_list = self.controller.users.get_user_roles_names(user_id)
# user_servers =
# 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
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 (
ApiAuthInvalidateTokensHandler,
)
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.server.action import (
ApiServersServerActionHandler,
@ -22,34 +28,60 @@ from app.classes.web.routes.api.users.user.public import ApiUsersUserPublicHandl
def api_handlers(handler_args):
return [
# 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,
handler_args,
),
# User routes
(r"/api/v2/users", ApiUsersIndexHandler, handler_args),
(r"/api/v2/users/([a-z0-9_]+)", 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/(@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/users/?", ApiUsersIndexHandler, handler_args),
(r"/api/v2/users/([a-z0-9_]+)/?", 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/(@me)/pfp/?", ApiUsersUserPfpHandler, 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,
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]+)/logs/?", ApiServersServerLogsHandler, 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,
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 get_only_ids:
data = [
user.user_id
for user in self.controller.users.get_all_user_ids().execute()
]
data = self.controller.users.get_all_user_ids()
else:
data = [
{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: add permissions and roles because I forgot
user_obj = HelperUsers.get_user_model(user_id)
self.controller.management.add_to_audit_log(