選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 
 

414 行
12 KiB

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