Files
chat/raven/permissions.py

416 lines
13 KiB
Python

import frappe
from raven.utils import (
get_channel_member,
get_workspace_member,
is_channel_member,
is_workspace_member,
)
def check_app_permission():
"""Check if user has permission to access the app (for showing the app on app screen)"""
if frappe.session.user == "Administrator":
return True
if frappe.db.exists("Raven User", {"user": frappe.session.user}):
return True
return False
def raven_user_has_permission(doc, user=None, ptype=None):
if doc.type == "Bot":
# Anyone with Raven User role can change the bot details
if user != "Guest":
return True
else:
# Only the user can change their own details
if doc.user == user:
return True
if ptype == "read":
return True
return False
def workspace_has_permission(doc, user=None, ptype=None):
"""
1. Only Raven Admins can create a workspace
2. Only Workspace admins can update/delete a workspace
3. If the workspace is public, anyone can read
4. If the workspace is private, only member can read
"""
if ptype == "create":
# Return True since this is checked via the Role in the doctype - only Raven Admins can create a workspace
return True
if ptype == "write" or ptype == "delete":
# Only Workspace admins can update/delete a workspace
workspace_member = get_workspace_member(doc.name, user)
if workspace_member and workspace_member.get("is_admin"):
return True
else:
return False
if ptype == "read":
if doc.type == "Public":
return True
if doc.type == "Private":
# Check if the user is a member of the workspace
if is_workspace_member(doc.name, user):
return True
return False
def workspace_member_has_permission(doc, user=None, ptype=None):
# If the workspace is a public workspace, anyone can join
if ptype == "create":
workspace_type = frappe.get_cached_value("Raven Workspace", doc.workspace, "type")
if workspace_type == "Public":
if user == doc.user:
return True
else:
workspace_member = get_workspace_member(doc.workspace, user)
if workspace_member and workspace_member.get("is_admin"):
return True
if workspace_type == "Private":
workspace_member = get_workspace_member(doc.workspace, user)
if workspace_member and workspace_member.get("is_admin"):
return True
if ptype == "write":
workspace_member = get_workspace_member(doc.workspace, user)
if workspace_member and workspace_member.get("is_admin"):
return True
if ptype == "delete":
if doc.user == user:
return True
else:
workspace_member = get_workspace_member(doc.workspace, user)
if workspace_member and workspace_member.get("is_admin"):
return True
if ptype == "read":
if doc.user == user:
return True
if is_workspace_member(doc.workspace, user):
return True
return False
def channel_has_permission(doc, user=None, ptype=None):
if doc.is_direct_message:
# Users can create direct message channels
if ptype == "create":
return True
if ptype == "read":
return is_channel_member(doc.name, user)
elif doc.is_thread:
if ptype == "create":
# Users can create threads in channels they are a member of the main channel in which the thread is created
main_channel = frappe.get_cached_value("Raven Message", doc.channel_name, "channel_id")
if is_channel_member(main_channel, user):
return True
if ptype == "read":
main_channel = frappe.get_cached_value("Raven Message", doc.channel_name, "channel_id")
# Check if the user has read permission to the main channel
channel_doc = frappe.get_cached_doc("Raven Channel", main_channel)
return channel_doc.has_permission("read", user=user)
if ptype == "delete":
# Only the creator of the thread can delete the thread
return doc.owner == user
else:
# For regular channels
if ptype == "create":
# Only workspace admins can create a channel
workspace_member = get_workspace_member(doc.workspace, user)
if workspace_member and workspace_member.get("is_admin"):
return True
# If the workspace allows any member to create a channel, then the user can create a channel
if not frappe.db.get_value("Raven Workspace", doc.workspace, "only_admins_can_create_channels"):
return True
if ptype == "delete" or ptype == "write":
# Only channel admins can update or delete a channel
channel_member = get_channel_member(doc.name, user)
if channel_member and channel_member.get("is_admin"):
return True
if ptype == "read":
# Check if the channel type is public or open
if doc.type == "Public" or doc.type == "Open":
return is_workspace_member(doc.workspace, user)
else:
return is_channel_member(doc.name, user)
return False
# if doc.type == "Open" or doc.type == "Public":
# # If the channel is public/open, check if the user is a member of the workspace
# if doc.workspace:
# return frappe.db.exists("Raven Workspace Member", {"workspace": doc.workspace, "user": user})
# return True
# elif doc.type == "Private":
# if doc.is_thread:
# if ptype == "read" or ptype == "create":
# # Only users who are part of the original channel can read the thread
# return frappe.has_permission(doctype="Raven Message", doc=doc.name, ptype="read")
# if frappe.db.exists("Raven Channel Member", {"channel_id": doc.name, "user_id": user}):
# return True
# elif (
# doc.owner == user and frappe.db.count("Raven Channel Member", {"channel_id": doc.name}) <= 0
# ):
# return True
# else:
# return False
def channel_member_has_permission(doc, user=None, ptype=None):
# Allow reads for self and if the user is a member of the channel
# They cannot make themselves an admin but this is handled in the controller method
if doc.user_id == user:
if ptype == "read" or ptype == "write":
return True
if ptype == "read":
# Other channel members can read the channel member document
return frappe.db.exists("Raven Channel Member", {"channel_id": doc.channel_id, "user_id": user})
channel_doc = frappe.get_cached_doc("Raven Channel", doc.channel_id)
channel_type = channel_doc.type
is_direct_message = channel_doc.is_direct_message
workspace = channel_doc.workspace
if ptype == "create":
if is_direct_message:
# This is handled in the creation of a direct message channel so we can safely return False
return False
elif doc.user_id == user:
# Check if the channel is open or public. If yes, then the user needs to be a member of the workspace
# if the channel is private, the user cannot add themselves to the channel
if channel_type == "Open" or channel_type == "Public":
return is_workspace_member(workspace, user)
if channel_doc.is_thread:
# Check if the user is a member of the main channel
main_channel = frappe.db.get_value("Raven Message", doc.channel_id, "channel_id")
return is_channel_member(main_channel, user)
else:
# Someone else is adding a new member to the channel
# Only existing channel members can add a new channel member
# If the member to be added is a member of the workspace, they can be added to the channel
if is_workspace_member(workspace, doc.user_id):
return frappe.db.exists(
"Raven Channel Member", {"channel_id": doc.channel_id, "user_id": user}
)
if ptype == "delete":
if is_direct_message:
# Users cannot delete their members in a direct message channel
return False
elif doc.user_id == user:
# Users can delete their own channel member document
return True
else:
# Only channel admins can delete a channel member
channel_member = get_channel_member(doc.channel_id, user)
if channel_member and channel_member.get("is_admin"):
return True
if ptype == "write":
# Only channel admins can update a channel member
channel_member = get_channel_member(doc.channel_id, user)
if channel_member and channel_member.get("is_admin"):
return True
# Allow self to modify their own channel member document
# if doc.user_id == user:
# if ptype == "create":
# return True
# # Check if the user is a member of the worksp
# if ptype in ["delete", "write"]:
# return True
# if channel_type == "Open" or channel_type == "Public":
# return True
# if channel_type == "Private":
# # If it's a private channel, only the members can modify the channel member
# if frappe.db.exists("Raven Channel Member", {"channel_id": doc.channel_id, "user_id": user}):
# return True
return False
def message_has_permission(doc, user=None, ptype=None):
# To send any message, the user needs to be a member of the channel
if ptype == "read":
channel_doc = frappe.get_cached_doc("Raven Channel", doc.channel_id)
return channel_has_permission(channel_doc, user, ptype)
# To create, update, or delete a message, the user needs to own this message
if ptype in ["create", "write", "delete"]:
channel_member = get_channel_member(doc.channel_id, user)
if not channel_member:
return False
return doc.owner == user
return False
def raven_poll_vote_has_permission(doc, user=None, ptype=None):
"""
Allowed users can add a vote to a poll and read votes (if the poll is not anonymous)
"""
# Check if the user has permission to the poll itself
if not frappe.has_permission("Raven Poll", doc=doc.poll_id, ptype="read", user=user):
return False
if ptype == "create":
# User can only vote if they are a member of the channel
channel_id = frappe.get_cached_value("Raven Message", {"poll_id": doc.poll_id}, "channel_id")
if is_channel_member(channel_id):
if doc.owner == user:
return True
else:
return False
if ptype in ["read", "delete"]:
if doc.owner == user:
return True
else:
is_anonymous = frappe.get_cached_value("Raven Poll", doc.poll_id, "is_anonymous")
if not is_anonymous:
if ptype == "read":
return True
return False
def raven_poll_has_permission(doc, user=None, ptype=None):
"""
Allowed users can create a poll and read polls.
Only the poll creator can delete the poll
"""
if ptype == "read" or ptype == "delete" or ptype == "write":
# Permissions to read the poll are delegated to the message associated with the poll
message = frappe.get_doc("Raven Message", {"poll_id": doc.name})
return message.has_permission(ptype, user=user)
if ptype == "create":
if doc.owner == user:
return True
return False
def raven_workspace_query(user):
if not user:
user = frappe.session.user
# Get all workspaces that the user is a member of
workspace_members = frappe.get_all(
"Raven Workspace Member", filters={"user": user}, fields=["workspace"]
)
workspace_names = [frappe.db.escape(member.workspace) for member in workspace_members]
if workspace_names:
return f"`tabRaven Workspace`.name in ({', '.join(workspace_names)}) OR `tabRaven Workspace`.type = 'Public'"
else:
return "`tabRaven Workspace`.type = 'Public'"
def raven_workspace_member_query(user):
if not user:
user = frappe.session.user
# Get all workspaces that the user is a member of
workspace_members = frappe.get_all(
"Raven Workspace Member", filters={"user": user}, fields=["workspace"]
)
workspace_names = [frappe.db.escape(member.workspace) for member in workspace_members]
if workspace_names:
return f"`tabRaven Workspace Member`.workspace in ({', '.join(workspace_names)})"
else:
return f"`tabRaven Workspace Member`.owner = {frappe.db.escape(user)}"
def raven_channel_query(user):
if not user:
user = frappe.session.user
"""
Only show channels that the user is a owner of
We could also remove "Raven User" from the Raven Channel doctype role, but then permission checks for joining socket rooms for the channel would fail
Hence, we are adding a WHERE clause to the query - this is inconsequential since we will never use the standard get_list query for Raven Channel,
but needed for security since we do not want users to be able to view channels they are not a member of
"""
return f"`tabRaven Channel`.owner = {frappe.db.escape(user)}"
def raven_message_query(user):
if not user:
user = frappe.session.user
"""
Only show messages created by the user using a WHERE clause
We could also remove "Raven User" from the Raven Message doctype role, but then permission checks for attached files would also fail.
Hence, we are adding a WHERE clause to the query - this is inconsequential since we will never use the standard get_list query for Raven Message,
but needed for security since we do not want users to be able to view messages from channels they are not a member of
"""
return f"`tabRaven Message`.owner = {frappe.db.escape(user)}"
def raven_poll_query(user):
if not user:
user = frappe.session.user
return f"`tabRaven Poll`.owner = {frappe.db.escape(user)}"
def raven_poll_vote_query(user):
if not user:
user = frappe.session.user
"""
Only show votes created by the user using a WHERE clause
Hence, we are adding a WHERE clause to the query - this is inconsequential since we will never use the standard get_list query for Raven Poll Vote,
but needed for security since we do not want users to be able to view votes from polls they did not vote for
"""
return f"`tabRaven Poll Vote`.owner = {frappe.db.escape(user)}"