Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email oauth 2 #471

Open
wants to merge 20 commits into
base: diamoerp-develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
02c6105
fix(email_account): Decode uid from bytes before update
gavindsouza Feb 21, 2022
ca2d987
fix: Strip all spacing characters from Message-ID & In-Reply-To (back…
shariquerik May 27, 2022
f7df0aa
Update email_account.py
finbyz May 30, 2022
24a5c82
fix: don't try appending email to sent folder if enable incoming and …
skjbulcher Jul 1, 2022
8b1f0ce
feat: STARTTLS authentication for IMAP (backport #17683) (#17881)
FHenry Aug 22, 2022
edf8ee2
feat: email oauth (v13) (#20790) (#20800)
mergify[bot] Apr 20, 2023
bbdf334
feat: oauth
fproldan Oct 21, 2024
447cf57
fix: Raise email account missing error if found while sending mail
leela Sep 22, 2021
b669137
fix: Sendmail is failing randomly
leela Sep 22, 2021
ceefcf3
fix: optimise `mark_email_as_seen`
sagarvora Nov 17, 2021
0804895
fix: enforce GET method
sagarvora Nov 17, 2021
96ec563
fix: move commit call to `mark_email_as_seen`
sagarvora Nov 17, 2021
91f9ca3
fix: slightly better naming
sagarvora Nov 17, 2021
bf580d1
fix: Resolve conflicts
surajshetty3416 Nov 22, 2021
544f5a0
fix: Double signature in composed Email (backport #16178) (#16228)
mergify[bot] Mar 9, 2022
3d40152
fix: Strip all spacing characters from Message-ID & In-Reply-To (back…
shariquerik May 27, 2022
af2d6f7
feat: Auto-expire web view link key
surajshetty3416 May 31, 2022
84eec13
fix: remove bare exceptions
ankush Jun 29, 2022
afe30a0
fix: Issue sending email with attachement (that are not added by send…
FHenry Jun 30, 2022
bf5a4e0
fix: type conversion for read receipt in communication email
DaizyModi Dec 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frappe/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def symlink(target, link_name, overwrite=False):
os.replace(temp_link_name, link_name)
except AttributeError:
os.renames(temp_link_name, link_name)
except:
except Exception:
if os.path.islink(temp_link_name):
os.remove(temp_link_name)
raise
Expand Down
6 changes: 4 additions & 2 deletions frappe/commands/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ def _is_scheduler_enabled():
enable_scheduler = False
try:
frappe.connect()
enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False
except:
enable_scheduler = (
cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False
)
except Exception:
pass
finally:
frappe.db.close()
Expand Down
39 changes: 39 additions & 0 deletions frappe/core/doctype/communication/communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from frappe.utils.user import is_system_user
from frappe.contacts.doctype.contact.contact import get_contact_name
from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule
from parse import compile

exclude_from_linked_with = True

Expand Down Expand Up @@ -111,6 +112,44 @@ def after_insert(self):
frappe.publish_realtime('new_message', self.as_dict(),
user=self.reference_name, after_commit=True)

def set_signature_in_email_content(self):
"""Set sender's User.email_signature or default outgoing's EmailAccount.signature to the email
"""
if not self.content:
return

quill_parser = compile('<div class="ql-editor read-mode">{}</div>')
email_body = quill_parser.parse(self.content)

if not email_body:
return

email_body = email_body[0]

user_email_signature = frappe.db.get_value(
"User",
self.sender,
"email_signature",
) if self.sender else None

signature = user_email_signature or frappe.db.get_value(
"Email Account",
{"default_outgoing": 1, "add_signature": 1},
"signature",
)

if not signature:
return

_signature = quill_parser.parse(signature)[0] if "ql-editor" in signature else None

if (_signature or signature) not in self.content:
self.content = f'{self.content}</p><br><p class="signature">{signature}'

def before_save(self):
if not self.flags.skip_add_signature:
self.set_signature_in_email_content()

def on_update(self):
# add to _comment property of the doctype, so it shows up in
# comments count for the list view
Expand Down
172 changes: 126 additions & 46 deletions frappe/core/doctype/communication/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@
from frappe.utils import (get_url, get_formatted_email, cint, list_to_str,
validate_email_address, split_emails, parse_addr, get_datetime)
from frappe.email.email_body import get_message_id
import frappe.email.smtp
import time
from frappe import _
from frappe.utils import (
cint,
get_datetime,
get_formatted_email,
get_string_between,
get_url,
list_to_str,
parse_addr,
split_emails,
validate_email_address,
)
from frappe.utils.background_jobs import enqueue

@frappe.whitelist()
def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False,
print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None,
flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None,
ignore_permissions=False):
"""Make a new communication.
read_receipt=None, print_letterhead=True, email_template=None, communication_type=None, **kwargs):
"""Make a new communication. Checks for email permissions for specified Document.

:param doctype: Reference DocType.
:param name: Reference Document name.
Expand All @@ -39,18 +46,71 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
:param send_me_a_copy: Send a copy to the sender (default **False**).
:param email_template: Template which is used to compose mail .
"""
if kwargs:
from frappe.utils.commands import warn
warn(
f"Options {kwargs} used in frappe.core.doctype.communication.email.make "
"are deprecated or unsupported",
category=DeprecationWarning
)

if doctype and name and not frappe.has_permission(doctype=doctype, ptype="email", doc=name):
raise frappe.PermissionError(
f"You are not allowed to send emails related to: {doctype} {name}"
)

return _make(
doctype=doctype,
name=name,
content=content,
subject=subject,
sent_or_received=sent_or_received,
sender=sender,
sender_full_name=sender_full_name,
recipients=recipients,
communication_medium=communication_medium,
send_email=send_email,
print_html=print_html,
print_format=print_format,
attachments=attachments,
send_me_a_copy=cint(send_me_a_copy),
cc=cc,
bcc=bcc,
read_receipt=cint(read_receipt),
print_letterhead=print_letterhead,
email_template=email_template,
communication_type=communication_type,
add_signature=False,
)

is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
send_me_a_copy = cint(send_me_a_copy)

if not ignore_permissions:
if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
doctype=doctype, name=name))

if not sender:
sender = get_formatted_email(frappe.session.user)
def _make(
doctype=None,
name=None,
content=None,
subject=None,
sent_or_received="Sent",
sender=None,
sender_full_name=None,
recipients=None,
communication_medium="Email",
send_email=False,
print_html=None,
print_format=None,
attachments="[]",
send_me_a_copy=False,
cc=None,
bcc=None,
read_receipt=None,
print_letterhead=True,
email_template=None,
communication_type=None,
add_signature=True,
):
"""Internal method to make a new communication that ignores Permission checks.
"""

sender = sender or get_formatted_email(frappe.session.user)
recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients
cc = list_to_str(cc) if isinstance(cc, list) else cc
bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc
Expand All @@ -72,10 +132,10 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"message_id":get_message_id().strip(" <>"),
"read_receipt":read_receipt,
"has_attachment": 1 if attachments else 0,
"communication_type": communication_type
}).insert(ignore_permissions=True)

comm.save(ignore_permissions=True)
"communication_type": communication_type,
})
comm.flags.skip_add_signature = not add_signature
comm.insert(ignore_permissions=True)

if isinstance(attachments, string_types):
attachments = json.loads(attachments)
Expand Down Expand Up @@ -257,7 +317,7 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None)
# is it a filename?
try:
# check for both filename and file id
file_id = frappe.db.get_list('File', or_filters={'file_name': a, 'name': a}, limit=1)
file_id = frappe.db.get_all("File", or_filters={"file_name": a, "name": a}, limit=1)
if not file_id:
frappe.throw(_("Unable to find attachment {0}").format(a))
file_id = file_id[0]['name']
Expand Down Expand Up @@ -431,13 +491,15 @@ def get_assignees(doc):

def get_attach_link(doc, print_format):
"""Returns public link for the attachment via `templates/emails/print_link.html`."""
return frappe.get_template("templates/emails/print_link.html").render({
"url": get_url(),
"doctype": doc.reference_doctype,
"name": doc.reference_name,
"print_format": print_format,
"key": get_parent_doc(doc).get_signature()
})
return frappe.get_template("templates/emails/print_link.html").render(
{
"url": get_url(),
"doctype": doc.reference_doctype,
"name": doc.reference_name,
"print_format": print_format,
"key": get_parent_doc(doc).get_document_share_key(),
}
)

def sendmail(communication_name, print_html=None, print_format=None, attachments=None,
recipients=None, cc=None, bcc=None, lang=None, session=None, print_letterhead=None):
Expand Down Expand Up @@ -472,29 +534,47 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments
else:
break

except:
except Exception:
traceback = frappe.log_error("frappe.core.doctype.communication.email.sendmail")
raise

@frappe.whitelist(allow_guest=True)
def mark_email_as_seen(name=None):
@frappe.whitelist(allow_guest=True, methods=("GET",))
def mark_email_as_seen(name: str = None):
try:
if name and frappe.db.exists("Communication", name) and not frappe.db.get_value("Communication", name, "read_by_recipient"):
frappe.db.set_value("Communication", name, "read_by_recipient", 1)
frappe.db.set_value("Communication", name, "delivery_status", "Read")
frappe.db.set_value("Communication", name, "read_by_recipient_on", get_datetime())
frappe.db.commit()
update_communication_as_read(name)
frappe.db.commit() # nosemgrep: this will be called in a GET request

except Exception:
frappe.log_error(frappe.get_traceback())

finally:
# Return image as response under all circumstances
from PIL import Image
import io
im = Image.new('RGBA', (1, 1))
im.putdata([(255,255,255,0)])
buffered_obj = io.BytesIO()
im.save(buffered_obj, format="PNG")

frappe.response["type"] = 'binary'
frappe.response["filename"] = "imaginary_pixel.png"
frappe.response["filecontent"] = buffered_obj.getvalue()
frappe.response.update({
"type": "binary",
"filename": "imaginary_pixel.png",
"filecontent": (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00"
b"\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\r"
b"IDATx\x9cc\xf8\xff\xff?\x03\x00\x08\xfc\x02\xfe\xa7\x9a\xa0"
b"\xa0\x00\x00\x00\x00IEND\xaeB`\x82"
)
})

def update_communication_as_read(name):
if not name or not isinstance(name, str):
return

communication = frappe.db.get_value(
"Communication",
name,
"read_by_recipient",
as_dict=True
)

if not communication or communication.read_by_recipient:
return

frappe.db.set_value("Communication", name, {
"read_by_recipient": 1,
"delivery_status": "Read",
"read_by_recipient_on": get_datetime()
})
2 changes: 1 addition & 1 deletion frappe/core/doctype/data_import_legacy/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def attach_file_to_doc(doctype, docname, file_url):
try:
doctype = get_header_row(get_data_keys_definition().main_table)[1]
columns = filter_empty_columns(get_header_row(get_data_keys_definition().columns)[1:])
except:
except Exception:
frappe.throw(_("Cannot change header content"))
doctypes = []
column_idx_to_fieldname = {}
Expand Down
11 changes: 8 additions & 3 deletions frappe/core/doctype/doctype/test_doctype.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,14 @@ def test_sync_field_order(self):

test_doctype.save()
test_doctype_json = frappe.get_file_json(path)
self.assertListEqual([f['fieldname'] for f in test_doctype_json['fields']], ['field_1', 'field_2', 'field_4', 'field_5'])
self.assertListEqual(test_doctype_json['field_order'], ['field_4', 'field_5', 'field_1', 'field_2'])
except:
self.assertListEqual(
[f["fieldname"] for f in test_doctype_json["fields"]],
["field_1", "field_2", "field_4", "field_5"],
)
self.assertListEqual(
test_doctype_json["field_order"], ["field_4", "field_5", "field_1", "field_2"]
)
except Exception:
raise
finally:
frappe.flags.allow_doctype_export = 0
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions frappe/core/doctype/document_share_key/document_share_key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2022, Frappe Technologies and contributors
// For license information, please see license.txt

frappe.ui.form.on('Document Share Key', {
// refresh: function(frm) {

// }
});
Loading
Loading