fix: enforce global ENABLE_CHANNELS check on all channel endpoints (#19957)

* fix: enforce global ENABLE_CHANNELS check on all channel endpoints

When channels are disabled globally (ENABLE_CHANNELS=false), users with
channel permissions could still fetch channels via API endpoints. This
fix adds a get_enabled_channels dependency to all 22 channel endpoints
that returns 403 Forbidden when channels are globally disabled.

Fixes #19914

* refac

* refac
This commit is contained in:
G30 2025-12-20 08:15:29 -05:00 committed by GitHub
parent e61724d2b1
commit d65116282c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -68,6 +68,21 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"])
router = APIRouter() router = APIRouter()
############################
# Channels Enabled Dependency
############################
def check_channels_access(request: Request):
"""Dependency to ensure channels are globally enabled."""
if not request.app.state.config.ENABLE_CHANNELS:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Channels are not enabled",
)
############################ ############################
# GetChatList # GetChatList
############################ ############################
@ -82,7 +97,11 @@ class ChannelListItemResponse(ChannelModel):
@router.get("/", response_model=list[ChannelListItemResponse]) @router.get("/", response_model=list[ChannelListItemResponse])
async def get_channels(request: Request, user=Depends(get_verified_user)): async def get_channels(
request: Request,
user=Depends(get_verified_user),
):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -134,7 +153,11 @@ async def get_channels(request: Request, user=Depends(get_verified_user)):
@router.get("/list", response_model=list[ChannelModel]) @router.get("/list", response_model=list[ChannelModel])
async def get_all_channels(user=Depends(get_verified_user)): async def get_all_channels(
request: Request,
user=Depends(get_verified_user),
):
check_channels_access(request)
if user.role == "admin": if user.role == "admin":
return Channels.get_channels() return Channels.get_channels()
return Channels.get_channels_by_user_id(user.id) return Channels.get_channels_by_user_id(user.id)
@ -147,8 +170,11 @@ async def get_all_channels(user=Depends(get_verified_user)):
@router.get("/users/{user_id}", response_model=Optional[ChannelModel]) @router.get("/users/{user_id}", response_model=Optional[ChannelModel])
async def get_dm_channel_by_user_id( async def get_dm_channel_by_user_id(
request: Request, user_id: str, user=Depends(get_verified_user) request: Request,
user_id: str,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -216,8 +242,11 @@ async def get_dm_channel_by_user_id(
@router.post("/create", response_model=Optional[ChannelModel]) @router.post("/create", response_model=Optional[ChannelModel])
async def create_new_channel( async def create_new_channel(
request: Request, form_data: CreateChannelForm, user=Depends(get_verified_user) request: Request,
form_data: CreateChannelForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -296,7 +325,12 @@ class ChannelFullResponse(ChannelResponse):
@router.get("/{id}", response_model=Optional[ChannelFullResponse]) @router.get("/{id}", response_model=Optional[ChannelFullResponse])
async def get_channel_by_id(id: str, user=Depends(get_verified_user)): async def get_channel_by_id(
request: Request,
id: str,
user=Depends(get_verified_user),
):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -383,6 +417,7 @@ PAGE_ITEM_COUNT = 30
@router.get("/{id}/members", response_model=UserListResponse) @router.get("/{id}/members", response_model=UserListResponse)
async def get_channel_members_by_id( async def get_channel_members_by_id(
request: Request,
id: str, id: str,
query: Optional[str] = None, query: Optional[str] = None,
order_by: Optional[str] = None, order_by: Optional[str] = None,
@ -390,6 +425,7 @@ async def get_channel_members_by_id(
page: Optional[int] = 1, page: Optional[int] = 1,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
@ -472,10 +508,12 @@ class UpdateActiveMemberForm(BaseModel):
@router.post("/{id}/members/active", response_model=bool) @router.post("/{id}/members/active", response_model=bool)
async def update_is_active_member_by_id_and_user_id( async def update_is_active_member_by_id_and_user_id(
request: Request,
id: str, id: str,
form_data: UpdateActiveMemberForm, form_data: UpdateActiveMemberForm,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -508,6 +546,7 @@ async def add_members_by_id(
form_data: UpdateMembersForm, form_data: UpdateMembersForm,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -556,6 +595,7 @@ async def remove_members_by_id(
form_data: RemoveMembersForm, form_data: RemoveMembersForm,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -593,8 +633,12 @@ async def remove_members_by_id(
@router.post("/{id}/update", response_model=Optional[ChannelModel]) @router.post("/{id}/update", response_model=Optional[ChannelModel])
async def update_channel_by_id( async def update_channel_by_id(
request: Request, id: str, form_data: ChannelForm, user=Depends(get_verified_user) request: Request,
id: str,
form_data: ChannelForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -631,8 +675,11 @@ async def update_channel_by_id(
@router.delete("/{id}/delete", response_model=bool) @router.delete("/{id}/delete", response_model=bool)
async def delete_channel_by_id( async def delete_channel_by_id(
request: Request, id: str, user=Depends(get_verified_user) request: Request,
id: str,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
if user.role != "admin" and not has_permission( if user.role != "admin" and not has_permission(
user.id, "features.channels", request.app.state.config.USER_PERMISSIONS user.id, "features.channels", request.app.state.config.USER_PERMISSIONS
): ):
@ -682,8 +729,13 @@ class MessageUserResponse(MessageResponse):
@router.get("/{id}/messages", response_model=list[MessageUserResponse]) @router.get("/{id}/messages", response_model=list[MessageUserResponse])
async def get_channel_messages( async def get_channel_messages(
id: str, skip: int = 0, limit: int = 50, user=Depends(get_verified_user) request: Request,
id: str,
skip: int = 0,
limit: int = 50,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -745,8 +797,12 @@ PAGE_ITEM_COUNT_PINNED = 20
@router.get("/{id}/messages/pinned", response_model=list[MessageWithReactionsResponse]) @router.get("/{id}/messages/pinned", response_model=list[MessageWithReactionsResponse])
async def get_pinned_channel_messages( async def get_pinned_channel_messages(
id: str, page: int = 1, user=Depends(get_verified_user) request: Request,
id: str,
page: int = 1,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1090,6 +1146,7 @@ async def post_new_message(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
try: try:
message, channel = await new_message_handler(request, id, form_data, user) message, channel = await new_message_handler(request, id, form_data, user)
@ -1134,8 +1191,12 @@ async def post_new_message(
@router.get("/{id}/messages/{message_id}", response_model=Optional[MessageResponse]) @router.get("/{id}/messages/{message_id}", response_model=Optional[MessageResponse])
async def get_channel_message( async def get_channel_message(
id: str, message_id: str, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1183,8 +1244,12 @@ async def get_channel_message(
@router.get("/{id}/messages/{message_id}/data", response_model=Optional[dict]) @router.get("/{id}/messages/{message_id}/data", response_model=Optional[dict])
async def get_channel_message_data( async def get_channel_message_data(
id: str, message_id: str, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1231,8 +1296,13 @@ class PinMessageForm(BaseModel):
"/{id}/messages/{message_id}/pin", response_model=Optional[MessageUserResponse] "/{id}/messages/{message_id}/pin", response_model=Optional[MessageUserResponse]
) )
async def pin_channel_message( async def pin_channel_message(
id: str, message_id: str, form_data: PinMessageForm, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
form_data: PinMessageForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1290,12 +1360,14 @@ async def pin_channel_message(
"/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse] "/{id}/messages/{message_id}/thread", response_model=list[MessageUserResponse]
) )
async def get_channel_thread_messages( async def get_channel_thread_messages(
request: Request,
id: str, id: str,
message_id: str, message_id: str,
skip: int = 0, skip: int = 0,
limit: int = 50, limit: int = 50,
user=Depends(get_verified_user), user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1348,8 +1420,13 @@ async def get_channel_thread_messages(
"/{id}/messages/{message_id}/update", response_model=Optional[MessageModel] "/{id}/messages/{message_id}/update", response_model=Optional[MessageModel]
) )
async def update_message_by_id( async def update_message_by_id(
id: str, message_id: str, form_data: MessageForm, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
form_data: MessageForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1423,8 +1500,13 @@ class ReactionForm(BaseModel):
@router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool) @router.post("/{id}/messages/{message_id}/reactions/add", response_model=bool)
async def add_reaction_to_message( async def add_reaction_to_message(
id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
form_data: ReactionForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1492,8 +1574,13 @@ async def add_reaction_to_message(
@router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool) @router.post("/{id}/messages/{message_id}/reactions/remove", response_model=bool)
async def remove_reaction_by_id_and_user_id_and_name( async def remove_reaction_by_id_and_user_id_and_name(
id: str, message_id: str, form_data: ReactionForm, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
form_data: ReactionForm,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(
@ -1564,8 +1651,12 @@ async def remove_reaction_by_id_and_user_id_and_name(
@router.delete("/{id}/messages/{message_id}/delete", response_model=bool) @router.delete("/{id}/messages/{message_id}/delete", response_model=bool)
async def delete_message_by_id( async def delete_message_by_id(
id: str, message_id: str, user=Depends(get_verified_user) request: Request,
id: str,
message_id: str,
user=Depends(get_verified_user),
): ):
check_channels_access(request)
channel = Channels.get_channel_by_id(id) channel = Channels.get_channel_by_id(id)
if not channel: if not channel:
raise HTTPException( raise HTTPException(