@@ -156,6 +156,7 @@ class LoginManager: | |||
authenticate_for_2factor(self.user) | |||
if not confirm_otp_token(self): | |||
return False | |||
frappe.form_dict.pop("pwd", None) | |||
self.post_login() | |||
def post_login(self): | |||
@@ -12,13 +12,19 @@ class TestActivityLog(unittest.TestCase): | |||
# test user login log | |||
frappe.local.form_dict = frappe._dict( | |||
{"cmd": "login", "sid": "Guest", "pwd": "admin", "usr": "Administrator"} | |||
{ | |||
"cmd": "login", | |||
"sid": "Guest", | |||
"pwd": frappe.conf.admin_password or "admin", | |||
"usr": "Administrator", | |||
} | |||
) | |||
frappe.local.cookie_manager = CookieManager() | |||
frappe.local.login_manager = LoginManager() | |||
auth_log = self.get_auth_log() | |||
self.assertFalse(frappe.form_dict.pwd) | |||
self.assertEqual(auth_log.status, "Success") | |||
# test user logout log | |||
@@ -984,7 +984,9 @@ class Column: | |||
if self.df.fieldtype == "Link": | |||
# find all values that dont exist | |||
values = list({cstr(v) for v in self.column_values[1:] if v}) | |||
exists = [d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)})] | |||
exists = [ | |||
cstr(d.name) for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)}) | |||
] | |||
not_exists = list(set(values) - set(exists)) | |||
if not_exists: | |||
missing_values = ", ".join(not_exists) | |||
@@ -80,17 +80,15 @@ class CustomField(Document): | |||
check_fieldname_conflicts(self) | |||
def on_update(self): | |||
if not frappe.flags.in_setup_wizard: | |||
frappe.clear_cache(doctype=self.dt) | |||
# validate field | |||
if not self.flags.ignore_validate: | |||
# validate field | |||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype | |||
validate_fields_for_doctype(self.dt) | |||
# update the schema | |||
if not frappe.db.get_value("DocType", self.dt, "issingle") and not frappe.flags.in_setup_wizard: | |||
# clear cache and update the schema | |||
if not frappe.flags.in_create_custom_fields: | |||
frappe.clear_cache(doctype=self.dt) | |||
frappe.db.updatedb(self.dt) | |||
def on_trash(self): | |||
@@ -169,38 +167,48 @@ def create_custom_fields(custom_fields, ignore_validate=False, update=True): | |||
:param custom_fields: example `{'Sales Invoice': [dict(fieldname='test')]}`""" | |||
if not ignore_validate and frappe.flags.in_setup_wizard: | |||
ignore_validate = True | |||
for doctypes, fields in custom_fields.items(): | |||
if isinstance(fields, dict): | |||
# only one field | |||
fields = [fields] | |||
if isinstance(doctypes, str): | |||
# only one doctype | |||
doctypes = (doctypes,) | |||
for doctype in doctypes: | |||
for df in fields: | |||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]}) | |||
if not field: | |||
try: | |||
df = df.copy() | |||
df["owner"] = "Administrator" | |||
create_custom_field(doctype, df, ignore_validate=ignore_validate) | |||
except frappe.exceptions.DuplicateEntryError: | |||
pass | |||
elif update: | |||
custom_field = frappe.get_doc("Custom Field", field) | |||
custom_field.flags.ignore_validate = ignore_validate | |||
custom_field.update(df) | |||
custom_field.save() | |||
frappe.clear_cache(doctype=doctype) | |||
frappe.db.updatedb(doctype) | |||
try: | |||
frappe.flags.in_create_custom_fields = True | |||
doctypes_to_update = set() | |||
if frappe.flags.in_setup_wizard: | |||
ignore_validate = True | |||
for doctypes, fields in custom_fields.items(): | |||
if isinstance(fields, dict): | |||
# only one field | |||
fields = [fields] | |||
if isinstance(doctypes, str): | |||
# only one doctype | |||
doctypes = (doctypes,) | |||
for doctype in doctypes: | |||
doctypes_to_update.add(doctype) | |||
for df in fields: | |||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]}) | |||
if not field: | |||
try: | |||
df = df.copy() | |||
df["owner"] = "Administrator" | |||
create_custom_field(doctype, df, ignore_validate=ignore_validate) | |||
except frappe.exceptions.DuplicateEntryError: | |||
pass | |||
elif update: | |||
custom_field = frappe.get_doc("Custom Field", field) | |||
custom_field.flags.ignore_validate = ignore_validate | |||
custom_field.update(df) | |||
custom_field.save() | |||
for doctype in doctypes_to_update: | |||
frappe.clear_cache(doctype=doctype) | |||
frappe.db.updatedb(doctype) | |||
finally: | |||
frappe.flags.in_create_custom_fields = False | |||
@frappe.whitelist() | |||
@@ -224,8 +224,9 @@ class Database: | |||
raise | |||
elif isinstance(e, self.ProgrammingError): | |||
traceback.print_stack() | |||
frappe.errprint(f"Error in query:\n{query, values}") | |||
if frappe.conf.developer_mode: | |||
traceback.print_stack() | |||
frappe.errprint(f"Error in query:\n{query, values}") | |||
raise | |||
if not ( | |||
@@ -29,6 +29,7 @@ | |||
"default_incoming", | |||
"use_imap", | |||
"use_ssl", | |||
"use_starttls", | |||
"email_server", | |||
"incoming_port", | |||
"column_break_18", | |||
@@ -601,12 +602,20 @@ | |||
"fieldtype": "Select", | |||
"label": "Method", | |||
"options": "Basic\nOAuth" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "eval:!doc.domain && doc.enable_incoming && doc.use_imap && !doc.use_ssl", | |||
"fetch_from": "domain.use_starttls", | |||
"fieldname": "use_starttls", | |||
"fieldtype": "Check", | |||
"label": "Use STARTTLS" | |||
} | |||
], | |||
"icon": "fa fa-inbox", | |||
"index_web_pages_for_search": 1, | |||
"links": [], | |||
"modified": "2022-08-10 13:05:45.445572", | |||
"modified": "2022-08-16 13:05:45.445572", | |||
"modified_by": "Administrator", | |||
"module": "Email", | |||
"name": "Email Account", | |||
@@ -82,6 +82,7 @@ class EmailAccount(Document): | |||
return | |||
use_oauth = self.auth_method == "OAuth" | |||
self.use_starttls = cint(self.use_imap and self.use_starttls and not self.use_ssl) | |||
if getattr(self, "service", "") != "GMail" and use_oauth: | |||
self.auth_method = "Basic" | |||
@@ -187,6 +188,7 @@ class EmailAccount(Document): | |||
"use_imap", | |||
"email_server", | |||
"use_ssl", | |||
"use_starttls", | |||
"smtp_server", | |||
"use_tls", | |||
"smtp_port", | |||
@@ -209,6 +211,7 @@ class EmailAccount(Document): | |||
"email_account": self.name, | |||
"host": self.email_server, | |||
"use_ssl": self.use_ssl, | |||
"use_starttls": self.use_starttls, | |||
"username": getattr(self, "login_id", None) or self.email_id, | |||
"service": getattr(self, "service", ""), | |||
"use_imap": self.use_imap, | |||
@@ -12,6 +12,7 @@ | |||
"email_server", | |||
"use_imap", | |||
"use_ssl", | |||
"use_starttls", | |||
"column_break_9", | |||
"incoming_port", | |||
"attachment_limit", | |||
@@ -60,6 +61,13 @@ | |||
"fieldtype": "Check", | |||
"label": "Use SSL" | |||
}, | |||
{ | |||
"default": "0", | |||
"depends_on": "eval:doc.use_imap && !doc.use_ssl", | |||
"fieldname": "use_starttls", | |||
"fieldtype": "Check", | |||
"label": "Use STARTTLS" | |||
}, | |||
{ | |||
"description": "Ignore attachments over this size", | |||
"fieldname": "attachment_limit", | |||
@@ -125,7 +133,7 @@ | |||
"link_fieldname": "domain" | |||
} | |||
], | |||
"modified": "2022-08-10 21:20:50.692698", | |||
"modified": "2022-08-19 12:55:06.434541", | |||
"modified_by": "Administrator", | |||
"module": "Email", | |||
"name": "Email Domain", | |||
@@ -56,6 +56,7 @@ class EmailDomain(Document): | |||
"email_server", | |||
"use_imap", | |||
"use_ssl", | |||
"use_starttls", | |||
"use_tls", | |||
"attachment_limit", | |||
"smtp_server", | |||
@@ -76,10 +77,12 @@ class EmailDomain(Document): | |||
def validate_incoming_server_conn(self): | |||
self.incoming_port = get_port(self) | |||
conn_method = Timed_POP3_SSL if self.use_ssl else Timed_POP3 | |||
if self.use_imap: | |||
conn_method = Timed_IMAP4_SSL if self.use_ssl else Timed_IMAP4 | |||
else: | |||
conn_method = Timed_POP3_SSL if self.use_ssl else Timed_POP3 | |||
self.use_starttls = cint(self.use_imap and self.use_starttls and not self.use_ssl) | |||
incoming_conn = conn_method(self.email_server, port=self.incoming_port) | |||
incoming_conn.logout() if self.use_imap else incoming_conn.quit() | |||
@@ -33,6 +33,7 @@ class TestDomain(unittest.TestCase): | |||
# Also make sure that the other attributes match | |||
self.assertEqual(mail_account.use_imap, mail_domain.use_imap) | |||
self.assertEqual(mail_account.use_ssl, mail_domain.use_ssl) | |||
self.assertEqual(mail_account.use_starttls, mail_domain.use_starttls) | |||
self.assertEqual(mail_account.use_tls, mail_domain.use_tls) | |||
self.assertEqual(mail_account.attachment_limit, mail_domain.attachment_limit) | |||
self.assertEqual(mail_account.smtp_server, mail_domain.smtp_server) | |||
@@ -100,6 +100,9 @@ class EmailServer: | |||
self.settings.host, self.settings.incoming_port, timeout=frappe.conf.get("pop_timeout") | |||
) | |||
if cint(self.settings.use_starttls): | |||
self.imap.starttls() | |||
if self.settings.use_oauth: | |||
Oauth( | |||
self.imap, | |||
@@ -354,7 +354,7 @@ def login(): | |||
args = frappe.form_dict | |||
ldap: LDAPSettings = frappe.get_doc("LDAP Settings") | |||
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) | |||
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pop("pwd", None))) | |||
frappe.local.login_manager.user = user.name | |||
if should_run_2fa(user.name): | |||
@@ -327,7 +327,7 @@ Active Domains,Domaines Actifs, | |||
Active Sessions,Sessions Actives, | |||
Activity Log,Journal d'Activité, | |||
Activity log of all users.,Journal d'activité de tous les utilisateurs., | |||
Add / Manage Email Domains.,Ajouter / Gérer les Noms de Domaines des Emails, | |||
Add / Manage Email Domains.,Ajouter / Gérer les serveurs de courriels, | |||
Add / Update,Ajouter / mettre à jour, | |||
Add A New Rule,Ajouter une Nouvelle Règle, | |||
Add Another Comment,Ajouter un Autre Commentaire, | |||
@@ -959,8 +959,8 @@ Eg. smsgateway.com/api/send_sms.cgi,Eg. smsgateway.com / api / send_sms.cgi, | |||
Email Account Name,Nom du Compte Email, | |||
Email Account added multiple times,Compte Email ajouté plusieurs fois, | |||
Email Addresses,Adresse Email, | |||
Email Domain,Nom de Domaine Email, | |||
"Email Domain not configured for this account, Create one?","Nom de Domain de l'Email non configuré pour ce compte, En créer un ?", | |||
Email Domain,Serveur de courriels, | |||
"Email Domain not configured for this account, Create one?","Serveur de courriels non configuré pour ce compte, En créer un ?", | |||
Email Flag Queue,Liste d'Attente des d'Emails Marqués, | |||
Email Footer Address,Pied de Page Email, | |||
Email Group,Groupe Email, | |||
@@ -3619,7 +3619,7 @@ Upload {0} files,Télécharger des fichiers {0}, | |||
Uploaded To Google Drive,Téléchargé sur Google Drive, | |||
Uploaded successfully,Envoyé avec succès, | |||
Uploading {0} of {1},Téléchargement de {0} sur {1}, | |||
Use SSL for Outgoing,Utiliser SSL pour les sorties, | |||
Use SSL,Utiliser SSL | |||
Use Same Name,Utiliser le même nom, | |||
Used For Google Maps Integration.,Utilisé pour l'intégration de Google Maps., | |||
User ID Property,Propriété ID utilisateur, | |||
@@ -4726,3 +4726,18 @@ Always use this email address as sender address,Toujours utiliser cet email comm | |||
Always use this name as sender name,Toujours utiliser ce nom comme expediteur | |||
Login to {0},Se connecter à {0} | |||
Add / Remove Fields,Ajouter / Supprimer des colonnes | |||
Use STARTTLS,Utiliser le STARTTLS | |||
Outgoing Settings,Configurations des Courriels Sortant | |||
Incoming Settings,Configurations des Courriels Entrant | |||
Incoming Server,Serveur de Courriel Entrant | |||
Outgoing Server,Serveur de Courriel Sortant | |||
Use different Email ID,Utiliser une authentification email différente | |||
Alternative Email ID,Email de connexion alternatif | |||
Incoming (POP/IMAP) Settings,Configuration POP/IMAP | |||
IMAP Details,Détails IMAP | |||
IMAP Folder,Dossier IMAP à récupérer | |||
Document Linking,Lien vers les documents | |||
Auto Reply,Réponse automatique | |||
Footer Content,Contenue du pied de page | |||
Brand Logo,Logo de la marque | |||
Folder Name,Nom du dossier |
@@ -12,14 +12,26 @@ import pydoc | |||
import sys | |||
import traceback | |||
from ldap3.core.exceptions import LDAPInvalidCredentialsResult | |||
import frappe | |||
from frappe.utils import cstr, encode | |||
EXCLUDE_EXCEPTIONS = ( | |||
frappe.AuthenticationError, | |||
frappe.CSRFTokenError, # CSRF covers OAuth too | |||
frappe.SecurityException, | |||
LDAPInvalidCredentialsResult, | |||
) | |||
def make_error_snapshot(exception): | |||
if frappe.conf.disable_error_snapshot: | |||
return | |||
if isinstance(exception, EXCLUDE_EXCEPTIONS): | |||
return | |||
logger = frappe.logger(with_more_info=True) | |||
try: | |||
@@ -74,7 +74,7 @@ dependencies = [ | |||
"markdownify~=0.11.2", | |||
# integration dependencies | |||
"boto3~=1.17.53", | |||
"boto3~=1.18.49", | |||
"dropbox~=11.7.0", | |||
"google-api-python-client~=2.2.0", | |||
"google-auth-httplib2~=0.1.0", | |||