Accept email tickets/comments for anon users

This commit is contained in:
Drew DeVault 2019-08-22 13:20:39 +09:00
parent 846417baeb
commit 204597dfb4
4 changed files with 33 additions and 22 deletions

View File

@ -10,8 +10,8 @@ from email.utils import parseaddr
from grp import getgrnam
from todosrht.access import get_tracker, get_ticket
from todosrht.types import TicketAccess, TicketResolution, Tracker, Ticket, User
from todosrht.types import Label, TicketLabel, Event, EventType
from todosrht.tickets import add_comment, get_participant_for_user, submit_ticket
from todosrht.types import Label, TicketLabel, Event, EventType, ParticipantType
from todosrht.tickets import add_comment, get_participant_for_email, submit_ticket
from todosrht.webhooks import UserWebhook, TrackerWebhook, TicketWebhook
from srht.validation import Validation
import asyncio
@ -68,10 +68,11 @@ class MailHandler:
else:
# TODO: user groups
return None, None
tracker, access = get_tracker(owner, tracker_name, user=sender)
# TODO: ACLs for email participants
tracker, access = get_tracker(owner, tracker_name, user=sender.user)
if not ticket_id:
return tracker, access
ticket, access = get_ticket(tracker, ticket_id, user=sender)
ticket, access = get_ticket(tracker, ticket_id, user=sender.user)
return ticket, access
async def handle_RCPT(self, server, session,
@ -105,8 +106,7 @@ class MailHandler:
print("Rejecting email due to validation errors")
return "550 " + ", ".join([e["reason"] for e in valid.errors])
participant = get_participant_for_user(sender)
ticket = submit_ticket(tracker, participant, title, desc)
ticket = submit_ticket(tracker, sender, title, desc)
UserWebhook.deliver(UserWebhook.Events.ticket_create,
ticket.to_dict(),
UserWebhook.Subscription.user_id == sender.id)
@ -122,8 +122,10 @@ class MailHandler:
resolution = None
resolve = reopen = False
cmds = ["!resolve", "!reopen", "!assign", "!label", "!unlabel"]
participant = get_participant_for_user(sender)
cmds = ["!resolve", "!reopen"]
if sender.participant_type == ParticipantType.user:
# TODO: This should be possible via ACLs later
cmds += ["!assign", "!label", "!unlabel"]
if any(last_line.startswith(cmd) for cmd in cmds):
cmd = shlex.split(last_line)
body = body[:-len(last_line)-1].rstrip()
@ -141,7 +143,7 @@ class MailHandler:
return ("550 The label you requested does not exist on " +
"this tracker.")
if not TicketAccess.triage in access:
print(f"Rejected, {participant.name} has insufficient " +
print(f"Rejected, {sender.name} has insufficient " +
f"permissions (have {access}, want triage)")
return "550 You do not have permission to triage on this tracker."
for label in labels:
@ -149,7 +151,7 @@ class MailHandler:
.filter(TicketLabel.label_id == label.id)
.filter(TicketLabel.ticket_id == ticket.id)).first()
event = Event()
event.participant_id = participant.id
event.participant_id = sender.id
event.ticket_id = ticket.id
event.label_id = label.id
if not ticket_label and cmd[0] == "!label":
@ -174,7 +176,7 @@ class MailHandler:
# TODO: Remaining commands
if not required_access in access:
print(f"Rejected, {participant.name} has insufficient " +
print(f"Rejected, {sender.name} has insufficient " +
f"permissions (have {access}, want {required_access})")
return "550 You do not have permission to post on this tracker."
@ -183,7 +185,7 @@ class MailHandler:
return "550 Comment must be between 3 and 16384 characters."
if body:
event = add_comment(participant, ticket, text=body,
event = add_comment(sender, ticket, text=body,
resolution=resolution, resolve=resolve, reopen=reopen)
TicketWebhook.deliver(TicketWebhook.Events.event_create,
event.to_dict(),
@ -206,14 +208,8 @@ class MailHandler:
mail = email.message_from_bytes(envelope.content,
policy=email.policy.SMTP)
_from = parseaddr(mail["From"])
sender = User.query.filter(User.email == _from[1]).one_or_none()
if not sender:
print(f"Rejecting email from unknown sender {_from[1]}")
# TODO: allow posting from users without an account
return ("550 There is no account associated with this address. " +
"Have you logged into todo.sr.ht on the web before?")
name, sender_addr = parseaddr(mail["From"])
sender = get_participant_for_email(sender_addr, name)
dest, access = self.lookup_destination(address, sender)
if dest is None:

View File

@ -158,7 +158,7 @@
<div class="updated">{{ ticket.updated | date }}</div>
<div class="submitter">
<a href="{{ ticket.submitter|participant_url }}">
{{ ticket.submitter }}
{{ ticket.submitter.name }}
</a>
</div>
<div class="comments">

View File

@ -64,6 +64,21 @@ def get_participant_for_user(user):
db.session.flush()
return participant
def get_participant_for_email(email, email_name=None):
user = User.query.filter(User.email == email).one_or_none()
if user:
return get_participant_for_user(user)
participant = Participant.query.filter(
Participant.email == email).one_or_none()
if not participant:
participant = Participant()
participant.email = email
participant.email_name = email_name
participant.participant_type = ParticipantType.email
db.session.add(participant)
db.session.flush()
return participant
def find_mentioned_users(text):
# TODO: Find mentioned email addresses as well
usernames = re.findall(USER_MENTION_PATTERN, text)

View File

@ -52,7 +52,7 @@ class Tracker(Base):
default_anonymous_perms = sa.Column(FlagType(TicketAccess),
nullable=False,
default=TicketAccess.browse)
default=TicketAccess.browse + TicketAccess.submit + TicketAccess.comment)
"""Permissions granted to anonymous (non-logged in) users"""
@staticmethod