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.
 
 
 
 
 
 

324 lines
9.3 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. @frappe.whitelist()
  20. def clear(user=None):
  21. frappe.local.session_obj.update(force=True)
  22. frappe.local.db.commit()
  23. clear_cache(frappe.session.user)
  24. clear_global_cache()
  25. frappe.response['message'] = _("Cache Cleared")
  26. def clear_cache(user=None):
  27. cache = frappe.cache()
  28. groups = ("bootinfo", "user_recent", "user_roles", "user_doc", "lang",
  29. "defaults", "user_permissions", "roles")
  30. if user:
  31. for name in groups:
  32. cache.hdel(name, user)
  33. cache.delete_keys("user:" + user)
  34. frappe.defaults.clear_cache(user)
  35. else:
  36. for name in groups:
  37. cache.delete_key(name, user)
  38. clear_global_cache()
  39. frappe.defaults.clear_cache()
  40. def clear_global_cache():
  41. frappe.model.meta.clear_cache()
  42. frappe.cache().delete_value(["app_hooks", "installed_apps",
  43. "app_modules", "module_app", "time_zone", "notification_config"])
  44. frappe.setup_module_map()
  45. def clear_sessions(user=None, keep_current=False):
  46. if not user:
  47. user = frappe.session.user
  48. for sid in frappe.db.sql("""select sid from tabSessions where user=%s and device=%s""",
  49. (user, frappe.session.device or "desktop")):
  50. if keep_current and frappe.session.sid==sid[0]:
  51. continue
  52. else:
  53. delete_session(sid[0])
  54. def delete_session(sid=None, user=None):
  55. if not user:
  56. user = hasattr(frappe.local, "session") and frappe.session.user or "Guest"
  57. frappe.cache().hdel("session", sid)
  58. frappe.cache().hdel("last_db_session_update", sid)
  59. frappe.db.sql("""delete from tabSessions where sid=%s""", sid)
  60. def clear_all_sessions():
  61. """This effectively logs out all users"""
  62. frappe.only_for("Administrator")
  63. for sid in frappe.db.sql_list("select sid from `tabSessions`"):
  64. delete_session(sid)
  65. def clear_expired_sessions():
  66. """This function is meant to be called from scheduler"""
  67. for device in ("desktop", "mobile"):
  68. for sid in frappe.db.sql_list("""select sid from tabSessions
  69. where TIMEDIFF(NOW(), lastupdate) > TIME(%s)
  70. and device = %s""", (device, get_expiry_period(device))):
  71. delete_session(sid)
  72. def get():
  73. """get session boot info"""
  74. from frappe.desk.notifications import \
  75. get_notification_info_for_boot, get_notifications
  76. from frappe.boot import get_bootinfo
  77. bootinfo = None
  78. if not getattr(frappe.conf,'disable_session_cache', None):
  79. # check if cache exists
  80. bootinfo = frappe.cache().hget("bootinfo", frappe.session.user)
  81. if bootinfo:
  82. bootinfo['from_cache'] = 1
  83. bootinfo["notification_info"].update(get_notifications())
  84. bootinfo["user"]["recent"] = json.dumps(\
  85. frappe.cache().hget("user_recent", frappe.session.user))
  86. if not bootinfo:
  87. # if not create it
  88. bootinfo = get_bootinfo()
  89. bootinfo["notification_info"] = get_notification_info_for_boot()
  90. frappe.cache().hset("bootinfo", frappe.session.user, bootinfo)
  91. try:
  92. frappe.cache().ping()
  93. except redis.exceptions.ConnectionError:
  94. message = _("Redis cache server not running. Please contact Administrator / Tech support")
  95. if 'messages' in bootinfo:
  96. bootinfo['messages'].append(message)
  97. else:
  98. bootinfo['messages'] = [message]
  99. # check only when clear cache is done, and don't cache this
  100. if frappe.local.request:
  101. bootinfo["change_log"] = get_change_log()
  102. bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")
  103. if not bootinfo["metadata_version"]:
  104. bootinfo["metadata_version"] = frappe.reset_metadata_version()
  105. for hook in frappe.get_hooks("extend_bootinfo"):
  106. frappe.get_attr(hook)(bootinfo=bootinfo)
  107. bootinfo["lang"] = frappe.translate.get_user_lang()
  108. return bootinfo
  109. class Session:
  110. def __init__(self, user, resume=False, full_name=None, user_type=None):
  111. self.sid = cstr(frappe.form_dict.get('sid') or unquote(frappe.request.cookies.get('sid', 'Guest')))
  112. self.user = user
  113. self.device = frappe.form_dict.get("device") or "desktop"
  114. self.user_type = user_type
  115. self.full_name = full_name
  116. self.data = frappe._dict({'data': frappe._dict({})})
  117. self.time_diff = None
  118. # set local session
  119. frappe.local.session = self.data
  120. if resume:
  121. self.resume()
  122. else:
  123. self.start()
  124. def start(self):
  125. """start a new session"""
  126. # generate sid
  127. if self.user=='Guest':
  128. sid = 'Guest'
  129. else:
  130. sid = frappe.generate_hash()
  131. self.data.user = self.user
  132. self.data.sid = sid
  133. self.data.data.user = self.user
  134. self.data.data.session_ip = frappe.local.request_ip
  135. if self.user != "Guest":
  136. self.data.data.update({
  137. "last_updated": frappe.utils.now(),
  138. "session_expiry": get_expiry_period(self.device),
  139. "full_name": self.full_name,
  140. "user_type": self.user_type,
  141. "device": self.device,
  142. "session_country": get_geo_ip_country(frappe.local.request_ip)
  143. })
  144. # insert session
  145. if self.user!="Guest":
  146. self.insert_session_record()
  147. # update user
  148. frappe.db.sql("""UPDATE tabUser SET last_login = %s, last_ip = %s
  149. where name=%s""", (frappe.utils.now(), frappe.local.request_ip, self.data['user']))
  150. frappe.db.commit()
  151. def insert_session_record(self):
  152. frappe.db.sql("""insert into tabSessions
  153. (sessiondata, user, lastupdate, sid, status, device)
  154. values (%s , %s, NOW(), %s, 'Active', %s)""",
  155. (str(self.data['data']), self.data['user'], self.data['sid'], self.device))
  156. # also add to memcache
  157. frappe.cache().hset("session", self.data.sid, self.data)
  158. def resume(self):
  159. """non-login request: load a session"""
  160. import frappe
  161. data = self.get_session_record()
  162. if data:
  163. # set language
  164. self.data.update({'data': data, 'user':data.user, 'sid': self.sid})
  165. else:
  166. self.start_as_guest()
  167. if self.sid != "Guest":
  168. frappe.local.user_lang = frappe.translate.get_user_lang(self.data.user)
  169. frappe.local.lang = frappe.local.user_lang
  170. def get_session_record(self):
  171. """get session record, or return the standard Guest Record"""
  172. from frappe.auth import clear_cookies
  173. r = self.get_session_data()
  174. if not r:
  175. frappe.response["session_expired"] = 1
  176. clear_cookies()
  177. self.sid = "Guest"
  178. r = self.get_session_data()
  179. return r
  180. def get_session_data(self):
  181. if self.sid=="Guest":
  182. return frappe._dict({"user":"Guest"})
  183. data = self.get_session_data_from_cache()
  184. if not data:
  185. data = self.get_session_data_from_db()
  186. return data
  187. def get_session_data_from_cache(self):
  188. data = frappe.cache().hget("session", self.sid)
  189. if data:
  190. data = frappe._dict(data)
  191. session_data = data.get("data", {})
  192. # set user for correct timezone
  193. self.time_diff = frappe.utils.time_diff_in_seconds(frappe.utils.now(),
  194. session_data.get("last_updated"))
  195. expiry = self.get_expiry_in_seconds(session_data.get("session_expiry"))
  196. if self.time_diff > expiry:
  197. self.delete_session()
  198. data = None
  199. return data and data.data
  200. def get_session_data_from_db(self):
  201. rec = frappe.db.sql("""select user, sessiondata
  202. from tabSessions where sid=%s and
  203. TIMEDIFF(NOW(), lastupdate) < TIME(%s)""", (self.sid,
  204. get_expiry_period(self.device)))
  205. if rec:
  206. data = frappe._dict(eval(rec and rec[0][1] or '{}'))
  207. data.user = rec[0][0]
  208. else:
  209. self.delete_session()
  210. data = None
  211. return data
  212. def get_expiry_in_seconds(self, expiry):
  213. if not expiry: return 3600
  214. parts = expiry.split(":")
  215. return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2])
  216. def delete_session(self):
  217. delete_session(self.sid, user=self.user)
  218. def start_as_guest(self):
  219. """all guests share the same 'Guest' session"""
  220. self.user = "Guest"
  221. self.start()
  222. def update(self, force=False):
  223. """extend session expiry"""
  224. if (frappe.session['user'] == "Guest" or frappe.form_dict.cmd=="logout"):
  225. return
  226. now = frappe.utils.now()
  227. self.data['data']['last_updated'] = now
  228. self.data['data']['lang'] = unicode(frappe.lang)
  229. # update session in db
  230. last_updated = frappe.cache().hget("last_db_session_update", self.sid)
  231. time_diff = frappe.utils.time_diff_in_seconds(now, last_updated) if last_updated else None
  232. # database persistence is secondary, don't update it too often
  233. updated_in_db = False
  234. if force or (time_diff==None) or (time_diff > 600):
  235. frappe.db.sql("""update tabSessions set sessiondata=%s,
  236. lastupdate=NOW() where sid=%s""" , (str(self.data['data']),
  237. self.data['sid']))
  238. frappe.cache().hset("last_db_session_update", self.sid, now)
  239. updated_in_db = True
  240. # set in memcache
  241. frappe.cache().hset("session", self.sid, self.data)
  242. return updated_in_db
  243. def get_expiry_period(device="desktop"):
  244. if device=="desktop":
  245. key = "session_expiry"
  246. default = "06:00:00"
  247. else:
  248. key = "session_expiry_mobile"
  249. default = "720:00:00"
  250. exp_sec = frappe.defaults.get_global_default(key) or default
  251. # incase seconds is missing
  252. if len(exp_sec.split(':')) == 2:
  253. exp_sec = exp_sec + ':00'
  254. return exp_sec
  255. def get_geo_from_ip(ip_addr):
  256. try:
  257. from geoip import geolite2
  258. return geolite2.lookup(ip_addr)
  259. except ImportError:
  260. return
  261. except ValueError:
  262. return
  263. def get_geo_ip_country(ip_addr):
  264. match = get_geo_from_ip(ip_addr)
  265. if match:
  266. return match.country