mirror of
https://github.com/open-webui/open-webui.git
synced 2026-04-26 09:35:29 +02:00
refac
This commit is contained in:
@@ -7,17 +7,13 @@ from functools import lru_cache
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import Base, get_db, get_db_context
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.utils.access_control import has_access
|
||||
from open_webui.models.users import User, UserModel, Users, UserResponse
|
||||
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
||||
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy import BigInteger, Boolean, Column, String, Text, JSON
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
|
||||
from sqlalchemy import or_, func, select, and_, text, cast, or_, and_, func
|
||||
from sqlalchemy.sql import exists
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from sqlalchemy import BigInteger, Column, Text, JSON
|
||||
from sqlalchemy import or_, func, cast
|
||||
|
||||
####################
|
||||
# Note DB Schema
|
||||
@@ -34,8 +30,6 @@ class Note(Base):
|
||||
data = Column(JSON, nullable=True)
|
||||
meta = Column(JSON, nullable=True)
|
||||
|
||||
access_control = Column(JSON, nullable=True)
|
||||
|
||||
created_at = Column(BigInteger)
|
||||
updated_at = Column(BigInteger)
|
||||
|
||||
@@ -50,7 +44,7 @@ class NoteModel(BaseModel):
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
|
||||
access_control: Optional[dict] = None
|
||||
access_grants: list[AccessGrantModel] = Field(default_factory=list)
|
||||
|
||||
created_at: int # timestamp in epoch
|
||||
updated_at: int # timestamp in epoch
|
||||
@@ -65,14 +59,14 @@ class NoteForm(BaseModel):
|
||||
title: str
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
access_control: Optional[dict] = None
|
||||
access_grants: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class NoteUpdateForm(BaseModel):
|
||||
title: Optional[str] = None
|
||||
data: Optional[dict] = None
|
||||
meta: Optional[dict] = None
|
||||
access_control: Optional[dict] = None
|
||||
access_grants: Optional[list[dict]] = None
|
||||
|
||||
|
||||
class NoteUserResponse(NoteModel):
|
||||
@@ -94,122 +88,25 @@ class NoteListResponse(BaseModel):
|
||||
|
||||
|
||||
class NoteTable:
|
||||
def _get_access_grants(
|
||||
self, note_id: str, db: Optional[Session] = None
|
||||
) -> list[AccessGrantModel]:
|
||||
return AccessGrants.get_grants_by_resource("note", note_id, db=db)
|
||||
|
||||
def _to_note_model(self, note: Note, db: Optional[Session] = None) -> NoteModel:
|
||||
note_data = NoteModel.model_validate(note).model_dump(exclude={"access_grants"})
|
||||
note_data["access_grants"] = self._get_access_grants(note_data["id"], db=db)
|
||||
return NoteModel.model_validate(note_data)
|
||||
|
||||
def _has_permission(self, db, query, filter: dict, permission: str = "read"):
|
||||
group_ids = filter.get("group_ids", [])
|
||||
user_id = filter.get("user_id")
|
||||
dialect_name = db.bind.dialect.name
|
||||
|
||||
conditions = []
|
||||
|
||||
# Handle read_only permission separately
|
||||
if permission == "read_only":
|
||||
# For read_only, we want items where:
|
||||
# 1. User has explicit read permission (via groups or user-level)
|
||||
# 2. BUT does NOT have write permission
|
||||
# 3. Public items are NOT considered read_only
|
||||
|
||||
read_conditions = []
|
||||
|
||||
# Group-level read permission
|
||||
if group_ids:
|
||||
group_read_conditions = []
|
||||
for gid in group_ids:
|
||||
if dialect_name == "sqlite":
|
||||
group_read_conditions.append(
|
||||
Note.access_control["read"]["group_ids"].contains([gid])
|
||||
)
|
||||
elif dialect_name == "postgresql":
|
||||
group_read_conditions.append(
|
||||
cast(
|
||||
Note.access_control["read"]["group_ids"],
|
||||
JSONB,
|
||||
).contains([gid])
|
||||
)
|
||||
|
||||
if group_read_conditions:
|
||||
read_conditions.append(or_(*group_read_conditions))
|
||||
|
||||
# Combine read conditions
|
||||
if read_conditions:
|
||||
has_read = or_(*read_conditions)
|
||||
else:
|
||||
# If no read conditions, return empty result
|
||||
return query.filter(False)
|
||||
|
||||
# Now exclude items where user has write permission
|
||||
write_exclusions = []
|
||||
|
||||
# Exclude items owned by user (they have implicit write)
|
||||
if user_id:
|
||||
write_exclusions.append(Note.user_id != user_id)
|
||||
|
||||
# Exclude items where user has explicit write permission via groups
|
||||
if group_ids:
|
||||
group_write_conditions = []
|
||||
for gid in group_ids:
|
||||
if dialect_name == "sqlite":
|
||||
group_write_conditions.append(
|
||||
Note.access_control["write"]["group_ids"].contains([gid])
|
||||
)
|
||||
elif dialect_name == "postgresql":
|
||||
group_write_conditions.append(
|
||||
cast(
|
||||
Note.access_control["write"]["group_ids"],
|
||||
JSONB,
|
||||
).contains([gid])
|
||||
)
|
||||
|
||||
if group_write_conditions:
|
||||
# User should NOT have write permission
|
||||
write_exclusions.append(~or_(*group_write_conditions))
|
||||
|
||||
# Exclude public items (items without access_control)
|
||||
write_exclusions.append(Note.access_control.isnot(None))
|
||||
write_exclusions.append(cast(Note.access_control, String) != "null")
|
||||
|
||||
# Combine: has read AND does not have write AND not public
|
||||
if write_exclusions:
|
||||
query = query.filter(and_(has_read, *write_exclusions))
|
||||
else:
|
||||
query = query.filter(has_read)
|
||||
|
||||
return query
|
||||
|
||||
# Original logic for other permissions (read, write, etc.)
|
||||
# Public access conditions
|
||||
if group_ids or user_id:
|
||||
conditions.extend(
|
||||
[
|
||||
Note.access_control.is_(None),
|
||||
cast(Note.access_control, String) == "null",
|
||||
]
|
||||
)
|
||||
|
||||
# User-level permission (owner has all permissions)
|
||||
if user_id:
|
||||
conditions.append(Note.user_id == user_id)
|
||||
|
||||
# Group-level permission
|
||||
if group_ids:
|
||||
group_conditions = []
|
||||
for gid in group_ids:
|
||||
if dialect_name == "sqlite":
|
||||
group_conditions.append(
|
||||
Note.access_control[permission]["group_ids"].contains([gid])
|
||||
)
|
||||
elif dialect_name == "postgresql":
|
||||
group_conditions.append(
|
||||
cast(
|
||||
Note.access_control[permission]["group_ids"],
|
||||
JSONB,
|
||||
).contains([gid])
|
||||
)
|
||||
conditions.append(or_(*group_conditions))
|
||||
|
||||
if conditions:
|
||||
query = query.filter(or_(*conditions))
|
||||
|
||||
return query
|
||||
return AccessGrants.has_permission_filter(
|
||||
db=db,
|
||||
query=query,
|
||||
DocumentModel=Note,
|
||||
filter=filter,
|
||||
resource_type="note",
|
||||
permission=permission,
|
||||
)
|
||||
|
||||
def insert_new_note(
|
||||
self, user_id: str, form_data: NoteForm, db: Optional[Session] = None
|
||||
@@ -219,17 +116,21 @@ class NoteTable:
|
||||
**{
|
||||
"id": str(uuid.uuid4()),
|
||||
"user_id": user_id,
|
||||
**form_data.model_dump(),
|
||||
**form_data.model_dump(exclude={"access_grants"}),
|
||||
"created_at": int(time.time_ns()),
|
||||
"updated_at": int(time.time_ns()),
|
||||
"access_grants": [],
|
||||
}
|
||||
)
|
||||
|
||||
new_note = Note(**note.model_dump())
|
||||
new_note = Note(**note.model_dump(exclude={"access_grants"}))
|
||||
|
||||
db.add(new_note)
|
||||
db.commit()
|
||||
return note
|
||||
AccessGrants.set_access_grants(
|
||||
"note", note.id, form_data.access_grants, db=db
|
||||
)
|
||||
return self._to_note_model(new_note, db=db)
|
||||
|
||||
def get_notes(
|
||||
self, skip: int = 0, limit: int = 50, db: Optional[Session] = None
|
||||
@@ -241,7 +142,7 @@ class NoteTable:
|
||||
if limit is not None:
|
||||
query = query.limit(limit)
|
||||
notes = query.all()
|
||||
return [NoteModel.model_validate(note) for note in notes]
|
||||
return [self._to_note_model(note, db=db) for note in notes]
|
||||
|
||||
def search_notes(
|
||||
self,
|
||||
@@ -330,7 +231,7 @@ class NoteTable:
|
||||
for note, user in items:
|
||||
notes.append(
|
||||
NoteUserResponse(
|
||||
**NoteModel.model_validate(note).model_dump(),
|
||||
**self._to_note_model(note, db=db).model_dump(),
|
||||
user=(
|
||||
UserResponse(**UserModel.model_validate(user).model_dump())
|
||||
if user
|
||||
@@ -365,14 +266,14 @@ class NoteTable:
|
||||
query = query.limit(limit)
|
||||
|
||||
notes = query.all()
|
||||
return [NoteModel.model_validate(note) for note in notes]
|
||||
return [self._to_note_model(note, db=db) for note in notes]
|
||||
|
||||
def get_note_by_id(
|
||||
self, id: str, db: Optional[Session] = None
|
||||
) -> Optional[NoteModel]:
|
||||
with get_db_context(db) as db:
|
||||
note = db.query(Note).filter(Note.id == id).first()
|
||||
return NoteModel.model_validate(note) if note else None
|
||||
return self._to_note_model(note, db=db) if note else None
|
||||
|
||||
def update_note_by_id(
|
||||
self, id: str, form_data: NoteUpdateForm, db: Optional[Session] = None
|
||||
@@ -391,17 +292,20 @@ class NoteTable:
|
||||
if "meta" in form_data:
|
||||
note.meta = {**note.meta, **form_data["meta"]}
|
||||
|
||||
if "access_control" in form_data:
|
||||
note.access_control = form_data["access_control"]
|
||||
if "access_grants" in form_data:
|
||||
AccessGrants.set_access_grants(
|
||||
"note", id, form_data["access_grants"], db=db
|
||||
)
|
||||
|
||||
note.updated_at = int(time.time_ns())
|
||||
|
||||
db.commit()
|
||||
return NoteModel.model_validate(note) if note else None
|
||||
return self._to_note_model(note, db=db) if note else None
|
||||
|
||||
def delete_note_by_id(self, id: str, db: Optional[Session] = None) -> bool:
|
||||
try:
|
||||
with get_db_context(db) as db:
|
||||
AccessGrants.revoke_all_access("note", id, db=db)
|
||||
db.query(Note).filter(Note.id == id).delete()
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user