25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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