| | import json |
| | import logging |
| | from typing import Optional |
| |
|
| |
|
| | from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks |
| | from pydantic import BaseModel |
| |
|
| | from open_webui.socket.main import sio |
| |
|
| | from open_webui.models.groups import Groups |
| | from open_webui.models.users import Users, UserResponse |
| | from open_webui.models.notes import ( |
| | NoteListResponse, |
| | Notes, |
| | NoteModel, |
| | NoteForm, |
| | NoteUserResponse, |
| | ) |
| |
|
| | from open_webui.config import ( |
| | BYPASS_ADMIN_ACCESS_CONTROL, |
| | ENABLE_ADMIN_CHAT_ACCESS, |
| | ENABLE_ADMIN_EXPORT, |
| | ) |
| | from open_webui.constants import ERROR_MESSAGES |
| |
|
| |
|
| | from open_webui.utils.auth import get_admin_user, get_verified_user |
| | from open_webui.utils.access_control import has_permission |
| | from open_webui.models.access_grants import AccessGrants, has_public_read_access_grant |
| | from open_webui.internal.db import get_session |
| | from sqlalchemy.orm import Session |
| |
|
| | log = logging.getLogger(__name__) |
| |
|
| | router = APIRouter() |
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class NoteItemResponse(BaseModel): |
| | id: str |
| | title: str |
| | data: Optional[dict] |
| | updated_at: int |
| | created_at: int |
| | user: Optional[UserResponse] = None |
| |
|
| |
|
| | @router.get("/", response_model=list[NoteItemResponse]) |
| | async def get_notes( |
| | request: Request, |
| | page: Optional[int] = None, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | limit = None |
| | skip = None |
| | if page is not None: |
| | limit = 60 |
| | skip = (page - 1) * limit |
| |
|
| | notes = Notes.get_notes_by_user_id(user.id, "read", skip=skip, limit=limit, db=db) |
| | if not notes: |
| | return [] |
| |
|
| | user_ids = list(set(note.user_id for note in notes)) |
| | users = {user.id: user for user in Users.get_users_by_user_ids(user_ids, db=db)} |
| |
|
| | return [ |
| | NoteUserResponse( |
| | **{ |
| | **note.model_dump(), |
| | "user": UserResponse(**users[note.user_id].model_dump()), |
| | } |
| | ) |
| | for note in notes |
| | if note.user_id in users |
| | ] |
| |
|
| |
|
| | @router.get("/search", response_model=NoteListResponse) |
| | async def search_notes( |
| | request: Request, |
| | query: Optional[str] = None, |
| | view_option: Optional[str] = None, |
| | permission: Optional[str] = None, |
| | order_by: Optional[str] = None, |
| | direction: Optional[str] = None, |
| | page: Optional[int] = 1, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | limit = None |
| | skip = None |
| | if page is not None: |
| | limit = 60 |
| | skip = (page - 1) * limit |
| |
|
| | filter = {} |
| | if query: |
| | filter["query"] = query |
| | if view_option: |
| | filter["view_option"] = view_option |
| | if permission: |
| | filter["permission"] = permission |
| | if order_by: |
| | filter["order_by"] = order_by |
| | if direction: |
| | filter["direction"] = direction |
| |
|
| | if not user.role == "admin" or not BYPASS_ADMIN_ACCESS_CONTROL: |
| | groups = Groups.get_groups_by_member_id(user.id, db=db) |
| | if groups: |
| | filter["group_ids"] = [group.id for group in groups] |
| |
|
| | filter["user_id"] = user.id |
| |
|
| | return Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | @router.post("/create", response_model=Optional[NoteModel]) |
| | async def create_new_note( |
| | request: Request, |
| | form_data: NoteForm, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | try: |
| | note = Notes.insert_new_note(user.id, form_data, db=db) |
| | return note |
| | except Exception as e: |
| | log.exception(e) |
| | raise HTTPException( |
| | status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class NoteResponse(NoteModel): |
| | write_access: bool = False |
| |
|
| |
|
| | @router.get("/{id}", response_model=Optional[NoteResponse]) |
| | async def get_note_by_id( |
| | request: Request, |
| | id: str, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | note = Notes.get_note_by_id(id, db=db) |
| | if not note: |
| | raise HTTPException( |
| | status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND |
| | ) |
| |
|
| | if user.role != "admin" and ( |
| | user.id != note.user_id |
| | and ( |
| | not AccessGrants.has_access( |
| | user_id=user.id, |
| | resource_type="note", |
| | resource_id=note.id, |
| | permission="read", |
| | db=db, |
| | ) |
| | ) |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| | write_access = ( |
| | user.role == "admin" |
| | or (user.id == note.user_id) |
| | or AccessGrants.has_access( |
| | user_id=user.id, |
| | resource_type="note", |
| | resource_id=note.id, |
| | permission="write", |
| | db=db, |
| | ) |
| | or has_public_read_access_grant(note.access_grants) |
| | ) |
| |
|
| | return NoteResponse(**note.model_dump(), write_access=write_access) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | @router.post("/{id}/update", response_model=Optional[NoteModel]) |
| | async def update_note_by_id( |
| | request: Request, |
| | id: str, |
| | form_data: NoteForm, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | note = Notes.get_note_by_id(id, db=db) |
| | if not note: |
| | raise HTTPException( |
| | status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND |
| | ) |
| |
|
| | if user.role != "admin" and ( |
| | user.id != note.user_id |
| | and not AccessGrants.has_access( |
| | user_id=user.id, |
| | resource_type="note", |
| | resource_id=note.id, |
| | permission="write", |
| | db=db, |
| | ) |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| | |
| | if ( |
| | user.role != "admin" |
| | and has_public_read_access_grant(form_data.access_grants) |
| | and not has_permission( |
| | user.id, |
| | "sharing.public_notes", |
| | request.app.state.config.USER_PERMISSIONS, |
| | db=db, |
| | ) |
| | ): |
| | form_data.access_grants = [] |
| |
|
| | try: |
| | note = Notes.update_note_by_id(id, form_data, db=db) |
| | await sio.emit( |
| | "note-events", |
| | note.model_dump(), |
| | to=f"note:{note.id}", |
| | ) |
| |
|
| | return note |
| | except Exception as e: |
| | log.exception(e) |
| | raise HTTPException( |
| | status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | class NoteAccessGrantsForm(BaseModel): |
| | access_grants: list[dict] |
| |
|
| |
|
| | @router.post("/{id}/access/update", response_model=Optional[NoteModel]) |
| | async def update_note_access_by_id( |
| | request: Request, |
| | id: str, |
| | form_data: NoteAccessGrantsForm, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | note = Notes.get_note_by_id(id, db=db) |
| | if not note: |
| | raise HTTPException( |
| | status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND |
| | ) |
| |
|
| | if user.role != "admin" and ( |
| | user.id != note.user_id |
| | and not AccessGrants.has_access( |
| | user_id=user.id, |
| | resource_type="note", |
| | resource_id=note.id, |
| | permission="write", |
| | db=db, |
| | ) |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| | |
| | if ( |
| | user.role != "admin" |
| | and has_public_read_access_grant(form_data.access_grants) |
| | and not has_permission( |
| | user.id, |
| | "sharing.public_notes", |
| | request.app.state.config.USER_PERMISSIONS, |
| | ) |
| | ): |
| | form_data.access_grants = [ |
| | grant |
| | for grant in form_data.access_grants |
| | if not ( |
| | grant.get("principal_type") == "user" |
| | and grant.get("principal_id") == "*" |
| | ) |
| | ] |
| |
|
| | AccessGrants.set_access_grants("note", id, form_data.access_grants, db=db) |
| |
|
| | return Notes.get_note_by_id(id, db=db) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | @router.delete("/{id}/delete", response_model=bool) |
| | async def delete_note_by_id( |
| | request: Request, |
| | id: str, |
| | user=Depends(get_verified_user), |
| | db: Session = Depends(get_session), |
| | ): |
| | if user.role != "admin" and not has_permission( |
| | user.id, "features.notes", request.app.state.config.USER_PERMISSIONS, db=db |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_401_UNAUTHORIZED, |
| | detail=ERROR_MESSAGES.UNAUTHORIZED, |
| | ) |
| |
|
| | note = Notes.get_note_by_id(id, db=db) |
| | if not note: |
| | raise HTTPException( |
| | status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND |
| | ) |
| |
|
| | if user.role != "admin" and ( |
| | user.id != note.user_id |
| | and not AccessGrants.has_access( |
| | user_id=user.id, |
| | resource_type="note", |
| | resource_id=note.id, |
| | permission="write", |
| | db=db, |
| | ) |
| | ): |
| | raise HTTPException( |
| | status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|
| | try: |
| | note = Notes.delete_note_by_id(id, db=db) |
| | return True |
| | except Exception as e: |
| | log.exception(e) |
| | raise HTTPException( |
| | status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT() |
| | ) |
| |
|