diff --git a/frappe/__init__.py b/frappe/__init__.py index b17bc1c2e4..575de13595 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template -__version__ = '7.2.10' +__version__ = '7.2.11' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/auth.py b/frappe/auth.py index fcaa9da0da..24c442980a 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -255,7 +255,7 @@ class LoginManager: self.run_trigger('on_logout') if user == frappe.session.user: - delete_session(frappe.session.sid) + delete_session(frappe.session.sid, user=user, reason="User Manually Logged Out") self.clear_cookies() else: clear_sessions(user) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 7c74e41834..67fef989ad 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -52,15 +52,16 @@ def clear_website_cache(context): frappe.destroy() @click.command('destroy-all-sessions') +@click.option('--reason') @pass_context -def destroy_all_sessions(context): +def destroy_all_sessions(context, reason=None): "Clear sessions of all users (logs them out)" import frappe.sessions for site in context.sites: try: frappe.init(site=site) frappe.connect() - frappe.sessions.clear_all_sessions() + frappe.sessions.clear_all_sessions(reason) frappe.db.commit() finally: frappe.destroy() diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index eb4bf2a9c2..2fcaa42b52 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -334,12 +334,13 @@ def add_attachments(name, attachments): # loop through attachments for a in attachments: - attach = frappe.db.get_value("File", {"name":a}, - ["file_name", "file_url", "is_private"], as_dict=1) + if isinstance(a, basestring): + attach = frappe.db.get_value("File", {"name":a}, + ["file_name", "file_url", "is_private"], as_dict=1) - # save attachments to new doc - save_url(attach.file_url, attach.file_name, "Communication", name, - "Home/Attachments", attach.is_private) + # save attachments to new doc + save_url(attach.file_url, attach.file_name, "Communication", name, + "Home/Attachments", attach.is_private) def filter_email_list(doc, email_list, exclude, is_cc=False): # temp variables diff --git a/frappe/core/doctype/communication/feed.py b/frappe/core/doctype/communication/feed.py index b9054f7218..b49bd1163e 100644 --- a/frappe/core/doctype/communication/feed.py +++ b/frappe/core/doctype/communication/feed.py @@ -58,6 +58,15 @@ def login_feed(login_manager): "full_name": get_fullname(login_manager.user) }) +def logout_feed(user, reason): + if not user: + return + + add_info_comment(**{ + "subject": _("{0} logged out: {1}").format(get_fullname(user), reason), + "full_name": get_fullname(user), + }) + def get_feed_match_conditions(user=None, force=True): if not user: user = frappe.session.user diff --git a/frappe/sessions.py b/frappe/sessions.py index 56c075ee17..cdfa07b806 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -61,7 +61,7 @@ def clear_sessions(user=None, keep_current=False, device=None): :param device: delete sessions of this device (default: desktop) ''' for sid in get_sessions_to_clear(user, keep_current, device): - delete_session(sid) + delete_session(sid, reason="Logged In From Another Session") def get_sessions_to_clear(user=None, keep_current=False, device=None): '''Returns sessions of the current user. Called at login / logout @@ -85,23 +85,30 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): if keep_current: condition = ' and sid != "{0}"'.format(frappe.db.escape(frappe.session.sid)) - return frappe.db.sql_list("""select sid from tabSessions where user=%s and device=%s {condition} order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit), (user, device)) -def delete_session(sid=None, user=None): +def delete_session(sid=None, user=None, reason="Session Expired"): + from frappe.core.doctype.communication.feed import logout_feed + frappe.cache().hdel("session", sid) frappe.cache().hdel("last_db_session_update", sid) + if sid and not user: + user_details = frappe.db.sql("""select user from tabSessions where sid=%s""", sid, as_dict=True) + if user_details: user = user_details[0].get("user") + + logout_feed(user, reason) frappe.db.sql("""delete from tabSessions where sid=%s""", sid) frappe.db.commit() -def clear_all_sessions(): +def clear_all_sessions(reason=None): """This effectively logs out all users""" frappe.only_for("Administrator") + if not reason: reason = "Deleted All Active Session" for sid in frappe.db.sql_list("select sid from `tabSessions`"): - delete_session(sid) + delete_session(sid, reason=reason) def get_expired_sessions(): '''Returns list of expired sessions''' @@ -113,11 +120,10 @@ def get_expired_sessions(): return expired - def clear_expired_sessions(): """This function is meant to be called from scheduler""" for sid in get_expired_sessions(): - delete_session(sid) + delete_session(sid, reason="Session Expired") def get(): """get session boot info""" @@ -335,7 +341,7 @@ class Session: return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2]) def delete_session(self): - delete_session(self.sid) + delete_session(self.sid, reason="Session Expired") def start_as_guest(self): """all guests share the same 'Guest' session"""