You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

421 lines
12 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. """
  5. Boot session from cache or build
  6. Session bootstraps info needed by common client side activities including
  7. permission, homepage, default variables, system defaults etc
  8. """
  9. import frappe, json
  10. from frappe import _
  11. import frappe.utils
  12. from frappe.utils import cint, cstr
  13. import frappe.model.meta
  14. import frappe.defaults
  15. import frappe.translate
  16. from frappe.utils.change_log import get_change_log
  17. import redis
  18. from urllib import unquote
  19. from frappe.desk.notifications import clear_notifications
  20. @frappe.whitelist()
  21. def clear(user=None):
  22. frappe.local.session_obj.update(force=True)
  23. frappe.local.db.commit()
  24. clear_cache(frappe.session.user)
  25. clear_global_cache()
  26. frappe.response['message'] = _("Cache Cleared")
  27. def clear_cache(user=None):
  28. cache = frappe.cache()
  29. groups = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
  30. "defaults", "user_permissions", "roles", "home_page", "linked_with",
  31. "desktop_icons", 'portal_menu_items')
  32. if user:
  33. for name in groups:
  34. cache.hdel(name, user)
  35. cache.delete_keys("user:" + user)
  36. frappe.defaults.clear_cache(user)
  37. else:
  38. for name in groups:
  39. cache.delete_key(name)
  40. clear_global_cache()
  41. frappe.defaults.clear_cache()
  42. clear_notifications(user)
  43. def clear_global_cache():
  44. frappe.model.meta.clear_cache()
  45. frappe.cache().delete_value(["app_hooks", "installed_apps",
  46. "app_modules", "module_app", "notification_config", 'system_settings'
  47. 'scheduler_events', 'time_zone'])
  48. frappe.setup_module_map()
  49. def clear_sessions(user=None, keep_current=False, device=None):
  50. '''Clear other sessions of the current user. Called at login / logout
  51. :param user: user name (default: current user)
  52. :param keep_current: keep current session (default: false)
  53. :param device: delete sessions of this device (default: desktop)
  54. '''
  55. for sid in get_sessions_to_clear(user, keep_current, device):
  56. delete_session(sid, reason="Logged In From Another Session")
  57. def get_sessions_to_clear(user=None, keep_current=False, device=None):
  58. '''Returns sessions of the current user. Called at login / logout
  59. :param user: user name (default: current user)
  60. :param keep_current: keep current session (default: false)
  61. :param device: delete sessions of this device (default: desktop)
  62. '''
  63. if not user:
  64. user = frappe.session.user
  65. if not device:
  66. device = frappe.session.data.device or "desktop"
  67. limit = 0
  68. if user == frappe.session.user:
  69. simultaneous_sessions = frappe.db.get_value('User', user, 'simultaneous_sessions') or 1
  70. limit = simultaneous_sessions - 1
  71. condition = ''
  72. if keep_current:
  73. condition = ' and sid != "{0}"'.format(frappe.db.escape(frappe.session.sid))
  74. return frappe.db.sql_list("""select sid from tabSessions
  75. where user=%s and device=%s {condition}
  76. order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit),
  77. (user, device))
  78. def delete_session(sid=None, user=None, reason="Session Expired"):
  79. from frappe.core.doctype.communication.feed import logout_feed
  80. frappe.cache().hdel("session", sid)
  81. frappe.cache().hdel("last_db_session_update", sid)
  82. if sid and not user:
  83. user_details = frappe.db.sql("""select user from tabSessions where sid=%s""", sid, as_dict=True)
  84. if user_details: user = user_details[0].get("user")
  85. logout_feed(user, reason)
  86. frappe.db.sql("""delete from tabSessions where sid=%s""", sid)
  87. frappe.db.commit()
  88. def clear_all_sessions(reason=None):
  89. """This effectively logs out all users"""
  90. frappe.only_for("Administrator")
  91. if not reason: reason = "Deleted All Active Session"
  92. for sid in frappe.db.sql_list("select sid from `tabSessions`"):
  93. delete_session(sid, reason=reason)
  94. def get_expired_sessions():
  95. '''Returns list of expired sessions'''
  96. expired = []
  97. for device in ("desktop", "mobile"):
  98. expired += frappe.db.sql_list("""select sid from tabSessions
  99. where TIMEDIFF(NOW(), lastupdate) > TIME(%s)
  100. and device = %s""", (get_expiry_period(device), device))
  101. return expired
  102. def clear_expired_sessions():
  103. """This function is meant to be called from scheduler"""
  104. for sid in get_expired_sessions():
  105. delete_session(sid, reason="Session Expired")
  106. def get():
  107. """get session boot info"""
  108. from frappe.desk.notifications import \
  109. get_notification_info_for_boot, get_notifications
  110. from frappe.boot import get_bootinfo, get_unseen_notes
  111. from frappe.limits import get_limits, get_expiry_message
  112. bootinfo = None
  113. if not getattr(frappe.conf,'disable_session_cache', None):
  114. # check if cache exists
  115. bootinfo = frappe.cache().hget("bootinfo", frappe.session.user)
  116. if bootinfo:
  117. bootinfo['from_cache'] = 1
  118. bootinfo["notification_info"].update(get_notifications())
  119. bootinfo["user"]["recent"] = json.dumps(\
  120. frappe.cache().hget("user_recent", frappe.session.user))
  121. if not bootinfo:
  122. # if not create it
  123. bootinfo = get_bootinfo()
  124. bootinfo["notification_info"] = get_notification_info_for_boot()
  125. frappe.cache().hset("bootinfo", frappe.session.user, bootinfo)
  126. try:
  127. frappe.cache().ping()
  128. except redis.exceptions.ConnectionError:
  129. message = _("Redis cache server not running. Please contact Administrator / Tech support")
  130. if 'messages' in bootinfo:
  131. bootinfo['messages'].append(message)
  132. else:
  133. bootinfo['messages'] = [message]
  134. # check only when clear cache is done, and don't cache this
  135. if frappe.local.request:
  136. bootinfo["change_log"] = get_change_log()
  137. bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")
  138. if not bootinfo["metadata_version"]:
  139. bootinfo["metadata_version"] = frappe.reset_metadata_version()
  140. bootinfo.notes = get_unseen_notes()
  141. for hook in frappe.get_hooks("extend_bootinfo"):
  142. frappe.get_attr(hook)(bootinfo=bootinfo)
  143. bootinfo["lang"] = frappe.translate.get_user_lang()
  144. bootinfo["disable_async"] = frappe.conf.disable_async
  145. # limits
  146. bootinfo.limits = get_limits()
  147. bootinfo.expiry_message = get_expiry_message()
  148. return bootinfo
  149. def get_csrf_token():
  150. if not frappe.local.session.data.csrf_token:
  151. generate_csrf_token()
  152. return frappe.local.session.data.csrf_token
  153. def generate_csrf_token():
  154. frappe.local.session.data.csrf_token = frappe.generate_hash()
  155. frappe.local.session_obj.update(force=True)
  156. # send sid and csrf token to the user
  157. # handles the case when a user logs in again from another tab
  158. # and it leads to invalid request in the current tab
  159. frappe.publish_realtime(event="csrf_generated",
  160. message={"sid": frappe.local.session.sid, "csrf_token": frappe.local.session.data.csrf_token},
  161. user=frappe.session.user, after_commit=True)
  162. class Session:
  163. def __init__(self, user, resume=False, full_name=None, user_type=None):
  164. self.sid = cstr(frappe.form_dict.get('sid') or
  165. unquote(frappe.request.cookies.get('sid', 'Guest')))
  166. self.user = user
  167. self.device = frappe.form_dict.get("device") or "desktop"
  168. self.user_type = user_type
  169. self.full_name = full_name
  170. self.data = frappe._dict({'data': frappe._dict({})})
  171. self.time_diff = None
  172. # set local session
  173. frappe.local.session = self.data
  174. if resume:
  175. self.resume()
  176. else:
  177. if self.user:
  178. self.start()
  179. def start(self):
  180. """start a new session"""
  181. # generate sid
  182. if self.user=='Guest':
  183. sid = 'Guest'
  184. else:
  185. sid = frappe.generate_hash()
  186. self.data.user = self.user
  187. self.data.sid = sid
  188. self.data.data.user = self.user
  189. self.data.data.session_ip = frappe.local.request_ip
  190. if self.user != "Guest":
  191. self.data.data.update({
  192. "last_updated": frappe.utils.now(),
  193. "session_expiry": get_expiry_period(self.device),
  194. "full_name": self.full_name,
  195. "user_type": self.user_type,
  196. "device": self.device,
  197. "session_country": get_geo_ip_country(frappe.local.request_ip) if frappe.local.request_ip else None,
  198. })
  199. # insert session
  200. if self.user!="Guest":
  201. self.insert_session_record()
  202. # update user
  203. frappe.db.sql("""UPDATE tabUser SET last_login = %(now)s, last_ip = %(ip)s, last_active = %(now)s
  204. where name=%(name)s""", {
  205. "now": frappe.utils.now(),
  206. "ip": frappe.local.request_ip,
  207. "name": self.data['user']
  208. })
  209. frappe.db.commit()
  210. def insert_session_record(self):
  211. frappe.db.sql("""insert into tabSessions
  212. (sessiondata, user, lastupdate, sid, status, device)
  213. values (%s , %s, NOW(), %s, 'Active', %s)""",
  214. (str(self.data['data']), self.data['user'], self.data['sid'], self.device))
  215. # also add to memcache
  216. frappe.cache().hset("session", self.data.sid, self.data)
  217. def resume(self):
  218. """non-login request: load a session"""
  219. import frappe
  220. data = self.get_session_record()
  221. if data:
  222. # set language
  223. self.data.update({'data': data, 'user':data.user, 'sid': self.sid})
  224. self.user = data.user
  225. self.device = data.device
  226. else:
  227. self.start_as_guest()
  228. if self.sid != "Guest":
  229. frappe.local.user_lang = frappe.translate.get_user_lang(self.data.user)
  230. frappe.local.lang = frappe.local.user_lang
  231. def get_session_record(self):
  232. """get session record, or return the standard Guest Record"""
  233. from frappe.auth import clear_cookies
  234. r = self.get_session_data()
  235. if not r:
  236. frappe.response["session_expired"] = 1
  237. clear_cookies()
  238. self.sid = "Guest"
  239. r = self.get_session_data()
  240. return r
  241. def get_session_data(self):
  242. if self.sid=="Guest":
  243. return frappe._dict({"user":"Guest"})
  244. data = self.get_session_data_from_cache()
  245. if not data:
  246. data = self.get_session_data_from_db()
  247. return data
  248. def get_session_data_from_cache(self):
  249. data = frappe.cache().hget("session", self.sid)
  250. if data:
  251. data = frappe._dict(data)
  252. session_data = data.get("data", {})
  253. # set user for correct timezone
  254. self.time_diff = frappe.utils.time_diff_in_seconds(frappe.utils.now(),
  255. session_data.get("last_updated"))
  256. expiry = self.get_expiry_in_seconds(session_data.get("session_expiry"))
  257. if self.time_diff > expiry:
  258. self.delete_session()
  259. data = None
  260. return data and data.data
  261. def get_session_data_from_db(self):
  262. self.device = frappe.db.sql('select device from tabSessions where sid=%s', self.sid)
  263. self.device = self.device and self.device[0][0] or 'desktop'
  264. rec = frappe.db.sql("""select user, sessiondata
  265. from tabSessions where sid=%s and
  266. TIMEDIFF(NOW(), lastupdate) < TIME(%s)""", (self.sid,
  267. get_expiry_period(self.device)))
  268. if rec:
  269. data = frappe._dict(eval(rec and rec[0][1] or '{}'))
  270. data.user = rec[0][0]
  271. else:
  272. self.delete_session()
  273. data = None
  274. return data
  275. def get_expiry_in_seconds(self, expiry):
  276. if not expiry:
  277. return 3600
  278. parts = expiry.split(":")
  279. return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2])
  280. def delete_session(self):
  281. delete_session(self.sid, reason="Session Expired")
  282. def start_as_guest(self):
  283. """all guests share the same 'Guest' session"""
  284. self.user = "Guest"
  285. self.start()
  286. def update(self, force=False):
  287. """extend session expiry"""
  288. if (frappe.session['user'] == "Guest" or frappe.form_dict.cmd=="logout"):
  289. return
  290. now = frappe.utils.now()
  291. self.data['data']['last_updated'] = now
  292. self.data['data']['lang'] = unicode(frappe.lang)
  293. # update session in db
  294. last_updated = frappe.cache().hget("last_db_session_update", self.sid)
  295. time_diff = frappe.utils.time_diff_in_seconds(now, last_updated) if last_updated else None
  296. # database persistence is secondary, don't update it too often
  297. updated_in_db = False
  298. if force or (time_diff==None) or (time_diff > 600):
  299. # update sessions table
  300. frappe.db.sql("""update tabSessions set sessiondata=%s,
  301. lastupdate=NOW() where sid=%s""" , (str(self.data['data']),
  302. self.data['sid']))
  303. # update last active in user table
  304. frappe.db.sql("""update `tabUser` set last_active=%(now)s where name=%(name)s""", {
  305. "now": now,
  306. "name": frappe.session.user
  307. })
  308. frappe.db.commit()
  309. frappe.cache().hset("last_db_session_update", self.sid, now)
  310. updated_in_db = True
  311. # set in memcache
  312. frappe.cache().hset("session", self.sid, self.data)
  313. return updated_in_db
  314. def get_expiry_period(device="desktop"):
  315. if device=="mobile":
  316. key = "session_expiry_mobile"
  317. default = "720:00:00"
  318. else:
  319. key = "session_expiry"
  320. default = "06:00:00"
  321. exp_sec = frappe.defaults.get_global_default(key) or default
  322. # incase seconds is missing
  323. if len(exp_sec.split(':')) == 2:
  324. exp_sec = exp_sec + ':00'
  325. return exp_sec
  326. def get_geo_from_ip(ip_addr):
  327. try:
  328. from geoip import geolite2
  329. return geolite2.lookup(ip_addr)
  330. except ImportError:
  331. return
  332. except ValueError:
  333. return
  334. def get_geo_ip_country(ip_addr):
  335. match = get_geo_from_ip(ip_addr)
  336. if match:
  337. return match.country