mirror of
https://github.com/open-webui/open-webui.git
synced 2026-05-18 05:05:09 +02:00
fix: gate public sharing of calendars behind sharing.public_calendars permission (#24493)
* fix: gate public sharing of calendars behind sharing.public_calendars permission
The calendar router did not call filter_allowed_access_grants on either the
create or update endpoint, while every other shareable resource in the
codebase (channels, knowledge, models, notes, prompts, skills, tools) does.
A verified non-admin owner could therefore attach
`{"principal_type":"user","principal_id":"*","permission":"read"|"write"}`
to their own calendar in the create or update payload and have it persisted
unfiltered. Any other verified user with the (default-on) features.calendar
permission could then read or, for write grants, write events on it via the
existing /events* endpoints, bypassing the per-user sharing.public_<X>
permission gate the rest of the resource cohort enforces.
Three changes:
- config.py: add USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING (default
False, env-overridable) and surface it in DEFAULT_USER_PERMISSIONS
['sharing']['public_calendars'] so admins can grant it per group via the
same UI used for notes/models/etc.
- routers/calendar.py: import filter_allowed_access_grants and call it in
create_calendar with the new sharing.public_calendars key, identical to
the channel router's pattern.
- routers/calendar.py: call filter_allowed_access_grants in update_calendar
too. The pre-existing owner-only gate at L350 only restricts WHO may
change grants; the new filter restricts WHICH grants they may set, so a
non-admin owner cannot make their own calendar publicly readable or
writable without the corresponding sharing permission.
Same shape as GHSA-7rjh-px4v-5w55 (channels). Reported by Matteo Panzeri.
Co-authored-by: Matteo Panzeri <28739806+matte1782@users.noreply.github.com>
* fix: expose public_calendars + features.calendar through admin permissions surface
The earlier commit added DEFAULT_USER_PERMISSIONS['sharing']['public_calendars']
and the runtime filter call, but the new key was not yet plumbed through the
admin /users/default/permissions endpoint. Without these changes the toggle
would round-trip as silently dropped:
- routers/users.py SharingPermissions: any payload POSTed to
/default/permissions ran through `form_data.model_dump()`, and Pydantic
drops fields not declared on the model. The new public_calendars key
would have been stripped on every save, leaving admins unable to grant
the permission via the UI even though the runtime filter would honor it.
- src/lib/constants/permissions.ts: the frontend's DEFAULT_PERMISSIONS dict
is the seed shape used by the admin Groups Permissions panel; without
the new key it could not bind a Switch component to it.
- Permissions.svelte: add a Calendars Public Sharing toggle alongside the
Notes/Chats Public Sharing toggles, gated on the existing
features.calendar flag (matches the pattern used for notes/chats).
Also closes a pre-existing parity gap on features.calendar: DEFAULT_USER_
PERMISSIONS['features']['calendar'] has existed since the calendar feature
shipped, and Permissions.svelte already renders a Calendar feature toggle,
but FeaturesPermissions Pydantic and the frontend defaults never knew
about it. Adding it everywhere completes the round-trip so admin saves no
longer silently drop the calendar feature flag either.
---------
Co-authored-by: Matteo Panzeri <28739806+matte1782@users.noreply.github.com>
This commit is contained in:
@@ -1465,6 +1465,10 @@ USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING = (
|
||||
os.environ.get('USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING = (
|
||||
os.environ.get('USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING', 'False').lower() == 'true'
|
||||
)
|
||||
|
||||
USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS = (
|
||||
os.environ.get('USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS', 'True').lower() == 'true'
|
||||
)
|
||||
@@ -1585,6 +1589,7 @@ DEFAULT_USER_PERMISSIONS = {
|
||||
'notes': USER_PERMISSIONS_NOTES_ALLOW_SHARING,
|
||||
'public_notes': USER_PERMISSIONS_NOTES_ALLOW_PUBLIC_SHARING,
|
||||
'public_chats': USER_PERMISSIONS_CHAT_ALLOW_PUBLIC_SHARING,
|
||||
'public_calendars': USER_PERMISSIONS_CALENDAR_ALLOW_PUBLIC_SHARING,
|
||||
},
|
||||
'access_grants': {
|
||||
'allow_users': USER_PERMISSIONS_ACCESS_GRANTS_ALLOW_USERS,
|
||||
|
||||
@@ -22,7 +22,7 @@ from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.utils.auth import get_verified_user
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
|
||||
from open_webui.utils.calendar import expand_recurring_event
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
|
||||
@@ -112,6 +112,17 @@ async def get_calendars(request: Request, user: UserModel = Depends(get_verified
|
||||
async def create_calendar(request: Request, form_data: CalendarForm, user: UserModel = Depends(get_verified_user)):
|
||||
"""Create a new user calendar."""
|
||||
await check_calendar_permission(request, user)
|
||||
# Strip public/user grants the requesting user is not permitted to assign
|
||||
# (matches the channel/notes/models pattern). Without this, any verified user
|
||||
# could create a calendar with `principal_id='*' permission='read'|'write'`,
|
||||
# making their events readable or writable by any other verified user.
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
form_data.access_grants,
|
||||
'sharing.public_calendars',
|
||||
)
|
||||
return await Calendars.insert_new_calendar(user.id, form_data)
|
||||
|
||||
|
||||
@@ -350,6 +361,20 @@ async def update_calendar(
|
||||
if form_data.access_grants is not None and cal.user_id != user.id and user.role != 'admin':
|
||||
raise HTTPException(status_code=403, detail='Only owner can manage sharing')
|
||||
|
||||
# Strip public/user grants the requesting user is not permitted to assign
|
||||
# (matches the channel/notes/models pattern). The owner-only check above
|
||||
# only restricts WHO can set grants; this filter restricts WHICH grants
|
||||
# they may set, so a non-admin owner cannot make their calendar
|
||||
# publicly readable/writable without the corresponding sharing permission.
|
||||
if form_data.access_grants is not None:
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
form_data.access_grants,
|
||||
'sharing.public_calendars',
|
||||
)
|
||||
|
||||
updated = await Calendars.update_calendar_by_id(calendar_id, form_data)
|
||||
if not updated:
|
||||
raise HTTPException(status_code=500, detail='Failed to update')
|
||||
|
||||
@@ -194,6 +194,7 @@ class SharingPermissions(BaseModel):
|
||||
notes: bool = False
|
||||
public_notes: bool = True
|
||||
public_chats: bool = False
|
||||
public_calendars: bool = False
|
||||
|
||||
|
||||
class AccessGrantsPermissions(BaseModel):
|
||||
@@ -235,6 +236,7 @@ class FeaturesPermissions(BaseModel):
|
||||
code_interpreter: bool = True
|
||||
memories: bool = True
|
||||
automations: bool = False
|
||||
calendar: bool = True
|
||||
|
||||
|
||||
class SettingsPermissions(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user