Skip to content

Commit

Permalink
Improved authorization checks and fixed permission issues
Browse files Browse the repository at this point in the history
  • Loading branch information
kirgrim committed Apr 28, 2024
1 parent 618234a commit 2fa1057
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 143 deletions.
40 changes: 25 additions & 15 deletions chat_server/blueprints/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,14 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from fastapi import APIRouter
from starlette.requests import Request
from starlette.responses import JSONResponse

from chat_server.server_utils.api_dependencies import permitted_access
from chat_server.server_utils.api_dependencies.models.admin import (
RefreshServiceRequestModel,
ChatsOverviewRequestModel,
)
from chat_server.server_utils.enums import UserRoles, RequestModelType
from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI
from utils.logging_utils import LOG
from utils.http_utils import respond
Expand All @@ -46,21 +51,22 @@


@router.post("/refresh/{service_name}")
@login_required(tmp_allowed=False, required_roles=["admin"])
async def refresh_state(
request: Request, service_name: str, target_items: str | None = ""
async def refresh_service(
model: RefreshServiceRequestModel = permitted_access(
RefreshServiceRequestModel,
min_required_role=UserRoles.ADMIN,
request_model_type=RequestModelType.DATA,
)
):
"""
Refreshes state of the target
:param request: Starlette Request Object
:param service_name: name of service to refresh
:param target_items: comma-separated list of items to refresh
:param model: request data model
:returns JSON-formatted response from server
"""
target_items = [x for x in target_items.split(",") if x]
if service_name == "k8s":
target_items = [x for x in model.target_items.split(",") if x]
if model.service_name == "k8s":
if not server_config.k8s_config:
return respond("K8S Service Unavailable", 503)
deployments = target_items
Expand All @@ -69,18 +75,22 @@ async def refresh_state(
LOG.info(f"Restarting {deployments=!r}")
for deployment in deployments:
restart_deployment(deployment_name=deployment)
elif service_name == "mq":
elif model.service_name == "mq":
run_mq_validation()
else:
return respond(f"Unknown refresh type: {service_name!r}", 404)
return respond(f"Unknown refresh type: {model.service_name!r}", 404)
return respond("OK")


@router.get("/chats/list")
@login_required(tmp_allowed=False, required_roles=["admin"])
async def chats_overview(request: Request, search_str: str = ""):
conversations_data = MongoDocumentsAPI.CHATS.get_conversation_data(
search_str=search_str,
async def chats_overview(
model: ChatsOverviewRequestModel = permitted_access(
ChatsOverviewRequestModel, min_required_role=UserRoles.ADMIN
)
):
conversations_data = MongoDocumentsAPI.CHATS.get_chats(
search_str=model.search_str,
column_identifiers=["_id", "conversation_name"],
limit=100,
allow_regex_search=True,
)
Expand Down
12 changes: 5 additions & 7 deletions chat_server/blueprints/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
from utils.common import get_hash, generate_uuid
from chat_server.server_utils.auth import (
check_password_strength,
get_current_user_data,
generate_session_token,
create_unauthorized_user,
)
from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI
from utils.http_utils import respond
Expand Down Expand Up @@ -109,14 +109,12 @@ async def login(username: str = Form(...), password: str = Form(...)):


@router.get("/logout")
async def logout(request: Request):
async def logout():
"""
Erases current user session cookies and returns temporal credentials
:param request: logout intended request
Creates temporary user and returns its credentials
:returns response with temporal cookie
"""
user_data = get_current_user_data(request=request, force_tmp=True)
# TODO: store session tokens in runtime
user_data = create_unauthorized_user()
response = JSONResponse(content=dict(token=user_data.session))
return response
22 changes: 14 additions & 8 deletions chat_server/blueprints/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
from fastapi import APIRouter, Request, Form, Depends
from fastapi.responses import JSONResponse

from chat_server.server_utils.auth import login_required
from chat_server.server_utils.api_dependencies.validators.users import (
get_authorized_user,
)
from chat_server.server_utils.conversation_utils import build_message_json
from chat_server.server_utils.api_dependencies.extractors import CurrentUserData
from chat_server.server_utils.api_dependencies.models import GetConversationModel
Expand All @@ -50,9 +52,8 @@


@router.post("/new")
@login_required
async def new_conversation(
request: Request,
current_user: CurrentUserData = get_authorized_user,
conversation_id: str = Form(""), # DEPRECATED
conversation_name: str = Form(...),
is_private: str = Form(False),
Expand All @@ -61,7 +62,7 @@ async def new_conversation(
"""
Creates new conversation from provided conversation data
:param request: Starlette Request object
:param current_user: current user data
:param conversation_id: new conversation id (DEPRECATED)
:param conversation_name: new conversation name (optional)
:param is_private: if new conversation should be private (defaults to False)
Expand All @@ -73,8 +74,10 @@ async def new_conversation(
warnings.warn(
"Param conversation id is no longer considered", DeprecationWarning
)
conversation_data = MongoDocumentsAPI.CHATS.get_conversation_data(
search_str=[conversation_name],
conversation_data = MongoDocumentsAPI.CHATS.get_chat(
search_str=conversation_name,
column_identifiers=["conversation_name"],
requested_user_id=current_user.user_id,
)
if conversation_data:
return respond(f'Conversation "{conversation_name}" already exists', 400)
Expand All @@ -84,6 +87,7 @@ async def new_conversation(
"conversation_name": conversation_name,
"is_private": True if is_private == "1" else False,
"bound_service": bound_service,
"creator": current_user.user_id,
"created_on": int(time()),
}
MongoDocumentsAPI.CHATS.add_item(data=request_data_dict)
Expand All @@ -103,8 +107,10 @@ async def get_matching_conversation(
:returns conversation data if found, 401 error code otherwise
"""
conversation_data = MongoDocumentsAPI.CHATS.get_conversation_data(
search_str=model.search_str, requested_user_id=current_user.user_id
conversation_data = MongoDocumentsAPI.CHATS.get_chat(
search_str=model.search_str,
column_identifiers=["_id", "conversation_name"],
requested_user_id=current_user.user_id,
)

if not conversation_data:
Expand Down
12 changes: 5 additions & 7 deletions chat_server/blueprints/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@
from fastapi import APIRouter, Depends
from starlette.responses import JSONResponse

from chat_server.server_utils.api_dependencies import permitted_access
from chat_server.server_utils.enums import UserRoles, RequestModelType
from chat_server.server_utils.http_exceptions import (
ItemNotFoundException,
UserUnauthorizedException,
)
from chat_server.server_utils.http_utils import KlatAPIResponse

from chat_server.server_utils.api_dependencies.models import SetConfigModel, ConfigModel
from chat_server.server_utils.api_dependencies.extractors import CurrentUserData

from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI

router = APIRouter(
Expand All @@ -57,11 +55,11 @@ async def get_config_data(model: ConfigModel = Depends()) -> JSONResponse:

@router.put("/{config_property}")
async def update_config(
current_user: CurrentUserData, model: SetConfigModel = Depends()
model: SetConfigModel = permitted_access(
SetConfigModel, min_required_role=UserRoles.ADMIN
)
) -> JSONResponse:
"""Updates provided config by name"""
if "admin" not in current_user.roles:
raise UserUnauthorizedException
updated_data = MongoDocumentsAPI.CONFIGS.update_by_name(
config_name=model.config_property, version=model.version, data=model.data
)
Expand Down
14 changes: 5 additions & 9 deletions chat_server/blueprints/files_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
from typing import List

from fastapi import APIRouter, UploadFile, File
from starlette.requests import Request
from starlette.responses import JSONResponse

from chat_server.server_utils.auth import login_required
from chat_server.server_utils.api_dependencies.validators.users import (
get_authorized_user,
)
from chat_server.server_utils.http_utils import get_file_response, save_file
from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI
from utils.http_utils import respond
Expand All @@ -45,7 +46,6 @@

@router.get("/audio/{message_id}")
async def get_audio_message(
request: Request,
message_id: str,
):
"""Gets file based on the name"""
Expand Down Expand Up @@ -85,12 +85,10 @@ async def get_avatar(user_id: str):


@router.get("/{msg_id}/get_attachment/{filename}")
# @login_required
async def get_message_attachment(request: Request, msg_id: str, filename: str):
async def get_message_attachment(msg_id: str, filename: str):
"""
Gets file from the server
:param request: Starlette Request Object
:param msg_id: parent message id
:param filename: name of the file to get
"""
Expand All @@ -114,12 +112,10 @@ async def get_message_attachment(request: Request, msg_id: str, filename: str):


@router.post("/attachments")
@login_required
async def save_attachments(request: Request, files: List[UploadFile] = File(...)):
async def save_attachments(_=get_authorized_user, files: List[UploadFile] = File(...)):
"""
Stores received files in filesystem
:param request: Starlette Request Object
:param files: list of files to process
:returns JSON-formatted response from server
Expand Down
9 changes: 4 additions & 5 deletions chat_server/blueprints/personas.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
from fastapi import APIRouter
from starlette.responses import JSONResponse

from chat_server.server_utils.api_dependencies.extractors.personas import (
_get_persona_model,
)
from chat_server.server_utils.enums import UserRoles, RequestModelType
from chat_server.server_utils.enums import RequestModelType, UserRoles
from chat_server.server_utils.http_exceptions import (
ItemNotFoundException,
DuplicatedItemException,
Expand Down Expand Up @@ -149,7 +146,9 @@ async def delete_persona(
@router.post("/toggle")
async def toggle_persona_state(
request_model: TogglePersonaStatusModelModel = permitted_access(
TogglePersonaStatusModelModel, request_model_type=RequestModelType.DATA
TogglePersonaStatusModelModel,
min_required_role=UserRoles.AUTHORIZED_USER,
request_model_type=RequestModelType.DATA,
),
):
updated_data = MongoDocumentsAPI.PERSONAS.update_item(
Expand Down
30 changes: 14 additions & 16 deletions chat_server/blueprints/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from fastapi import APIRouter, Request, Form
from chat_server.server_utils.auth import get_current_user, login_required
from fastapi import APIRouter, Form, Depends

from chat_server.server_utils.api_dependencies import CurrentUserData
from chat_server.server_utils.api_dependencies.validators.users import (
get_authorized_user,
)
from utils.database_utils.mongo_utils.queries.wrapper import MongoDocumentsAPI
from utils.http_utils import respond
from utils.logging_utils import LOG

router = APIRouter(
prefix="/preferences",
Expand All @@ -39,38 +42,33 @@


@router.post("/update")
@login_required
async def update_settings(
request: Request,
minify_messages: str = Form("0"),
current_user: CurrentUserData = get_authorized_user,
):
"""
Updates user settings with provided form data
:param request: FastAPI Request Object
:param current_user: current user data
:param minify_messages: "1" if user prefers to get minified messages
:return: status 200 if OK, error code otherwise
"""
user = get_current_user(request=request)
preferences_mapping = {"minify_messages": minify_messages}
MongoDocumentsAPI.USERS.set_preferences(
user_id=user["_id"], preferences_mapping=preferences_mapping
user_id=current_user.user_id, preferences_mapping=preferences_mapping
)
return respond(msg="OK")


@router.post("/update_language/{cid}/{input_type}")
@login_required
async def update_language(
request: Request, cid: str, input_type: str, lang: str = Form(...)
cid: str,
input_type: str,
lang: str = Form(...),
current_user: CurrentUserData = get_authorized_user,
):
"""Updates preferred language of user in conversation"""
try:
current_user_id = get_current_user(request)["_id"]
except Exception as ex:
LOG.error(ex)
return respond(f"Failed to update language of {cid}/{input_type} to {lang}")
MongoDocumentsAPI.USERS.set_preferences(
user_id=current_user_id,
user_id=current_user.user_id,
preferences_mapping={f"chat_language_mapping.{cid}.{input_type}": lang},
)
return respond(f"Updated cid={cid}, input_type={input_type} to language={lang}")
Loading

0 comments on commit 2fa1057

Please sign in to comment.