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/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/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 66fedc53aa..fc2bb91c57 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -59,7 +59,8 @@ class DocType(Document): self.make_amendable() self.validate_website() - self.update_fields_to_fetch() + if not self.is_new(): + self.update_fields_to_fetch() def check_developer_mode(self): """Throw exception if not developer mode or via patch""" diff --git a/frappe/sessions.py b/frappe/sessions.py index 2a16489ab4..898ced3e0c 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -73,19 +73,27 @@ def clear_sessions(user=None, keep_current=False, device=None): where user=%s and device=%s {condition} order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit), (user, device))): - delete_session(sid) + delete_session(sid, reason="Logged In From Another Session") + +def delete_session(sid=None, user=None, reason="Session Expired"): + from frappe.core.doctype.communication.feed import logout_feed -def delete_session(sid=None, user=None): 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 clear_expired_sessions(): """This function is meant to be called from scheduler""" @@ -93,7 +101,7 @@ def clear_expired_sessions(): for sid in frappe.db.sql_list("""select sid from tabSessions where TIMEDIFF(NOW(), lastupdate) > TIME(%s) and device = %s""", (get_expiry_period(device), device)): - delete_session(sid) + delete_session(sid, reason="Session Expired") def get(): """get session boot info""" @@ -310,7 +318,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"""